Skip to content

docs planning NATIVE_NAVIGATION_PLAN

AlexandreZanata edited this page Nov 5, 2026 · 1 revision

Native navigation like CoMaps (route from my position, follow camera, voice)

Status: PLANNED (2026-10-28). Owner track: MC-C7 in MAP_COMPLETION_PLAN.md. Engine: pinned CoMaps 4587b8ad SDK (maps/engine/vendor/sdk-release.aar). Delivery: one planning stage = one PR (codex/phase-native-navigation).

1. Goal

Inside the Abastevo map, a driver gets the same routing experience as the CoMaps app: tap a station (or any point) → route from the current location → preview → Start → animated follow/perspective camera with the my-position arrow, turn panel, voice, automatic rerouting and arrival, in the foreground and with the screen off, offline once the region is installed.

"Online" and "offline" use the same engine: CoMaps routes only from the local .mwm routing sections, with no server routing or live traffic. Online only adds downloading a missing region, which goes through the Abastevo signed distribution, not CoMaps servers.

2. Baseline (what exists, verified 2026-10-28)

Already in place:

Area State
Route build ComapsRoutePlanner (maps/engine/.../ComapsRoutePlanner.kt): car/avoid options, preview distance/time/geometry/steps. It owns the native routing listener, so the SDK RoutingController is bypassed.
Follow route ComapsMapEngine.startGuidance calls nativeFollowRoute. The native camera (DrapeEngine::FollowRoute, navigation scale, 3D, rotation) runs once fixes arrive and a surface is rendered. Not yet verified visually.
Fix feed GuidanceService (location foreground service, GPS at 1 Hz) → NavigationFixPolicy → NavigationFixBridge → LocationState.nativeLocationUpdated.
Reroute Native reroute plus a 60 s "not recoverable" timeout (GuidancePolicy).
Voice LocalGuidanceVoice (Android TTS, local voices only, audio focus).
Recovery GuidanceRecoveryStore with explicit resume, and a map-generation lease.
Tests Domain/application/engine JVM tests, plus device tests GuidanceEngineInstrumentedTest, GuidanceInstrumentedTest, NativeRoutePlannerInstrumentedTest and NativeRouteCrossRegionInstrumentedTest.

Gaps against CoMaps:

  1. Start point. Every point is added with isMyPosition=false. The start is a one-shot "Current" coordinate, and the station "Route" action fills only the destination.
  2. My position. The SDK LocationHelper is never started. There is no LocationState mode machine, no my-position arrow outside guidance and no compass. recenter() reads locationHelper.myPosition, which is null, so it likely always reports "no-fix".
  3. Guidance UI. Guidance controls live inside the dismissible route sheet (MapScreen.kt:119). There is no on-map turn panel or bottom navigation bar.
  4. Dropped turn data. toNavigationProgress drops lanes, exit number, then-turn, speed limit, current street, road shields and distToNextStop.
  5. Voice.
    • Only notifications.firstOrNull() is spoken.
    • LocalGuidanceVoice.start() waits up to 8 s on the main thread for TTS onInit, which is also delivered on main. This is a suspected stall followed by "voice unavailable". Confirm it first.
  6. No arrival handling. nativeIsRouteFinished is never called.
  7. Notification. It is static: no turn, distance or street.
  8. Permissions. The guidance UI never requests FINE location or (API 33+) POST_NOTIFICATIONS, so GuidanceService refuses to start silently.
  9. No camera tuning. There is no 3D/auto-zoom setting and no updateMyPositionRoutingOffset for the bottom panel.
  10. Contract conflict. docs/product/MAP_21_23_CONTRACT.md says "browsing never starts continuous GPS". CoMaps tracks location whenever the map is visible and the user enabled it.

All SDK classes and natives needed are present in the shipped jar and in both liborganicmaps.so ABIs:

  • Classes: RoutingController, LocationHelper, LocationState, TtsPlayer, CarDirection, RoutingInfo, LaneInfo, RoadShieldUtils, RouteSimulationProvider, MapController.updateMyPositionRoutingOffset, SensorHelper.
  • Framework natives: nativeFollowRoute, nativeIsRouteFinished, nativeSetAutoReroute, nativeSet3dMode, nativeSetAutoZoomEnabled, nativeGetIntermediateStopsProgress.

The UI layer is app-only in CoMaps and must be ported:

  • Navigation views: NavigationController, NavMenu, LanesView, SpeedLimitView.
  • Location and service: MyPositionButton, NavigationService.
  • Routing plan fragments.

3. Phases

Each task follows B-BR/BUC → RED → GREEN → REFACTOR, runs its targeted tests, and is verified on the POCO with the release build (test key) before the phase closes.

