The accent is selectable in Settings → Display: Dynamic Red (default), Volcano Orange, Holborn Blue, Racing Green, Camden Grey, Monument Silver, Arctic White and Black Pearl. White, silver and black cannot be an accent on the black screen as they are, so they become light greys and a pearl blue-grey. The accent palette is now theme attributes (attrs_accent.xml) with one overlay per paint (accents.xml), applied before the activity inflates; layouts, colour lists, drawables and the views that draw in code use them, including the red tints that were hard-coded. A change recreates the activity on the same page. Android 9 crashes on gradients with theme colours, so the battery card is built from layers. Logs → "Look for the car colour" lists the head unit's system properties that mention colour, paint or car model, to find out whether the paint colour can be read from the car. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
125 lines
6.2 KiB
Markdown
125 lines
6.2 KiB
Markdown
# AGENTS.md — EVABRPUploader
|
|
|
|
Context for AI agents and new contributors working in this repository.
|
|
|
|
EVABRPUploader is part of **EVSuite**. The workspace `AGENTS.md` and normative
|
|
workspace `DESIGN.md` apply; this file contains only telemetry-specific additions.
|
|
|
|
## App-specific visual direction (2.2.6)
|
|
|
|
The user explicitly requested a permanent OLED-black theme, charcoal glass-like cards,
|
|
an accent the user picks from the MG4 paint colours (Dynamic Red #E0424C by default; the palette
|
|
is theme attributes in `res/values/attrs_accent.xml` and `accents.xml` — use `?attr/evAccent…`,
|
|
never a hard-coded accent hex). These app resources intentionally override the shared
|
|
DESIGN.md palette, DayNight preference and flat-surface rules. Do not overwrite them
|
|
with the shared token generator. Use static gradients rather than continuous effects;
|
|
keep large touch targets, readable text and null telemetry shown as a dash.
|
|
Wide layouts start at 1000dp; narrower windows use a bottom navigation bar.
|
|
|
|
## What this is
|
|
|
|
An Android Automotive app for the **MG4 (SAIC eh32)** that reads vehicle telemetry and
|
|
pushes it to A Better Route Planner. It runs on the car's head unit (AAOS 9, MT2712).
|
|
|
|
Fork of Leon Kernan's `ABRP_Uploader`. See `LICENSE.md` — the licence status is
|
|
**unresolved** and that is a real constraint, not paperwork.
|
|
|
|
## Non-negotiables
|
|
|
|
1. **Telemetry is read-only.** No vehicle settings may be written. Window automation
|
|
was removed in 2.2.5 at the user's request, including its service and door listener.
|
|
2. **A failed read is not a zero.** Getters return `Integer`/`Float`/`Boolean` and `null`
|
|
means "could not read". Null fields are omitted from the payload. Sending 0 for SOC
|
|
tells ABRP the battery is empty and wrecks a live route plan.
|
|
3. **Fail closed.** Update origin, APK signature, unknown vehicle state — refuse rather
|
|
than assume.
|
|
4. **Never let a credential reach a URL, a log line, or a crash report.**
|
|
5. **AAOS 9 must stay stable.** A third-party app crashing the head unit is unacceptable.
|
|
Every car call goes through reflection with try/catch; keep it that way.
|
|
6. **Power matters.** The head unit is awake whenever the car is on. No wake locks, no
|
|
alarms. The GPS subscription and the upload tick both cost power — keep them in step.
|
|
|
|
## Architecture
|
|
|
|
```
|
|
AbrpUploadService foreground service; scheduler thread does all IPC and HTTP
|
|
CarPropertyAdapter reflection bridge to android.car (not on the compile classpath)
|
|
TelemetryPayload pure JSON building — no Android types, unit-tested
|
|
UploadCadence when a tick actually uploads — pure, unit-tested
|
|
UploadSettings user cadence configuration
|
|
UploadLog last 20 attempts + derived service state — pure, unit-tested
|
|
SecurePrefs credentials in EncryptedSharedPreferences, migrates plaintext
|
|
AbrpApi HTTP; credentials in the POST body, never the query string
|
|
MainActivity configuration, service state, upload log
|
|
```
|
|
|
|
**Put logic in the pure classes.** Anything Android-free can be unit-tested on the JVM;
|
|
anything inside the service effectively cannot be tested without a car.
|
|
|
|
## Source sets
|
|
|
|
| Set | Contents | Ships in |
|
|
|---|---|---|
|
|
| `main` | everything shared | all builds |
|
|
| `stable` | `UpdateHook` no-op | stable channel |
|
|
| `unstable` | active GitHub pre-release updater and PackageInstaller result receiver | unstable channel |
|
|
| `debug` | `VhalProbe` — enumerates the whole VHAL | debug builds only |
|
|
| `testUnstable` | OTA policy tests | test only |
|
|
|
|
The stable channel does not merely disable self-update — the updater is **not in the
|
|
APK**.
|
|
|
|
## Firmware generations
|
|
|
|
MG4 infotainment ships as SWI68 / SWI69 / SWI131 / SWI132 / SWI133 / SWI165, and they
|
|
differ. Property IDs confirmed on one generation are not universal. Anything vehicle-facing
|
|
must tolerate a property simply not existing.
|
|
|
|
**Two read paths, split by whether the signal is firmware-agnostic:**
|
|
|
|
| Signal | Read path | Firmwares |
|
|
|---|---|---|
|
|
| Speed, outside temp, park | `EVHardware` getters (`getVehicleSpeedKmh`, `getOutsideTempCelsius`, `isVehicleInPark`) | all six — EVHardware branches per generation internally |
|
|
| SOC, range | `CarPropertyAdapter` vendor IDs (`0x2160F404`, `0x2140F41C`) | **SWI68 only** |
|
|
| Charge rate/port, cabin temp | `CarPropertyAdapter` standard-AAOS IDs | wherever the VHAL implements them |
|
|
|
|
`EVHardware.init()` is called in `AbrpUploadService.onCreate()` (idempotent); it detects
|
|
the generation and picks the right underlying API. Fields it covers therefore work on every
|
|
supported firmware.
|
|
|
|
**Reference firmware: `SWI68-29958-1300R69`.** The vendor EV IDs still in
|
|
`CarPropertyAdapter` are supported only on SWI68 firmware. EVHardware
|
|
has **no EV-battery abstraction for any generation**, so SOC and range cannot go through it;
|
|
until those vendor IDs are confirmed on the other generations they stay SWI68-only, and on
|
|
another firmware they may silently return nothing (handled safely — the field is omitted, but
|
|
SOC/range would be missing). Re-confirm with `VhalProbe` before trusting the vendor IDs on any
|
|
other generation.
|
|
|
|
## Working here
|
|
|
|
```bash
|
|
mise run check # what CI runs: permission gate + lint + unit tests
|
|
mise run build
|
|
mise run logs # adb logcat, filtered to this app
|
|
```
|
|
|
|
Gradle needs JDK 17. If a build fails in `JdkImageTransform` complaining about `jlink`,
|
|
a stale Gradle daemon started by VS Code's Java extension is being reused —
|
|
`pkill -f GradleDaemon` and retry.
|
|
|
|
## Things that will bite you
|
|
|
|
- **`android.car` is not on the compile classpath.** Everything car-related is reflection.
|
|
R8 cannot see those uses, so `proguard-rules.pro` keeps them by name.
|
|
- **Unit tests get a stub `org.json`** from `android.jar` that returns null. The real
|
|
implementation is added as a test dependency; do not remove it.
|
|
- **`service_running` in preferences goes stale-true** after a force-kill. Use
|
|
`AbrpUploadService.isRunning()`.
|
|
- **Permission changes fail CI** until allowlisted with a justification.
|
|
|
|
## Honesty requirements
|
|
|
|
Most of this cannot be verified off a vehicle. When reporting work, state plainly what was
|
|
tested and what was not. "Unit tests pass, not tried on the car" is the expected and
|
|
acceptable answer. Do not imply road-testing that did not happen.
|