Skip to content

Feature/tasker bridge - #66

Closed
malys wants to merge 13 commits into
SliDeeN:mainfrom
malys:feature/tasker-bridge
Closed

Feature/tasker bridge#66
malys wants to merge 13 commits into
SliDeeN:mainfrom
malys:feature/tasker-bridge

Conversation

@malys

@malys malys commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Extract a shared MG4Hardware layer + add a profile bridge for a companion app

What this is

Building on the audit branch, this extracts MG4Control's vehicle-access layer into a
standalone, reusable library (MG4Hardware) and adds
a narrow, signature-protected external control API (ProfileControlService /
IProfileControl) so any app signed with the platform key can list and apply MG4Control
profiles over IPC. It also modernises the toolchain and hardens a few manifest items.

It is a sizeable, structural change. None of it is vehicle-tested — I do not have a car
to validate on — so please treat the on-vehicle checklist below as required before merging.

The commits

Commit What
refactor: consume shared MG4Hardware library via git submodule Move MG4Hardware, VehicleWriteGate, FirmwareInfo, AppLogger, and the driving models out of the app into the shared library, vendored as a submodule at ./MG4Hardware and consumed as the :mg4hardware Gradle subproject. VehicleWriteGate no longer references the app's R (message provider). Every gated setter now carries a @RequiresStandstill marker, asserted by a test.
feat: ProfileControlService — narrow external control API A signature-permission-protected service exposing only listProfiles() + applyProfile(id). No raw property write, no snapshot read. Caller-agnostic: any app signed with the platform key can bind. VEHICLE_POWER_OFF stays out of reach.
build: migrate to AGP 9.1.1 / Gradle 9.3.1 / built-in Kotlin compileSdk 34 → 36, drop the standalone Kotlin plugin, replace the removed applicationVariants output API (release naming moved to CI).
security: harden manifest + scope SAST to source allowBackup=false, taskAffinity="" on the launcher (StrandHogg); SAST scans app/src only, not build/.
chore: remove the dead MG4Tasker ignition broadcast + refactor: trim the Tasker bridge to profile-only The companion app became independent (it reads/writes the vehicle itself), so the ignition broadcast and the bridge's snapshot/action methods were removed.
refactor: generalize the Tasker bridge into a caller-agnostic profile API Rename the now profile-only surface so its name no longer implies a single caller: ITaskerBridgeIProfileControl, TaskerBridgeServiceProfileControlService, permission …TASKER_BRIDGE…CONTROL_PROFILES (package com.mg4.control.api). Behaviour identical.
chore: bump MG4Hardware … (several) Submodule pointer bumps: English translation of the library, a WINDOW_POS property-id fix confirmed against the R69 sources (0x13340BC00x13400BC0), minSdk alignment, AAR publishing.

Full diff: 56 files, ~4.6k insertions / ~4.0k deletions (most of the deletion count is
files moving into the submodule).

Why extract the hardware layer

MG4Control, the ABRP telemetry app, and the new tasker app all need the same reflection
layer, the same 0 km/h gate, and the same per-generation routing. Keeping three copies
guarantees drift. The library is the single source of truth; the safety gate and the
per-firmware dispatch live in exactly one place.

What is unchanged

MG4Control's behaviour is intended to be identical — this is a structural move, not a
feature change. The profile logic, the hardkey handling, the OTA path, and the flavors are
untouched.

⚠️ Please verify on a vehicle before merging

The changes that cannot be validated off the car:

  • The AGP 9 / R8 bump. A new major R8 shrinks/obfuscates the reflection-heavy code;
    the keep rules moved into the library's consumer-rules.pro. Needs a full on-vehicle
    release check (Katman1/2/3, HVAC, ADAS/AEB/ELK, ignition, OTA).
  • The submodule extraction. Reflection targets and the system_uid path must still
    resolve identically after the package move (com.mg4.control.hardware
    com.mg4.hardware).
  • The WINDOW_POS id fix and the climate reads — derived from the R69 sources, not
    confirmed live on any generation.

Notes for you as the owner

  • This introduces a git-submodule dependency on malys/MG4Hardware. If you would
    rather not take that on upstream, I am happy to instead vendor the library sources
    directly, or keep this as a fork-only change — your call.
  • The tasker app and the ABRP app are separate repos; nothing here requires them.
  • Commit authorship and the co-author trailer are on each commit.

Happy to split this into smaller PRs (library extraction / AGP bump / bridge) if that is
easier to review.

malys and others added 13 commits July 22, 2026 14:40
emulator-setup / emulator-car / emulator-screen / emulator-stop / run.

No single emulator matches the MG4: the car runs AAOS 9 (API 28) and Google
publishes no Automotive system image below API 33 (checked with
sdkmanager --list). So two AVDs, each faithful on one axis -- emulator-car is
API 33 Automotive (has CarPropertyManager, wrong OS), emulator-screen is API 28
at the MG4's 1920x1080 @ 160dpi panel (right OS and screen, no car service).
Neither exposes the SAIC vendor properties, so those reads fail on both, as
they should.

`run` builds, installs with -g to pre-grant runtime permissions, and launches
the activity on whatever device is attached -- emulator or the car over ADB.

setup aborts if /dev/kvm is missing and warns when the user is not in the kvm
group, rather than handing over an unusably slow emulator.

This is a local dev convenience only; nothing here runs in CI or changes the
build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Exposes a 4-method AIDL bridge (ITaskerBridge) that lets a separately
signed companion app (MG4Tasker) read a vehicle snapshot and request
named actions, without granting it any vehicle access of its own.