NAV-0 Decisions and contract (blocking, no code)

  • NAV-0.1 Product decision. Continuous location while the Map tab is visible, only after the user taps my-position or starts a route. The default stays "no GPS while browsing". Stop on background unless navigating. Update MAP_21_23_CONTRACT.md and the scripts/check-mobile.sh location allowlist in the same task.

  • NAV-0.2 Licence record. Porting CoMaps android/app code (Apache-2.0) to Kotlin is allowed if we:

    • keep the Apache header and NOTICE on ported files;
    • mark the files as modified (§4(b));
    • use no CoMaps/Organic Maps branding;
    • copy only drawables whose provenance in data/copyright.html allows it. The turn icons already ship in the AAR.

    Record this in maps/engine/vendor/NOTICE and in an evidence note.

  • NAV-0.3 Engine ownership. Keep the Abastevo planner as the single owner of the native routing listener. It is stricter on NEED_MORE_MAPS than the SDK RoutingController. Do not mix in RoutingController; document why.

NAV-1 My position and recenter (CoMaps parity)

  • NAV-1.1 Single location feed.
    • Start the SDK LocationHelper (start/restartWithNewMode/addListener, 500 ms) while the map is foreground and location is enabled. Stop it on ON_STOP unless navigating.
    • GuidanceService reuses the same feed instead of its own GPS_PROVIDER subscription. One feed means one arrow.
    • Keep NavigationFixPolicy as a filter, but relax the 50 m accuracy cap to CoMaps behaviour: accept GPS-provider fixes and let the core weight accuracy. Add an urban-canyon test.
  • NAV-1.2 Mode machine.
    • Register LocationState.nativeSetListener. Expose MyPositionMode (PENDING, NOT_FOLLOW_NO_POSITION, NOT_FOLLOW, FOLLOW, FOLLOW_AND_ROTATE) in MapUiState.
    • Report denial and GPS off through nativeOnLocationError.
    • Never call getMode() before the drape engine exists.
  • NAV-1.3 My-position button. Port MyPositionButton to Compose, with a per-mode icon and a nativeSwitchToNextMode click. It replaces today's one-shot recenter. A permission request on first tap; the pending spinner state.
  • NAV-1.4 Compass. SensorHelper → Map.onCompassUpdated for FOLLOW_AND_ROTATE.
  • Tests:
    • JVM: mode reducer and permission states.
    • Device: the arrow appears, a pan leaves FOLLOW and a tap returns to it. Use simulated locations (RouteSimulationProvider) and no real-user GPS in fixtures.

NAV-2 Route from my position

  • NAV-2.1 Add a "Minha localização" start point with isMyPosition=true (nativeAddRoutePoint type START). This is the default origin.
    • The core then routes and reroutes from the live MyPositionMark.
    • Map NO_POSITION (2) to a pending state that waits for the first fix (nativeStartPendingPositionMode), then builds.
  • NAV-2.2 The station sheet "Rota" sets origin = my position and destination = station, then opens the preview directly, as CoMaps startLocationToPoint does.
  • NAV-2.3 Missing region (NEED_MORE_MAPS) → the Abastevo regional download card or screen for the named regions, then rebuild.
  • Tests: planner JVM tests for the start type and the NO_POSITION path; device test station → preview offline in Mato Grosso.

NAV-3 Route preview on the map

  • NAV-3.1 Replace the modal sheet with an on-map preview. A bottom card shows time, distance, ETA (local clock), avoid chips and Start. A steps sheet shows CarDirection.getTurnRes() icons and street names. The route line is drawn by the core.
  • NAV-3.2 Before Start, request FINE location and POST_NOTIFICATIONS (API 33+) in context. On refusal, explain once and keep the preview usable. Mirror CoMaps requestPostNotificationsPermission.
  • NAV-3.3 Visible rect: nativeSetVisibleRect so the route fits above the card.

NAV-4 Navigation mode (the "Google/CoMaps feel")

  • NAV-4.1 Data. Extend NavigationProgress with:

    • nextCarDirection, exitNum, lanes, currentStreet, nextStreet (with road shields via RoadShieldUtils);
    • speedLimitMps, distToNextStop, isLeftHandTraffic, shouldPlayWarningSignal;
    • nativeGetIntermediateStopsProgress.

    Pure domain value objects; mapping tests.

  • NAV-4.2 Turn panel (top, Compose port of NavigationController and LanesView): turn icon, distance to the turn (nativeFormatSpeed/distance formatting), then-turn, roundabout exit, lanes, street with shields.

  • NAV-4.3 Bottom bar (port of NavMenu and RouteProgressBar): ETA clock, time left, distance left, progress, mute, stop. Current speed vs speed limit (SpeedLimitView).

  • NAV-4.4 Camera.

    • nativeSet3dMode(true, buildings) and nativeSetAutoZoomEnabled(true) as the defaults, both editable in settings.
    • updateMyPositionRoutingOffset(bottomBarHeight) through a new same-package accessor in MapViewHelper.
    • Feed fixes at 2 Hz.
    • The native follow camera returns to FOLLOW 20 s after a pan (kMaxNotFollowRoutingTimeSec). Show a "Recentralizar" chip meanwhile.
  • NAV-4.5 Surface rules. Navigation keeps a live MapView while foreground. nativeFollowRoute without a rendered surface crashed (MAP-23 defect 1). Background continues through the service with the surface released safely; add a regression test.

  • NAV-4.6 Screen. FLAG_KEEP_SCREEN_ON while navigating. Prompt once about battery optimisation, as CoMaps does.

