Feature/tasker bridge - #66
Closed
malys wants to merge 13 commits into
Closed
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 MG4Controlprofiles 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
refactor: consume shared MG4Hardware library via git submoduleMG4Hardware,VehicleWriteGate,FirmwareInfo,AppLogger, and the driving models out of the app into the shared library, vendored as a submodule at./MG4Hardwareand consumed as the:mg4hardwareGradle subproject.VehicleWriteGateno longer references the app'sR(message provider). Every gated setter now carries a@RequiresStandstillmarker, asserted by a test.feat: ProfileControlService — narrow external control APIlistProfiles()+applyProfile(id). No raw property write, no snapshot read. Caller-agnostic: any app signed with the platform key can bind.VEHICLE_POWER_OFFstays out of reach.build: migrate to AGP 9.1.1 / Gradle 9.3.1 / built-in KotlinapplicationVariantsoutput API (release naming moved to CI).security: harden manifest + scope SAST to sourceallowBackup=false,taskAffinity=""on the launcher (StrandHogg); SAST scansapp/srconly, notbuild/.chore: remove the dead MG4Tasker ignition broadcast+refactor: trim the Tasker bridge to profile-onlyrefactor: generalize the Tasker bridge into a caller-agnostic profile APIITaskerBridge→IProfileControl,TaskerBridgeService→ProfileControlService, permission…TASKER_BRIDGE→…CONTROL_PROFILES(packagecom.mg4.control.api). Behaviour identical.chore: bump MG4Hardware …(several)WINDOW_POSproperty-id fix confirmed against the R69 sources (0x13340BC0→0x13400BC0), 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.
The changes that cannot be validated off the car:
the keep rules moved into the library's
consumer-rules.pro. Needs a full on-vehiclerelease check (Katman1/2/3, HVAC, ADAS/AEB/ELK, ignition, OTA).
system_uidpath must stillresolve identically after the package move (
com.mg4.control.hardware→com.mg4.hardware).WINDOW_POSid fix and the climate reads — derived from the R69 sources, notconfirmed live on any generation.
Notes for you as the owner
malys/MG4Hardware. If you wouldrather 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.
Happy to split this into smaller PRs (library extraction / AGP bump / bridge) if that is
easier to review.