Design:
- Closed action catalogue, no raw property-write method. A caller can only
  request what the user could already do from MG4Control's UI. VEHICLE_POWER_OFF
  is intentionally excluded.
- Every write still runs in this process, so VehicleWriteGate (0 km/h) applies
  in one place and the per-firmware routing is not duplicated.
- Guarded by a new signature-level permission (com.mg4.control.permission.
  TASKER_BRIDGE); the exported service and the ignition broadcast both require it.
- Ignition broadcast to MG4Tasker is delayed ~8s so the default profile finishes
  applying first, avoiding interleaved write sequences.

Also adds read-only outside-temperature and climate/window reads
(HVAC AC/AUTO/recirc/fan/temperature, window position). These use standard
AOSP property ids and are UNVERIFIED on MG4 firmware — all return null when
unreadable, for the MG4Tasker diagnostic screen to check exposure before any
write path is added. No write counterparts.

Permission added to the allowlist; ProGuard keeps the AIDL stub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the vehicle-access layer (MG4Hardware, VehicleWriteGate, FirmwareInfo,
AppLogger, driving models) out of the app and into the shared MG4Hardware
library, vendored as a git submodule at ./MG4Hardware and consumed as the
:mg4hardware Gradle subproject. One implementation across MG4Control,
MG4Tasker and ABRP_Uploader, no drift.

- VehicleWriteGate no longer references the app's R: it takes a message
  provider, which MG4ControlService sets to MG4Control's localized strings
  (the module falls back to English).
- Every gated setter now carries @RequiresStandstill (in the library).
- Imports repointed to com.mg4.hardware.*; ProGuard keep rules and the
  ProguardRules test updated to the new package.
- CI checks out submodules recursively.

Behaviour is unchanged; this is a structural move. Needs the usual
on-vehicle release check (R8 + reflection + system UID) before shipping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AGP 8.5.2 → 9.1.1, Gradle 9.0 → 9.3.1, compileSdk 34 → 36.
- Drop the standalone Kotlin plugin (AGP 9 built-in); jvmTarget via
  kotlin { compilerOptions }.
- Replace the removed applicationVariants output API; the release workflow
  now renames app-<flavor>-release.apk to MG4Control-<flavor>-<version>.apk.
- Remove deprecated android.enableJetifier.
- Bump MG4Hardware submodule to its AGP 9 build.

Builds + unit tests green. MUST pass the on-vehicle release check (R8 +
reflection + system UID) before shipping — the AGP/R8 major bump changes
shrinking/obfuscation and cannot be validated off the car.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- allowBackup=false / fullBackupContent=false and taskAffinity="" on the
  launcher activity (StrandHogg / android_task_hijacking2, allow-backup).
- SAST (mobsfscan/semgrep) now scans source only, not build/ artifacts or
  the vendored submodule — removes stale build-manifest and duplicate
  submodule findings from code scanning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MG4Tasker is now independent and listens for ignition itself, so
MG4Control's ignition notification broadcast had no receiver. Remove
notifyTaskerOnIgnition(), the ACTION_IGNITION_ON constant, the TASKER_*
constants, and MG4Control's own use of the TASKER_BRIDGE permission (it
still declares the permission to protect the profile bridge service).

Build + tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MG4Tasker is now independent and reads/writes the vehicle itself, so the
bridge's readSnapshot() and applyAction() had no caller. Reduce the AIDL
contract and the service to what MG4Tasker actually uses — listProfiles()
and applyProfile() — and correct the now-false docstring (it claimed
MG4Tasker never touches the vehicle and needs no privileges).

Removes the snapshot builder, the action dispatch, the snapshot keys and
the PARAM_VALUE constant. Build + tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… API

The IPC surface was named for one caller (MG4Tasker), but it is just
MG4Control's external control API — list and apply driving profiles, nothing
else. Rename it so any app signed with the platform key can use it without the
naming implying a single client:

- com.mg4.control.tasker.ITaskerBridge      -> com.mg4.control.api.IProfileControl
- TaskerBridgeService                       -> ProfileControlService
- permission …TASKER_BRIDGE                 -> …CONTROL_PROFILES

No behaviour change: same two methods, same signature-level protection, same
0 km/h gate verdicts. Manifest, proguard keep rules, permission allowlist and
the verdict test move with it; French bridge comments translated to English.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The app declares sharedUserId="android.uid.system" for vehicle access, which
requires the platform signature. A stock emulator is signed with Google's own
platform key (not the public AOSP test key — verified: signing with the AOSP
key still fails INSTALL_FAILED_SHARED_USER_INCOMPATIBLE on both the API 33
automotive and API 28 images), so neither a platform-signed nor a debug-key APK
with sharedUserId can install.

A debug-only manifest overlay (app/src/debug/AndroidManifest.xml) removes
sharedUserId with tools:remove, so debug builds install with the ordinary debug
key on any emulator. Release builds keep sharedUserId untouched — the src/main
manifest is unchanged and still declares it for the car.

This costs nothing on the emulator: the SAIC vehicle services are absent there,
so the vehicle calls fail regardless; the UI, layouts and navigation render,
which is the point of an emulator preview. The mise `run` task documents the
arrangement.

Verified by installing and launching the sibling MG4Tasker debug APK (same
sharedUserId, same overlay) on an API 33 automotive emulator: installs,
launches, renders at 1920x1080, no crash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@malys malys closed this Jul 31, 2026
@malys
malys deleted the feature/tasker-bridge branch July 31, 2026 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant