|
| 1 | +# CP-040 — Route and destination source |
| 2 | + |
| 3 | +Date: 2026-09-02 |
| 4 | + |
| 5 | +Decision owner: pending. This document states the finding and the recommendation; the |
| 6 | +network question at the end of it is the owner's, not this session's. |
| 7 | + |
| 8 | +Firmware examined: `saicadapterservice_overseas_eh32` (R69 EH32), decompiled sources under |
| 9 | +workspace `apks/`. |
| 10 | + |
| 11 | +## Verdict in one line |
| 12 | + |
| 13 | +The head unit **does** publish remaining distance and remaining time to the destination, to |
| 14 | +any process that registers as a listener on its general service — no Android permission, no |
| 15 | +network, no account. That closes the question CP-040 was written to answer. It does **not** |
| 16 | +close Phase 4, because it carries no elevation, no chargers and no alternative routes. |
| 17 | + |
| 18 | +## What was actually found |
| 19 | + |
| 20 | +`SaicNav` recorded that the map adapter answers no synchronous question about a trip. That |
| 21 | +is correct and remains correct. What it did not record is where the guidance goes instead. |
| 22 | + |
| 23 | +The navigation app calls into `com.saicmotor.adapterservice`: |
| 24 | + |
| 25 | +| Call | Transaction | Payload | |
| 26 | +| --- | --- | --- | |
| 27 | +| `remainingDistanceChange` | `IMapService` 39 | one int, logged as `remainingDistance` | |
| 28 | +| `remainingTimesChange` | `IMapService` 38 | one int, logged as `remainingMinutes` | |
| 29 | +| `guideStatusChange` | `IMapService` 29 | one int | |
| 30 | +| `guideInfosChange` | `IMapService` 30 | icon, distance to next manoeuvre, direction text | |
| 31 | + |
| 32 | +`MapService` stores each of these and forwards it to `GeneralService`, which fans it out over |
| 33 | +a `RemoteCallbackList` to every registered `IGeneralNotificationListener`: |
| 34 | + |
| 35 | +| Callback | Transaction | |
| 36 | +| --- | --- | |
| 37 | +| `guideStatusChange` | 1 | |
| 38 | +| `guideInfosChange` | 2 | |
| 39 | +| `roadInfoChange` | 3 | |
| 40 | +| `remainingTimesChange` | 12 | |
| 41 | +| `remainingDistanceChange` | 13 | |
| 42 | + |
| 43 | +Registration is `IGeneralService.registerNotificationListener`, transaction 1, on |
| 44 | +`com.saicmotor.adapterservice/.services.GeneralService`. |
| 45 | + |
| 46 | +Implemented as `EVHardware/lib/.../saic/SaicNavGuidance.kt`; the probe that captures it is |
| 47 | +`EVChargePilot/app/src/unstable/.../NavGuidanceProbeActivity.kt`. |
| 48 | + |
| 49 | +## Two traps found on the way, both avoided |
| 50 | + |
| 51 | +**`IMapNotificationListener` is the wrong direction.** It is tempting because |
| 52 | +`IMapService.registerNotificationListener` is transaction 1 and the name matches. But that |
| 53 | +interface carries `goHome`, `zoomIn`, `stopNav`, `setFastestRoute` and `startNavFromEVRout` — |
| 54 | +it is the channel the head unit uses to *command the navigation app*, not a feed of guidance. |
| 55 | +Registering on it would make this app impersonate a navigation provider, and every method on |
| 56 | +it is a write. It is out of bounds for a read-only app. `IGeneralNotificationListener` is the |
| 57 | +observer side and is what is used. |
| 58 | + |
| 59 | +**The callbacks are not `oneway`.** The generated proxy calls |
| 60 | +`transact(code, data, reply, 0)` and then `readException()`, and `GeneralService` fans out |
| 61 | +while holding the lock on its callback list. A listener that blocks therefore stalls every |
| 62 | +other consumer of guidance, the instrument cluster included. `SaicNavGuidance` does one |
| 63 | +parcel read and one volatile write per callback and nothing else — no I/O, no lock, no |
| 64 | +allocation beyond the immutable state object. Unrecognised transactions are answered without |
| 65 | +being parsed, so unrelated callbacks cost a reply and nothing more. |
| 66 | + |
| 67 | +Registration is additive: `RemoteCallbackList.register` appends and the list drops a dead |
| 68 | +listener on its own, so this displaces neither the navigation app nor the cluster. |
| 69 | + |
| 70 | +## What is still unproven |
| 71 | + |
| 72 | +- **The unit of `remainingDistance`.** The service logs the value without naming a unit and |
| 73 | + `distanceUnitChange` exists as a separate concern. Metres and kilometres are both plausible |
| 74 | + and the difference is a factor of a thousand. Nothing is converted anywhere in the code; |
| 75 | + the value is carried as `remainingDistanceRaw`. |
| 76 | +- **The meaning of `guideStatus` codes.** Undocumented; carried raw. |
| 77 | +- **Behaviour with no guidance running.** If the navigation app publishes nothing when idle, |
| 78 | + the feature only exists for drivers who navigate with the OEM app. This is the single most |
| 79 | + decision-relevant unknown and the probe is built to record it. |
| 80 | +- **Whether a third-party navigation app feeds the same path.** TomTom broadcasts |
| 81 | + (`com.tomtom.navigation.GUIDE_INFO`) reach `MapService`, so at least one non-SAIC app does. |
| 82 | + |
| 83 | +None of these can be settled off-vehicle, which is why CP-040 carries `on_vehicle_required`. |
| 84 | + |
| 85 | +## Scored comparison |
| 86 | + |
| 87 | +Scores are against CP-040's four criteria. "Boundary" means the read-only, offline, |
| 88 | +minimal-permission boundary stated in `PRODUCT.md`. |
| 89 | + |
| 90 | +| Source | Offline | Permission cost | Firmware stability | Boundary | Verdict | |
| 91 | +| --- | --- | --- | --- | --- | --- | |
| 92 | +| **OEM adapter guidance listener** | full | none | unproven; private interface, transaction codes are firmware-specific and must be evidence-gated per generation | intact — read-only, additive registration | **recommended for remaining distance and time** | |
| 93 | +| Manual destination entry + straight-line estimate | full | none | not firmware-dependent | intact | fallback only; a great-circle distance is wrong by 15–30 % on real roads and cannot see terrain | |
| 94 | +| Destination handed in by intent from another app | full | none | not firmware-dependent | intact | no such app exists today; keep as an interface, not a plan | |
| 95 | +| External routing engine (ORS, GraphHopper, Valhalla) | no | `INTERNET`, plus API-key handling | stable | **breaks it** — stable variant becomes networked | the only source that yields elevation and alternative routes | |
| 96 | +| ABRP `get_next_charge` | no | `INTERNET` | undocumented contract | breaks it, and adds an unagreed dependency | rejected, per `PLAN.md` | |
| 97 | +| Do not build Phase 4 | — | — | — | intact | still available, and still acceptable | |
| 98 | + |
| 99 | +## Recommendation |
| 100 | + |
| 101 | +**Take the adapter listener, and split Phase 4 in two.** |
| 102 | + |
| 103 | +1. **CP-041 arrival SOC becomes buildable** on remaining distance from the listener, once the |
| 104 | + probe returns a unit and an idle-behaviour answer from the vehicle. It stays offline, |
| 105 | + permission-free and inside the stated boundary. Its band must widen for the absent grade |
| 106 | + term, per the CP-031 decision. |
| 107 | + |
| 108 | +2. **Everything else in the driver's stated goal stays blocked on a network decision.** |
| 109 | + Elevation profile, charger locations and "via the departmental road instead" cannot come |
| 110 | + from the head unit. They need a routing engine, and that means `INTERNET` in the stable |
| 111 | + variant, a `PRODUCT.md` rewrite and a fresh CP-091 pass. That decision is not taken here. |
| 112 | + |
| 113 | +## Do-not-build option |
| 114 | + |
| 115 | +If the probe shows the adapter publishes nothing when the OEM navigation is idle, and the |
| 116 | +driver does not use the OEM navigation, then the listener is dead weight: delete |
| 117 | +`SaicNavGuidance` and the probe, mark CP-041 and CP-042 `blocked` permanently, and ship |
| 118 | +without Phase 4. That remains an acceptable outcome. |
| 119 | + |
| 120 | +## Security note |
| 121 | + |
| 122 | +No permission is added by this work; `mise run permissions` passes unchanged. The listener |
| 123 | +exists only in the unstable variant's reachable UI. Should CP-041 promote it to stable, the |
| 124 | +promotion needs its own CP-091 entry, because a stable build would then hold a registration |
| 125 | +on a head-unit service for the life of the process. |
| 126 | + |
| 127 | +## Next step |
| 128 | + |
| 129 | +Run the probe on the vehicle: park, arm, start a guidance in the car's own navigation, drive a |
| 130 | +leg whose distance is known independently, then park and copy the trace. The trace answers the |
| 131 | +unit question and the idle question together. |
0 commit comments