NAV-5 Voice

  • NAV-5.1 Initialise TTS asynchronously, never blocking main. Fix the suspected 8 s stall with a device test that starts the service from the main thread.
  • NAV-5.2 Speak every string from nativeGenerateNotifications(announceStreets), in order, as TtsPlayer.playTurnNotifications does. Turn texts come from the native sound-strings (pt-BR ships in the AAR).
  • NAV-5.3 Voice settings: on/off, language (supported list from TtsPlayer natives), street names, and a test voice. The mute toggle sits in the bottom bar.

NAV-6 Background, rerouting and arrival

  • NAV-6.1 Live notification: turn bitmap, distance and street; CATEGORY_NAVIGATION, low importance; a tap opens the map in navigation mode.
  • NAV-6.2 nativeSetAutoReroute(true) at init.
    • Show "Recalculando…" on OffRoute/RouteRebuilding.
    • Native rebuild errors are silent (no NEED_MORE_MAPS callback), so keep the 60 s recoverable timeout and offer the download when the position leaves the installed regions.
  • NAV-6.3 Arrival. After each fix, if nativeIsRouteFinished():
    • speak the last notification;
    • stop guidance and the service;
    • show an arrival card with the station's profile and price contribution.
  • NAV-6.4 Speed cameras. nativeSetSpeedCamManagerMode, plus a beep through MediaPlayerWrapper and our own raw asset (none ships in the AAR).

NAV-7 Offline and cross-region

  • NAV-7.1 Airplane-mode journey (MC-C8 leg): MT installed, airplane on, station → route → navigation → arrival.
  • NAV-7.2 Cross-region route with the generated packages, for example Sorriso (MT) → Goiânia (GO). The routers match border twins by OSM way and segment, so the generated cross_mwm sections must agree.
    • If routes fail or detour, regenerate the neighbouring regions from the same PBF date with cross-MWM transitions intact (and --world-roads if long leaps need it).
    • Add a per-region minimum-transitions check to generate-regional-map.sh.
  • NAV-7.3 Missing-map detection relies on SDK countries.txt/packed_polygons.bin ids matching the generated file names (Brazil_Mato Grosso). Lock this in with a test over all 23 regions.

NAV-8 Acceptance

  • Instrumented journeys with LocationHelper.startNavigationSimulation(nativeGetRouteJunctionPoints): preview → start → turn panel updates → off-route → reroute → arrival. Offline and online, light and dark.
  • Field drive in Sorriso with screen recording: follow camera, rotation, voice, a wrong turn and its reroute, arrival, screen off and on. Record timings in docs/backend/evidence/maps/NAV.md.
  • Gates:
    • Android: :domain:test :data:testDebugUnitTest :application:test :maps:engine:testDebugUnitTest :app:testDebugUnitTest :app:lintDebug :app:assembleRelease (APK ≤ 80 MiB).
    • Mobile guard: scripts/check-mobile.sh with the updated allowlist.

4. Order, size and risks

Order: NAV-0 → NAV-1 → NAV-2 → NAV-3 → NAV-4 → NAV-5 → NAV-6 → NAV-7 → NAV-8. NAV-5.1 can start in parallel at any time, because it is a likely defect today.

Rough size, one engineer with the device:

Phases Days
NAV-0 0.5
NAV-1 2
NAV-2 1
NAV-3 2
NAV-4 4
NAV-5 1
NAV-6 2
NAV-7 1–3 (depends on regeneration)
NAV-8 1.5
Total ≈ 15–17 working days

Risks:

  • Product contract. The NAV-0.1 location decision is blocking.
  • Native crashes without a surface. Covered by NAV-4.5.
  • Cross-region routing on generated packages. Covered by NAV-7.2.
  • TTS stall. Covered by NAV-5.1.
  • Guard updates. The check-mobile.sh location allowlist must change in the same commits as the location code.

Fuel prices wiki

Source: AlexandreZanata/brazil-fuel-prices @ 99f218303462287ad0d2122e065fa9d9759314ee

Clone this wiki locally