The head unit has no map engine (paper #1). What it does have is the car — every signal the vehicle bus carries, sitting behind two interfaces an ordinary sideloaded app can reach.
Question#
Which live vehicle signals can an app read on an export Sealion 7 without being platform-signed, and through what interface?
Environment#
Decompiled read-only with jadx 1.5.5 from CarAdapterService.apk (com.ts.appservice.caradapter) and from the 277-class android.hardware.bydauto.* SDK bundled inside BydSpotifyMusic.apk. Verified by reading the signals continuously on the car from a sideloaded, debug-signed app over several months of driving.
Observation#
There are two paths to the same data, and they answer differently.
The ThunderSoft car adapter. com.ts.appservice.caradapter exports a bindable service, CarAdapterService, answering the action com.ts.lib.caradapter.ICarAdapterService. Its root binder has one method that matters — getCarAdapterService(String domain) — and it hands back a second binder per domain:
"general" -> com.ts.lib.caradapter.general.ICarGeneralService
"body" -> com.ts.lib.caradapter.body.ICarBodyService
"cloud" -> com.ts.lib.caradapter.cloud.ICarCloudService
"sensor" -> com.ts.lib.caradapter.sensor.ICarSensorService
"hvac" -> com.ts.lib.caradapter.hvac.IHvacAdapterService
"setting" -> com.ts.lib.caradapter.setting.ISettingService
"cabin" -> com.ts.lib.caradapter.cabin.ICabinService
"assist" -> com.ts.lib.caradapter.assist.ICarAssistAdapterService
"selfstudy" -> com.ts.lib.caradapter.selfstudy.ISelfStudyService
"drivemode" -> com.ts.lib.caradapter.drivemode.IDriveModeServiceEach domain binder carries named getters — getSocValue(), getTyrePressureValueByTypeLf(), getMotorMCUGeneratrixVolt(2) — which is a far more pleasant surface than raw property ids. The AIDL stubs live in the platform’s ts-framework.jar, already on the system classpath of any app on this unit.
Android Automotive’s own property reader. android.car.Car.createCar(context) then getCarManager(Car.PROPERTY_SERVICE) gives the standard AAOS CarPropertyManager, and the signals arrive as android.car.hardware.CarPropertyValue on vendor VHAL properties. Every id starts 0x21…, and the leading 0x2 is AOSP’s VehiclePropertyGroup.VENDOR (0x20000000) — these are BYD’s own properties, not the standard catalogue.
The two views agree. ID_CURRENT_SPEEDR in the BYD SDK’s HAL-id map is decimal 557 867 014, which is 0x21406006; ID_GEAR_R is 557 857 290, which is 0x21403a0a. The named getter and the raw property are the same signal reached two ways.
Evidence#
| Property | Signal | Unit | Reached by |
|---|---|---|---|
0x21604402 |
State of charge | % | setting.getSocValue |
0x21604421 |
Remaining battery energy | kWh | general.getEvRemainingBatteryPower |
0x21404401 |
Remaining range | km | general.getElecDrivingRangeValue |
0x21402037 |
State of health | % | cabin.getBatteryHealthStatus |
0x21603408 |
Charging power | kW | body.getChargingPower |
0x21406006 |
Speed | km/h | sensor.getCurrentSpeed |
0x21403a0a |
Gear | — | cabin.getGear |
0x21403974 |
Driving mode | — | cabin.getSportModeState |
0x21604409 |
Odometer | km | general.getTotalMileageValue |
0x21604405 |
Average consumption | kWh/100 km | general.getTotalElecConPHMValue |
0x21601421 |
Last 50 km consumption | kWh/100 km | body.getTripLast50kmElectricConAvgValue |
0x21407407 |
HV bus voltage | V | cloud.getMotorMCUGeneratrixVolt(2) |
0x21407409 |
HV bus current | A | cloud.getMotorMCUGeneratrixCurrent(2) |
0x21407403 |
Rear motor torque | Nm | cloud.getDriverMotorTorque(2) |
0x21407405 |
Rear motor temperature | °C | cloud.getDriverMotorTemperature(2) |
0x2140460c |
12 V battery voltage | V | raw property 0x2140461e |
0x2140460a |
Exterior temperature | °C | hvac.getTempratureOut |
0x21401023 |
Cabin setpoint | °C | hvac.getTempratureMain |
0x2160801d |
Tyre pressure, front left | kPa | sensor.getTyrePressureValueByTypeLf |
0x2160801e |
Tyre pressure, front right | kPa | sensor.getTyrePressureValueByTypeRf |
0x2160801f |
Tyre pressure, rear left | kPa | sensor.getTyrePressureValueByTypeLb |
0x21608020 |
Tyre pressure, rear right | kPa | sensor.getTyrePressureValueByTypeRb |
Four more appear in the SDK’s id map and are named here because they answer questions the table does not: 0x21604601 (ID_SPEED_VALUE_R) is the speed the adapter itself reads, 0x21600d04 is a rolling alive-counter for it, 0x21600d03 an IPB validity flag, and 0x21404b03 (ID_SLOPE) is road grade — pitch, from the BYD “sensor” device.
Interpretation#
Two things follow from the shape of this.
First, BYD’s signals are vendor properties, not the AAOS standard set. An app written against VehiclePropertyIds.PERF_VEHICLE_SPEED finds nothing here. Every useful value is in the 0x21…… vendor group, and the map from id to meaning exists only inside BYD’s own SDK.
Second, the named-getter path is the one to use. The domain binders are stable, self-describing and typed; the raw property path is a fallback for the handful of values with no getter — the 12 V battery among them. An app that binds CarAdapterService once and keeps the ten domain proxies is doing what the vendor’s own apps do.
The permissions are the surprise. Alongside the AAOS android.car.permission.* set, the platform declares a parallel android.permission.BYDAUTO_*_GET family — speed, gearbox, charging, power, sensor, tyre, engine, ADAS, location, bodywork, air conditioning, door lock, light, radar, PM2.5, instrument, version, OTA. Some of these are declared at protection level dangerous rather than signature, which means a normal app is granted them by a runtime prompt, the same way it is granted the camera. BYD’s vehicle data is not uniformly behind a platform signature.
Result#
Confirmed: twenty-two live vehicle signals are readable from an ordinary sideloaded app on this unit, through com.ts.appservice.caradapter’s domain binders, with CarPropertyManager on the vendor property group as a second path to the same values. No platform signature is needed.
What is not in the catalogue matters as much as what is. There is no heading, no yaw rate and no steering angle anywhere in the SDK — that absence is paper #3.
Limitations#
One car, one market, one head-unit build. The list is what returns live values on this unit, which is not the same as the complete set of properties the VHAL implements — a domain binder may carry getters nobody has called. Nothing here was written; every reading is a read.
Signals were sanity-checked against the car’s own displays (SOC, range, odometer, tyre pressures) rather than against a reference instrument, so scaling and units are BYD’s, taken at face value. The four ids named from the SDK map but not in the table were read from the id catalogue, not polled.
We have not tested which of the BYDAUTO permissions are actually enforced on a read. The app declares the whole family, so a signal that works may be working without needing the permission it declares — and the protection levels are read from the platform’s own permission list, not from a grant that was observed to be refused.
Reproduction#
adb shell dumpsys package com.ts.appservice.caradapter
adb shell pm list permissions -g -d | grep -i bydautoThe first shows the exported service and the action it answers; the second lists the BYDAUTO permission family with its protection levels, which is where the dangerous entries show up.
From an app on the unit, bind the service and take the domain binders:
Intent i = new Intent("com.ts.lib.caradapter.ICarAdapterService");
i.setComponent(new ComponentName("com.ts.appservice.caradapter",
"com.ts.appservice.caradapter.CarAdapterService"));
bindService(i, connection, Context.BIND_AUTO_CREATE);
// then, on the root ICarAdapterService:
IBinder sensor = root.getCarAdapterService("sensor");The ts-framework.jar stubs are on the system classpath of this unit, so the AIDL interfaces resolve at runtime without bundling anything. Off a BYD head unit they do not exist, and a build that assumes them will not start — reach them reflectively if the same APK has to run anywhere else.