@@ -22,10 +22,13 @@ import kotlinx.coroutines.launch
/ * *
/ * *
* Hardware abstraction layer for MG4 vehicle control .
* Hardware abstraction layer for MG4 vehicle control .
*
*
* Runtime communication layers ( " Katman " , Turkish for layer , as the names in this file call them ) :
* Runtime communication layers , numbered as the names in this file use them ( Layer1 … Layer5 ) :
* Katman1 — android . car . Car → CarPropertyManager / CarHvacManager ( async ServiceConnection )
* Layer1 — android . car . Car → CarPropertyManager / CarHvacManager ( async ServiceConnection )
* Katman2 — ServiceManager . getService ( " vehiclesetting " ) → raw IBinder ( often SELinux - blocked )
* Layer2 — ServiceManager . getService ( " vehiclesetting " ) → raw IBinder ( often SELinux - blocked )
* Katman3 — bindService ( VehicleService ) → IHubService → sub - services ( not needed for our use case )
* Layer3 — bindService ( VehicleService ) → IHubService → sub - services ( not needed for our use case )
* Layer4 — SAIC ' s settings SDK in the launcher ' s context : VehiclePropertyManager ( SWI133 ) ,
* VehicleSettingManager ( SWI68 / SWI165 ) , CarVehicleSettingClient via CarAdapterClient ( A9 )
* Layer5 — ignition state : VehicleConditionManager , or ICarGeneralService via CarAdapterClient ( A9 )
* /
* /
object EVHardware {
object EVHardware {
@@ -146,11 +149,11 @@ object EVHardware {
private const val VSM _SERVICE _CODE = 0x8 // queryClient(0x8) → ICarVehicleSettingService
private const val VSM _SERVICE _CODE = 0x8 // queryClient(0x8) → ICarVehicleSettingService
private const val VEHICLE _SETTING _PKG = " com.saicmotor.vehiclesetting " // SWI131: carapi inside VS
private const val VEHICLE _SETTING _PKG = " com.saicmotor.vehiclesetting " // SWI131: carapi inside VS
// Katman 5 — VehicleConditionManager (IVehicleConditionService via IHubService "vehiclecondition")
// Layer 5 — VehicleConditionManager (IVehicleConditionService via IHubService "vehiclecondition")
private const val VCM _CLASS = " com.saicmotor.sdk.vehiclesettings.manager.VehicleConditionManager "
private const val VCM _CLASS = " com.saicmotor.sdk.vehiclesettings.manager.VehicleConditionManager "
private const val VCM _LISTENER _CLASS = " com.saicmotor.sdk.vehiclesettings.IVehicleConditionListener "
private const val VCM _LISTENER _CLASS = " com.saicmotor.sdk.vehiclesettings.IVehicleConditionListener "
// Katman 5 SWI69/SWI131 — ICarGeneralService via CarAdapterClient (queryClient(0x1))
// Layer 5 SWI69/SWI131 — ICarGeneralService via CarAdapterClient (queryClient(0x1))
private const val CAR _GENERAL _CLIENT _CLASS = " com.saicmotor.carapi.client.CarGeneralClient "
private const val CAR _GENERAL _CLIENT _CLASS = " com.saicmotor.carapi.client.CarGeneralClient "
private const val BIND _CODE _CAR _GENERAL = 0x1 // ICarAdapterService.queryClient(0x1)
private const val BIND _CODE _CAR _GENERAL = 0x1 // ICarAdapterService.queryClient(0x1)
@@ -177,7 +180,7 @@ object EVHardware {
}
}
/ * *
/ * *
* Values returned by IVehicleConditionService . getVehicleIgnition ( ) ( Katman5 ) .
* Values returned by IVehicleConditionService . getVehicleIgnition ( ) ( Layer5 ) .
* Values exposed by the supported SWI133 runtime interface .
* Values exposed by the supported SWI133 runtime interface .
* /
* /
object CarIgnitionItem {
object CarIgnitionItem {
@@ -232,9 +235,9 @@ object EVHardware {
/** Set once the getters above have been looked up, present or absent. */
/** Set once the getters above have been looked up, present or absent. */
@Volatile private var sCpmMethodsResolved = false
@Volatile private var sCpmMethodsResolved = false
@Volatile private var sVehicleBinder : IBinder ? = null
@Volatile private var sVehicleBinder : IBinder ? = null
@Volatile private var sVpm : Any ? = null // VehiclePropertyManager instance (SWI133, Katman 4)
@Volatile private var sVpm : Any ? = null // VehiclePropertyManager instance (SWI133, Layer 4)
@Volatile private var sVpmService : Any ? = null // mIVehiclePropertyService field value (SWI133)
@Volatile private var sVpmService : Any ? = null // mIVehiclePropertyService field value (SWI133)
@Volatile private var sVsm : Any ? = null // VehicleSettingManager instance (SWI68, Katman 4)
@Volatile private var sVsm : Any ? = null // VehicleSettingManager instance (SWI68, Layer 4)
// The A9 adapter, kept after the VSM client is built: it is the door to every other
// The A9 adapter, kept after the VSM client is built: it is the door to every other
// carapi client too — the climate one among them — and nothing else can open it.
// carapi client too — the climate one among them — and nothing else can open it.
@Volatile private var sCarAdapter : Any ? = null
@Volatile private var sCarAdapter : Any ? = null
@@ -253,49 +256,49 @@ object EVHardware {
@Volatile private var sDriveModeListener : DriveModeListener ? = null
@Volatile private var sDriveModeListener : DriveModeListener ? = null
@Volatile private var sHvacListener : HvacListener ? = null
@Volatile private var sHvacListener : HvacListener ? = null
// ── Katman 5 — IGNITION_STATE via CarPropertyManager (standard AAOS) ──────
// ── Layer 5 — IGNITION_STATE via CarPropertyManager (standard AAOS) ──────
@Volatile private var sIgnitionCallbackProxy : Any ? = null
@Volatile private var sIgnitionCallbackProxy : Any ? = null
@Volatile private var sIgnitionCallbackRegistered = false
@Volatile private var sIgnitionCallbackRegistered = false
private val ignitionCallbacks = java . util . concurrent . CopyOnWriteArrayList < ( Int ) -> Unit > ( )
private val ignitionCallbacks = java . util . concurrent . CopyOnWriteArrayList < ( Int ) -> Unit > ( )
// ── Katman 5 — VehicleConditionManager / ICarGeneralService ───────────────
// ── Layer 5 — VehicleConditionManager / ICarGeneralService ───────────────
@Volatile private var sVcm : Any ? = null
@Volatile private var sVcm : Any ? = null
@Volatile private var sVcmListener : Any ? = null
@Volatile private var sVcmListener : Any ? = null
@Volatile private var sVcmCallbackRegistered = false
@Volatile private var sVcmCallbackRegistered = false
@Volatile private var sLastVcmIgnitionState = - 1 // filters repeated false RUN events
@Volatile private var sLastVcmIgnitionState = - 1 // filters repeated false RUN events
private val vehicleConditionCallbacks = java . util . concurrent . CopyOnWriteArrayList < ( Int ) -> Unit > ( )
private val vehicleConditionCallbacks = java . util . concurrent . CopyOnWriteArrayList < ( Int ) -> Unit > ( )
private val katman5ReadyListeners = java . util . concurrent . CopyOnWriteArrayList < ( ) -> Unit > ( )
private val layer5ReadyListeners = java . util . concurrent . CopyOnWriteArrayList < ( ) -> Unit > ( )
/** Listeners notified as soon as Katman 1 (CPM + HVAC) is operational. */
/** Listeners notified as soon as Layer 1 (CPM + HVAC) is operational. */
private val katman1ReadyListeners = java . util . concurrent . CopyOnWriteArrayList < ( ) -> Unit > ( )
private val layer1ReadyListeners = java . util . concurrent . CopyOnWriteArrayList < ( ) -> Unit > ( )
/** Listeners notified as soon as Katman 4 (mIVehiclePropertyService) is operational. */
/** Listeners notified as soon as Layer 4 (mIVehiclePropertyService) is operational. */
private val katman4ReadyListeners = java . util . concurrent . CopyOnWriteArrayList < ( ) -> Unit > ( )
private val layer4ReadyListeners = java . util . concurrent . CopyOnWriteArrayList < ( ) -> Unit > ( )
/ * *
/ * *
* Runs [ action ] as soon as CarPropertyManager and CarHvacManager are available .
* Runs [ action ] as soon as CarPropertyManager and CarHvacManager are available .
* If already ready , runs immediately . Otherwise queued and triggered on connection .
* If already ready , runs immediately . Otherwise queued and triggered on connection .
* /
* /
fun whenKatman1Ready ( action : ( ) -> Unit ) {
fun whenLayer1Ready ( action : ( ) -> Unit ) {
if ( sCarPropertyManager != null && sCarHvacManager != null ) {
if ( sCarPropertyManager != null && sCarHvacManager != null ) {
action ( )
action ( )
} else {
} else {
katman1ReadyListeners . add ( action )
layer1ReadyListeners . add ( action )
}
}
}
}
/ * *
/ * *
* Runs [ action ] as soon as the ADAS service ( Katman4 ) is available .
* Runs [ action ] as soon as the ADAS service ( Layer4 ) is available .
* SWI133 → mIVehiclePropertyService ; SWI68 / SWI69 / SWI131 → mVehicleSettingService
* SWI133 → mIVehiclePropertyService ; SWI68 / SWI69 / SWI131 → mVehicleSettingService
* /
* /
fun whenKatman4Ready ( action : ( ) -> Unit ) {
fun whenLayer4Ready ( action : ( ) -> Unit ) {
val ready = if ( FirmwareInfo . isVsmBased ( ) ) sVsmService != null else sVpmService != null
val ready = if ( FirmwareInfo . isVsmBased ( ) ) sVsmService != null else sVpmService != null
if ( ready ) action ( ) else katman4ReadyListeners . add ( action )
if ( ready ) action ( ) else layer4ReadyListeners . add ( action )
}
}
/** Runs [action] as soon as Katman 5 (IVehicleConditionService) is operational. */
/** Runs [action] as soon as Layer 5 (IVehicleConditionService) is operational. */
fun whenKatman5Ready ( action : ( ) -> Unit ) {
fun whenLayer5Ready ( action : ( ) -> Unit ) {
if ( sVcmCallbackRegistered ) action ( ) else katman5ReadyListeners . add ( action )
if ( sVcmCallbackRegistered ) action ( ) else layer5ReadyListeners . add ( action )
}
}
/** Registers a callback invoked on every ignition state change (CarIgnitionItem). */
/** Registers a callback invoked on every ignition state change (CarIgnitionItem). */
@@ -338,12 +341,12 @@ object EVHardware {
* Current ignition state , as [ IgnitionState ] ( AAOS ) values .
* Current ignition state , as [ IgnitionState ] ( AAOS ) values .
*
*
* CarPropertyManager first . But SWI68 does not expose IGNITION _STATE through CPM : the
* CarPropertyManager first . But SWI68 does not expose IGNITION _STATE through CPM : the
* property comes back empty , and the ignition state looked unreadable although Katman5
* property comes back empty , and the ignition state looked unreadable although Layer5
* reports it correctly — the log shows it ( ` Katman5 : initial state = 2 ( RUN / READY ) ` ) while
* reports it correctly — the log shows it ( ` Layer5 : initial state = 2 ( RUN / READY ) ` ) while
* the diagnostics classed IGNITION _STATE as NOT_READABLE .
* the diagnostics classed IGNITION _STATE as NOT_READABLE .
*
*
* Hence the fallback to the last state delivered by IVehicleConditionService , converted :
* Hence the fallback to the last state delivered by IVehicleConditionService , converted :
* Katman5 speaks [ CarIgnitionItem ] , this function returns [ IgnitionState ] , and the two
* Layer5 speaks [ CarIgnitionItem ] , this function returns [ IgnitionState ] , and the two
* scales do not line up ( RUN is 2 in one , OFF is 2 in the other ) .
* scales do not line up ( RUN is 2 in one , OFF is 2 in the other ) .
*
*
* Returns - 1 if nothing is readable , 0 if the property exists but says nothing .
* Returns - 1 if nothing is readable , 0 if the property exists but says nothing .
@@ -356,7 +359,7 @@ object EVHardware {
return vcmIgnitionAsAaos ( ) ?: global
return vcmIgnitionAsAaos ( ) ?: global
}
}
/** The last Katman 5 state translated into [IgnitionState], or null if there was none. */
/** The last Layer 5 state translated into [IgnitionState], or null if there was none. */
private fun vcmIgnitionAsAaos ( ) : Int ? = when ( sLastVcmIgnitionState ) {
private fun vcmIgnitionAsAaos ( ) : Int ? = when ( sLastVcmIgnitionState ) {
CarIgnitionItem . OFF -> IgnitionState . OFF
CarIgnitionItem . OFF -> IgnitionState . OFF
CarIgnitionItem . ACCESSORY -> IgnitionState . ACC
CarIgnitionItem . ACCESSORY -> IgnitionState . ACC
@@ -909,22 +912,22 @@ object EVHardware {
bindCarService ( context )
bindCarService ( context )
sVehicleBinder = getBinderService ( " vehiclesetting " )
sVehicleBinder = getBinderService ( " vehiclesetting " )
when {
when {
FirmwareInfo . getGeneration ( ) == FirmwareInfo . Gen . SWI68 -> initKatman4Swi68 ( context )
FirmwareInfo . getGeneration ( ) == FirmwareInfo . Gen . SWI68 -> initLayer4Swi68 ( context )
FirmwareInfo . getGeneration ( ) == FirmwareInfo . Gen . SWI165 -> initKatman4Swi68 ( context ) // same SDK as SWI68
FirmwareInfo . getGeneration ( ) == FirmwareInfo . Gen . SWI165 -> initLayer4Swi68 ( context ) // same SDK as SWI68
FirmwareInfo . getGeneration ( ) == FirmwareInfo . Gen . SWI132 -> initKatman4Swi69 ( context ) // CarVehicleSettingClient, same path as SWI69
FirmwareInfo . getGeneration ( ) == FirmwareInfo . Gen . SWI132 -> initLayer4Swi69 ( context ) // CarVehicleSettingClient, same path as SWI69
FirmwareInfo . isNewGenVsm ( ) -> initKatman4Swi69 ( context ) // SWI69 + SWI131
FirmwareInfo . isNewGenVsm ( ) -> initLayer4Swi69 ( context ) // SWI69 + SWI131
else -> initKatman4 ( context )
else -> initLayer4 ( context )
}
}
// Katman 5 — IGNITION_STATE push detection (VehicleConditionManager or ICarGeneralService)
// Layer 5 — IGNITION_STATE push detection (VehicleConditionManager or ICarGeneralService)
// SWI132 uses the same observed ICarGeneralService path as SWI69/SWI131.
// SWI132 uses the same observed ICarGeneralService path as SWI69/SWI131.
if ( FirmwareInfo . isNewGenVsm ( ) || FirmwareInfo . getGeneration ( ) == FirmwareInfo . Gen . SWI132 )
if ( FirmwareInfo . isNewGenVsm ( ) || FirmwareInfo . getGeneration ( ) == FirmwareInfo . Gen . SWI132 )
initKatman5Swi69 ( context )
initLayer5Swi69 ( context )
else
else
initKatman5 ( context )
initLayer5 ( context )
if ( sVehicleBinder != null )
if ( sVehicleBinder != null )
AppLogger . i ( TAG , " ✓ Katman 2: vehiclesetting binder OK " )
AppLogger . i ( TAG , " ✓ Layer 2: vehiclesetting binder OK " )
else
else
AppLogger . w ( TAG , " ✗ Katman 2: vehiclesetting null (SELinux — expected) " )
AppLogger . w ( TAG , " ✗ Layer 2: vehiclesetting null (SELinux — expected) " )
// Start the door watcher at boot if the feature is enabled (all firmwares).
// Start the door watcher at boot if the feature is enabled (all firmwares).
startDoorWatcherIfEnabled ( )
startDoorWatcherIfEnabled ( )
@@ -935,7 +938,7 @@ object EVHardware {
}
}
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Katman 1 — android.car.Car (async, mirrors original bindCarService exactly)
// Layer 1 — android.car.Car (async, mirrors original bindCarService exactly)
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
private fun bindCarService ( context : Context ) {
private fun bindCarService ( context : Context ) {
@@ -944,12 +947,12 @@ object EVHardware {
val carClass : Class < * >
val carClass : Class < * >
try {
try {
carClass = Class . forName ( " android.car.Car " )
carClass = Class . forName ( " android.car.Car " )
AppLogger . i ( TAG , " Katman 1: android.car.Car class found ✓ " )
AppLogger . i ( TAG , " Layer 1: android.car.Car class found ✓ " )
} catch ( e : ClassNotFoundException ) {
} catch ( e : ClassNotFoundException ) {
AppLogger . w ( TAG , " Katman 1: android.car.Car not found — not Automotive? " )
AppLogger . w ( TAG , " Layer 1: android.car.Car not found — not Automotive? " )
return
return
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . e ( TAG , " Katman 1: forName error: ${e.message} " )
AppLogger . e ( TAG , " Layer 1: forName error: ${e.message} " )
return
return
}
}
@@ -958,7 +961,7 @@ object EVHardware {
// Attempt 1: createCar(Context)
// Attempt 1: createCar(Context)
try {
try {
car = carClass . getMethod ( " createCar " , Context :: class . java ) . invoke ( null , context )
car = carClass . getMethod ( " createCar " , Context :: class . java ) . invoke ( null , context )
if ( car != null ) AppLogger . i ( TAG , " Katman 1: createCar(Context) → success " )
if ( car != null ) AppLogger . i ( TAG , " Layer 1: createCar(Context) → success " )
} catch ( _ : Exception ) { }
} catch ( _ : Exception ) { }
// Attempt 2: createCar(Context, Handler)
// Attempt 2: createCar(Context, Handler)
@@ -966,7 +969,7 @@ object EVHardware {
try {
try {
car = carClass . getMethod ( " createCar " , Context :: class . java , Handler :: class . java )
car = carClass . getMethod ( " createCar " , Context :: class . java , Handler :: class . java )
. invoke ( null , context , null )
. invoke ( null , context , null )
if ( car != null ) AppLogger . i ( TAG , " Katman 1: createCar(Context, Handler) → success " )
if ( car != null ) AppLogger . i ( TAG , " Layer 1: createCar(Context, Handler) → success " )
} catch ( _ : Exception ) { }
} catch ( _ : Exception ) { }
}
}
@@ -974,18 +977,18 @@ object EVHardware {
var scMethodFound : java . lang . reflect . Method ? = null
var scMethodFound : java . lang . reflect . Method ? = null
try {
try {
scMethodFound = carClass . getMethod ( " createCar " , Context :: class . java , ServiceConnection :: class . java )
scMethodFound = carClass . getMethod ( " createCar " , Context :: class . java , ServiceConnection :: class . java )
AppLogger . i ( TAG , " Katman 1: createCar(Context, ServiceConnection) method found " )
AppLogger . i ( TAG , " Layer 1: createCar(Context, ServiceConnection) method found " )
} catch ( _ : Exception ) { }
} catch ( _ : Exception ) { }
if ( car == null && scMethodFound != null ) {
if ( car == null && scMethodFound != null ) {
try {
try {
val sc = object : ServiceConnection {
val sc = object : ServiceConnection {
override fun onServiceConnected ( name : ComponentName ? , binder : IBinder ? ) {
override fun onServiceConnected ( name : ComponentName ? , binder : IBinder ? ) {
AppLogger . i ( TAG , " Katman 1: ServiceConnection.onServiceConnected " )
AppLogger . i ( TAG , " Layer 1: ServiceConnection.onServiceConnected " )
tryGetManagersFromCar ( carClass )
tryGetManagersFromCar ( carClass )
}
}
override fun onServiceDisconnected ( name : ComponentName ? ) {
override fun onServiceDisconnected ( name : ComponentName ? ) {
AppLogger . w ( TAG , " Katman 1: Car service disconnected " )
AppLogger . w ( TAG , " Layer 1: Car service disconnected " )
sCarPropertyManager = null
sCarPropertyManager = null
sCarHvacManager = null
sCarHvacManager = null
clearCarPropertyMethods ( )
clearCarPropertyMethods ( )
@@ -993,14 +996,14 @@ object EVHardware {
}
}
}
}
car = scMethodFound . invoke ( null , context , sc )
car = scMethodFound . invoke ( null , context , sc )
if ( car != null ) AppLogger . i ( TAG , " Katman 1: createCar(Context, SC) → callback pending " )
if ( car != null ) AppLogger . i ( TAG , " Layer 1: createCar(Context, SC) → callback pending " )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 1: createCar(Context, SC) error: ${e.message} " )
AppLogger . w ( TAG , " Layer 1: createCar(Context, SC) error: ${e.message} " )
}
}
}
}
if ( car == null ) {
if ( car == null ) {
AppLogger . e ( TAG , " Katman 1: all createCar methods failed " )
AppLogger . e ( TAG , " Layer 1: all createCar methods failed " )
// Not necessarily fatal: on a cold head unit the Car service is simply not up
// Not necessarily fatal: on a cold head unit the Car service is simply not up
// yet. Giving up here would leave the whole process without a vehicle forever.
// yet. Giving up here would leave the whole process without a vehicle forever.
armCarServiceWatchdog ( context , carClass )
armCarServiceWatchdog ( context , carClass )
@@ -1012,11 +1015,11 @@ object EVHardware {
// Call car.connect() if available (required on older builds)
// Call car.connect() if available (required on older builds)
try {
try {
carClass . getMethod ( " connect " ) . invoke ( car )
carClass . getMethod ( " connect " ) . invoke ( car )
AppLogger . i ( TAG , " Katman 1: car.connect() called " )
AppLogger . i ( TAG , " Layer 1: car.connect() called " )
} catch ( _ : NoSuchMethodException ) {
} catch ( _ : NoSuchMethodException ) {
// connect() not present on all builds, ignore
// connect() not present on all builds, ignore
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 1: car.connect() error: ${e.message} " )
AppLogger . w ( TAG , " Layer 1: car.connect() error: ${e.message} " )
}
}
// Try sync managers immediately
// Try sync managers immediately
@@ -1075,9 +1078,9 @@ object EVHardware {
( carClass . getMethod ( " isConnected " ) . invoke ( car ) as ? Boolean ) ?: true
( carClass . getMethod ( " isConnected " ) . invoke ( car ) as ? Boolean ) ?: true
} catch ( _ : Exception ) { true }
} catch ( _ : Exception ) { true }
AppLogger . i ( TAG , " Katman 1: isConnected() → $connected " )
AppLogger . i ( TAG , " Layer 1: isConnected() → $connected " )
if ( ! connected ) {
if ( ! connected ) {
AppLogger . w ( TAG , " Katman 1: car not yet connected " )
AppLogger . w ( TAG , " Layer 1: car not yet connected " )
return
return
}
}
@@ -1088,9 +1091,9 @@ object EVHardware {
val svc = carClass . getField ( " PROPERTY_SERVICE " ) . get ( null ) as String
val svc = carClass . getField ( " PROPERTY_SERVICE " ) . get ( null ) as String
sCarPropertyManager = getCarManager . invoke ( car , svc )
sCarPropertyManager = getCarManager . invoke ( car , svc )
cacheCarPropertyMethods ( sCarPropertyManager )
cacheCarPropertyMethods ( sCarPropertyManager )
AppLogger . i ( TAG , " Katman 1: CarPropertyManager READY ✓ " )
AppLogger . i ( TAG , " Layer 1: CarPropertyManager READY ✓ " )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 1: CarPropertyManager unavailable: ${e.message} " )
AppLogger . w ( TAG , " Layer 1: CarPropertyManager unavailable: ${e.message} " )
}
}
}
}
@@ -1098,28 +1101,28 @@ object EVHardware {
try {
try {
val svc = carClass . getField ( " HVAC_SERVICE " ) . get ( null ) as String
val svc = carClass . getField ( " HVAC_SERVICE " ) . get ( null ) as String
sCarHvacManager = getCarManager . invoke ( car , svc )
sCarHvacManager = getCarManager . invoke ( car , svc )
AppLogger . i ( TAG , " Katman 1: CarHvacManager READY ✓ " )
AppLogger . i ( TAG , " Layer 1: CarHvacManager READY ✓ " )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 1: CarHvacManager unavailable: ${e.message} " )
AppLogger . w ( TAG , " Layer 1: CarHvacManager unavailable: ${e.message} " )
}
}
}
}
// Notify whenKatman 1Ready subscribers as soon as both managers are ready
// Notify whenLayer 1Ready subscribers as soon as both managers are ready
if ( sCarPropertyManager != null && sCarHvacManager != null && katman1ReadyListeners . isNotEmpty ( ) ) {
if ( sCarPropertyManager != null && sCarHvacManager != null && layer1ReadyListeners . isNotEmpty ( ) ) {
val toNotify = katman1ReadyListeners . toList ( )
val toNotify = layer1ReadyListeners . toList ( )
katman1ReadyListeners . clear ( )
layer1ReadyListeners . clear ( )
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
}
}
// Try to register the IGNITION_STATE callback (best effort)
// Try to register the IGNITION_STATE callback (best effort)
if ( sCarPropertyManager != null ) registerIgnitionPropertyCallback ( )
if ( sCarPropertyManager != null ) registerIgnitionPropertyCallback ( )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . e ( TAG , " Katman 1: tryGetManagersFromCar error: ${e.message} " )
AppLogger . e ( TAG , " Layer 1: tryGetManagersFromCar error: ${e.message} " )
}
}
}
}
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Katman 2 — ServiceManager raw binder
// Layer 2 — ServiceManager raw binder
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
private fun getBinderService ( serviceName : String ) : IBinder ? {
private fun getBinderService ( serviceName : String ) : IBinder ? {
@@ -1136,10 +1139,10 @@ object EVHardware {
}
}
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Katman 4 — VehiclePropertyManager via saicmotor.hmi.launcher context
// Layer 4 — VehiclePropertyManager via saicmotor.hmi.launcher context
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
private fun initKatman4 ( context : Context ) {
private fun initLayer4 ( context : Context ) {
if ( sVpm != null ) return
if ( sVpm != null ) return
val launcherCtx : Context
val launcherCtx : Context
val vpmClass : Class < * >
val vpmClass : Class < * >
@@ -1151,8 +1154,8 @@ object EVHardware {
vpmClass = launcherCtx . classLoader
vpmClass = launcherCtx . classLoader
. loadClass ( " com.saicmotor.sdk.vehiclesettings.manager.VehiclePropertyManager " )
. loadClass ( " com.saicmotor.sdk.vehiclesettings.manager.VehiclePropertyManager " )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 4: package/class error: ${e.message} — will retry " )
AppLogger . w ( TAG , " Layer 4: package/class error: ${e.message} — will retry " )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initKatman4 ( context . applicationContext ) } , 5_000 )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initLayer4 ( context . applicationContext ) } , 5_000 )
return
return
}
}
@@ -1184,8 +1187,8 @@ object EVHardware {
}
}
if ( vpm == null ) {
if ( vpm == null ) {
AppLogger . w ( TAG , " Katman 4: all attempts failed — will retry " )
AppLogger . w ( TAG , " Layer 4: all attempts failed — will retry " )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initKatman4 ( context . applicationContext ) } , 10_000 )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initLayer4 ( context . applicationContext ) } , 10_000 )
return
return
}
}
@@ -1221,7 +1224,7 @@ object EVHardware {
}
}
tryGetVpmService ( vpm !! )
tryGetVpmService ( vpm !! )
AppLogger . i ( TAG , " Katman 4: VPM ready — mIVehiclePropertyService= ${if (sVpmService != null) "OK ✓" else "null (en attente)"} " )
AppLogger . i ( TAG , " Layer 4: VPM ready — mIVehiclePropertyService= ${if (sVpmService != null) "OK ✓" else "null (en attente)"} " )
}
}
private fun initWithServiceListener ( vpm : Any , context : Context , launcherCtx : Context ) {
private fun initWithServiceListener ( vpm : Any , context : Context , launcherCtx : Context ) {
@@ -1231,7 +1234,7 @@ object EVHardware {
val methodSummary = vpmClass . methods . joinToString ( " , " ) { m ->
val methodSummary = vpmClass . methods . joinToString ( " , " ) { m ->
" ${m.name} ( ${m.parameterTypes.joinToString(",") { it.simpleName } }) "
" ${m.name} ( ${m.parameterTypes.joinToString(",") { it.simpleName } }) "
}
}
AppLogger . d ( TAG , " Katman 4: VPM methods = $methodSummary " )
AppLogger . d ( TAG , " Layer 4: VPM methods = $methodSummary " )
// Strategy 1: Inspect the actual init() signature to get the real listener type
// Strategy 1: Inspect the actual init() signature to get the real listener type
val initMethod2 = vpmClass . methods . firstOrNull { m ->
val initMethod2 = vpmClass . methods . firstOrNull { m ->
@@ -1241,7 +1244,7 @@ object EVHardware {
if ( initMethod2 != null ) {
if ( initMethod2 != null ) {
val listenerType = initMethod2 . parameterTypes [ 1 ]
val listenerType = initMethod2 . parameterTypes [ 1 ]
AppLogger . i ( TAG , " Katman 4: init() found, listener type = ${listenerType.name} " )
AppLogger . i ( TAG , " Layer 4: init() found, listener type = ${listenerType.name} " )
// Try dynamic proxy with the actual listener interface type
// Try dynamic proxy with the actual listener interface type
if ( listenerType . isInterface ) {
if ( listenerType . isInterface ) {
@@ -1251,11 +1254,11 @@ object EVHardware {
) { _ , method , _ ->
) { _ , method , _ ->
when ( method . name ) {
when ( method . name ) {
" onServiceConnected " -> {
" onServiceConnected " -> {
AppLogger . i ( TAG , " Katman 4: onServiceConnected ✓ " )
AppLogger . i ( TAG , " Layer 4: onServiceConnected ✓ " )
tryGetVpmService ( vpm )
tryGetVpmService ( vpm )
}
}
" onServiceDisconnected " -> {
" onServiceDisconnected " -> {
AppLogger . w ( TAG , " Katman 4: onServiceDisconnected " )
AppLogger . w ( TAG , " Layer 4: onServiceDisconnected " )
sVpmService = null
sVpmService = null
}
}
else -> { }
else -> { }
@@ -1263,53 +1266,53 @@ object EVHardware {
null
null
}
}
initMethod2 . invoke ( vpm , context , proxy )
initMethod2 . invoke ( vpm , context , proxy )
AppLogger . i ( TAG , " Katman 4: init(Context, proxy) ✓ " )
AppLogger . i ( TAG , " Layer 4: init(Context, proxy) ✓ " )
return
return
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 4: init(Context, proxy) failed: ${e.message} " )
AppLogger . d ( TAG , " Layer 4: init(Context, proxy) failed: ${e.message} " )
}
}
}
}
// Fallback: try init(Context, null) — works if listener is nullable
// Fallback: try init(Context, null) — works if listener is nullable
try {
try {
initMethod2 . invoke ( vpm , context , null )
initMethod2 . invoke ( vpm , context , null )
AppLogger . i ( TAG , " Katman 4: init(Context, null) ✓ " )
AppLogger . i ( TAG , " Layer 4: init(Context, null) ✓ " )
return
return
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 4: init(Context, null) failed: ${e.message} " )
AppLogger . d ( TAG , " Layer 4: init(Context, null) failed: ${e.message} " )
}
}
}
}
// Strategy 2: init(Context) single-param
// Strategy 2: init(Context) single-param
try {
try {
vpmClass . getMethod ( " init " , Context :: class . java ) . invoke ( vpm , context )
vpmClass . getMethod ( " init " , Context :: class . java ) . invoke ( vpm , context )
AppLogger . i ( TAG , " Katman 4: init(Context) ✓ " )
AppLogger . i ( TAG , " Layer 4: init(Context) ✓ " )
return
return
} catch ( _ : NoSuchMethodException ) {
} catch ( _ : NoSuchMethodException ) {
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 4: init(Context) failed: ${e.message} " )
AppLogger . d ( TAG , " Layer 4: init(Context) failed: ${e.message} " )
}
}
// Strategy 3: init() no-arg
// Strategy 3: init() no-arg
try {
try {
vpmClass . getMethod ( " init " ) . invoke ( vpm )
vpmClass . getMethod ( " init " ) . invoke ( vpm )
AppLogger . i ( TAG , " Katman 4: init() ✓ " )
AppLogger . i ( TAG , " Layer 4: init() ✓ " )
return
return
} catch ( _ : NoSuchMethodException ) {
} catch ( _ : NoSuchMethodException ) {
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 4: init() failed: ${e.message} " )
AppLogger . d ( TAG , " Layer 4: init() failed: ${e.message} " )
}
}
AppLogger . w ( TAG , " Katman 4: no working init() — mIVehiclePropertyService stays null " )
AppLogger . w ( TAG , " Layer 4: no working init() — mIVehiclePropertyService stays null " )
}
}
/** Runs [block], returns the result or null, logs the result/error. */
/** Runs [block], returns the result or null, logs the result/error. */
private fun tryInvoke ( label : String , block : ( ) -> Any ? ) : Any ? = try {
private fun tryInvoke ( label : String , block : ( ) -> Any ? ) : Any ? = try {
val r = block ( )
val r = block ( )
AppLogger . i ( TAG , " Katman 4: $label → ${if (r != null) "OK ($r)" else "null"} " )
AppLogger . i ( TAG , " Layer 4: $label → ${if (r != null) "OK ($r)" else "null"} " )
r
r
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 4: $label → ${e.javaClass.simpleName} : ${e.message} " )
AppLogger . d ( TAG , " Layer 4: $label → ${e.javaClass.simpleName} : ${e.message} " )
null
null
}
}
@@ -1322,10 +1325,10 @@ object EVHardware {
val svc = f . get ( vpm )
val svc = f . get ( vpm )
if ( svc != null ) {
if ( svc != null ) {
sVpmService = svc
sVpmService = svc
AppLogger . i ( TAG , " Katman 4: mIVehiclePropertyService READY ✓ " )
AppLogger . i ( TAG , " Layer 4: mIVehiclePropertyService READY ✓ " )
// Notify any pending Katman 4 listeners
// Notify any pending Layer 4 listeners
val toNotify = katman4ReadyListeners . toList ( )
val toNotify = layer4ReadyListeners . toList ( )
katman4ReadyListeners . clear ( )
layer4ReadyListeners . clear ( )
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
}
}
return
return
@@ -1655,10 +1658,10 @@ object EVHardware {
}
}
}
}
// Katman 4 SWI68 — VehicleSettingManager via saicmotor.hmi.launcher context
// Layer 4 SWI68 — VehicleSettingManager via saicmotor.hmi.launcher context
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
private fun initKatman4Swi68 ( context : Context ) {
private fun initLayer4Swi68 ( context : Context ) {
if ( sVsm != null ) return
if ( sVsm != null ) return
val launcherCtx : Context
val launcherCtx : Context
val vsmClass : Class < * >
val vsmClass : Class < * >
@@ -1671,7 +1674,7 @@ object EVHardware {
AppLogger . i ( TAG , " SWI68: VehicleSettingManager class found ✓ " )
AppLogger . i ( TAG , " SWI68: VehicleSettingManager class found ✓ " )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " SWI68: class load error: ${e.message} — retry in 5s " )
AppLogger . w ( TAG , " SWI68: class load error: ${e.message} — retry in 5s " )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initKatman4Swi68 ( context . applicationContext ) } , 5_000 )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initLayer4Swi68 ( context . applicationContext ) } , 5_000 )
return
return
}
}
@@ -1757,8 +1760,8 @@ object EVHardware {
if ( svc != null ) {
if ( svc != null ) {
sVsmService = svc
sVsmService = svc
AppLogger . i ( TAG , " SWI68: mVehicleSettingService READY ✓ " )
AppLogger . i ( TAG , " SWI68: mVehicleSettingService READY ✓ " )
val toNotify = katman4ReadyListeners . toList ( )
val toNotify = layer4ReadyListeners . toList ( )
katman4ReadyListeners . clear ( )
layer4ReadyListeners . clear ( )
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
}
}
return
return
@@ -1767,7 +1770,7 @@ object EVHardware {
}
}
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Katman 4 SWI69/SWI131 — CarVehicleSettingClient via CarAdapterClient
// Layer 4 SWI69/SWI131 — CarVehicleSettingClient via CarAdapterClient
//
//
// Runtime integration contract:
// Runtime integration contract:
// CarAdapterClient.getInstance(ctx).start()
// CarAdapterClient.getInstance(ctx).start()
@@ -1779,7 +1782,7 @@ object EVHardware {
// (getAccTjaState, setAccTjaState, getLasWarningSound, getFcwState, etc.)
// (getAccTjaState, setAccTjaState, getLasWarningSound, getFcwState, etc.)
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
private fun initKatman4Swi69 ( context : Context ) {
private fun initLayer4Swi69 ( context : Context ) {
if ( sVsm != null ) return
if ( sVsm != null ) return
val launcherCtx : Context
val launcherCtx : Context
@@ -1795,7 +1798,7 @@ object EVHardware {
AppLogger . i ( TAG , " SWI69: CarAdapterClient + CarVehicleSettingClient classes found ✓ " )
AppLogger . i ( TAG , " SWI69: CarAdapterClient + CarVehicleSettingClient classes found ✓ " )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " SWI69: class load error: ${e.message} — retry in 5s " )
AppLogger . w ( TAG , " SWI69: class load error: ${e.message} — retry in 5s " )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initKatman4Swi69 ( context . applicationContext ) } , 5_000 )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initLayer4Swi69 ( context . applicationContext ) } , 5_000 )
return
return
}
}
@@ -1808,7 +1811,7 @@ object EVHardware {
if ( adapter == null ) {
if ( adapter == null ) {
AppLogger . w ( TAG , " SWI69: CarAdapterClient.getInstance() failed — retry in 10s " )
AppLogger . w ( TAG , " SWI69: CarAdapterClient.getInstance() failed — retry in 10s " )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initKatman4Swi69 ( context . applicationContext ) } , 10_000 )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initLayer4Swi69 ( context . applicationContext ) } , 10_000 )
return
return
}
}
@@ -1878,8 +1881,8 @@ object EVHardware {
sVsmService = ibinder
sVsmService = ibinder
AppLogger . i ( TAG , " SWI69: CarVehicleSettingClient READY ✓ " )
AppLogger . i ( TAG , " SWI69: CarVehicleSettingClient READY ✓ " )
val toNotify = katman4ReadyListeners . toList ( )
val toNotify = layer4ReadyListeners . toList ( )
katman4ReadyListeners . clear ( )
layer4ReadyListeners . clear ( )
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " SWI69: tryInitClientFromAdapter error: ${e.message} " )
AppLogger . d ( TAG , " SWI69: tryInitClientFromAdapter error: ${e.message} " )
@@ -1892,7 +1895,7 @@ object EVHardware {
* [ a9ClientBinder ] returns null for two entirely different reasons — the code is not served ,
* [ a9ClientBinder ] returns null for two entirely different reasons — the code is not served ,
* or there is no adapter to ask — and a survey that cannot tell them apart records " absent "
* or there is no adapter to ask — and a survey that cannot tell them apart records " absent "
* for a question that was never put to the vehicle . `sCarAdapter` is assigned only by
* for a question that was never put to the vehicle . `sCarAdapter` is assigned only by
* `initKatman 4Swi69` , so on SWI68 every `queryClient` in a probe fails before it reaches a
* `initLayer 4Swi69` , so on SWI68 every `queryClient` in a probe fails before it reaches a
* binder . RI - 002 was nearly closed on that false negative ; the probe now says so out loud .
* binder . RI - 002 was nearly closed on that false negative ; the probe now says so out loud .
* /
* /
internal fun hasCarAdapterClient ( ) : Boolean = sCarAdapter != null && sCarAdapterClass != null
internal fun hasCarAdapterClient ( ) : Boolean = sCarAdapter != null && sCarAdapterClass != null
@@ -2185,11 +2188,11 @@ object EVHardware {
when ( result ) {
when ( result ) {
is Int -> result
is Int -> result
is Number -> result . toInt ( )
is Number -> result . toInt ( )
null -> { AppLogger . d ( TAG , " Katman 4: getMixProperty(0x ${propId.toString(16)} ) = null " ) ; - 1 }
null -> { AppLogger . d ( TAG , " Layer 4: getMixProperty(0x ${propId.toString(16)} ) = null " ) ; - 1 }
else -> { AppLogger . d ( TAG , " Katman 4: getMixProperty(0x ${propId.toString(16)} ) = $result ( ${result.javaClass.simpleName} ) " ) ; - 1 }
else -> { AppLogger . d ( TAG , " Layer 4: getMixProperty(0x ${propId.toString(16)} ) = $result ( ${result.javaClass.simpleName} ) " ) ; - 1 }
}
}
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 4: getMixProperty(0x ${propId.toString(16)} ) exc: ${e.message} " )
AppLogger . d ( TAG , " Layer 4: getMixProperty(0x ${propId.toString(16)} ) exc: ${e.message} " )
- 1
- 1
}
}
}
}
@@ -2201,10 +2204,10 @@ object EVHardware {
vpm . javaClass
vpm . javaClass
. getMethod ( " setMixProperty " , Class :: class . java , Int :: class . java , Any :: class . java )
. getMethod ( " setMixProperty " , Class :: class . java , Int :: class . java , Any :: class . java )
. invoke ( vpm , Int :: class . javaObjectType , propId , value )
. invoke ( vpm , Int :: class . javaObjectType , propId , value )
AppLogger . i ( TAG , " Katman 4: setMixProperty(0x ${propId.toString(16)} , $value ) ✓ " )
AppLogger . i ( TAG , " Layer 4: setMixProperty(0x ${propId.toString(16)} , $value ) ✓ " )
true
true
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 4: setMixProperty(0x ${propId.toString(16)} , $value ) exc: ${e.message} " )
AppLogger . d ( TAG , " Layer 4: setMixProperty(0x ${propId.toString(16)} , $value ) exc: ${e.message} " )
false
false
}
}
}
}
@@ -2359,7 +2362,7 @@ object EVHardware {
}
}
/ * *
/ * *
* SAIC proprietary binder transact ( Katman2 fallback ) .
* SAIC proprietary binder transact ( Layer2 fallback ) .
* Runtime parcel contract : [ interfaceToken , AREA _GLOBAL , 1 , value , float [ ] , byte [ ] ]
* Runtime parcel contract : [ interfaceToken , AREA _GLOBAL , 1 , value , float [ ] , byte [ ] ]
* /
* /
private fun binderTransact ( binder : IBinder ? , descriptor : String , txCode : Int , value : Int ) : Boolean {
private fun binderTransact ( binder : IBinder ? , descriptor : String , txCode : Int , value : Int ) : Boolean {
@@ -2532,7 +2535,7 @@ object EVHardware {
}
}
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// ADAS API (Katman 4)
// ADAS API (Layer 4)
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
fun overspeedAlarmOnOrNull ( ) : Boolean ? {
fun overspeedAlarmOnOrNull ( ) : Boolean ? {
@@ -2603,7 +2606,7 @@ object EVHardware {
return setIntPropertyVpm ( PROP _SPEED _LIMIT _TONE , if ( on ) 1 else 0 )
return setIntPropertyVpm ( PROP _SPEED _LIMIT _TONE , if ( on ) 1 else 0 )
}
}
/** Returns 0– 4 (Off/Limiter/Auto/ACC/ICA), or -1 if Katman 4 not ready. */
/** Returns 0– 4 (Off/Limiter/Auto/ACC/ICA), or -1 if Layer 4 not ready. */
fun getMixedIntelligentDrive ( ) : Int = getMixIntProperty ( PROP _MIX _INTELLIGENT _DRIVE )
fun getMixedIntelligentDrive ( ) : Int = getMixIntProperty ( PROP _MIX _INTELLIGENT _DRIVE )
@RequiresStandstill
@RequiresStandstill
fun setMixedIntelligentDrive ( value : Int ) : Boolean {
fun setMixedIntelligentDrive ( value : Int ) : Boolean {
@@ -3161,7 +3164,7 @@ object EVHardware {
if ( ! hasDrowsinessAndEsc ( ) ) return false
if ( ! hasDrowsinessAndEsc ( ) ) return false
// [IgnitionState], not [CarIgnitionItem]: getCurrentIgnitionState() answers on the
// [IgnitionState], not [CarIgnitionItem]: getCurrentIgnitionState() answers on the
// AAOS scale, where the two do not line up — RUN is 2 for Katman 5 and OFF is 2 here.
// AAOS scale, where the two do not line up — RUN is 2 for Layer 5 and OFF is 2 here.
// Unreadable is 0 or -1 and is deliberately not a refusal, per the KDoc above.
// Unreadable is 0 or -1 and is deliberately not a refusal, per the KDoc above.
val ignition = getCurrentIgnitionState ( )
val ignition = getCurrentIgnitionState ( )
if ( ignition > 0 && ignition != IgnitionState . ON ) {
if ( ignition > 0 && ignition != IgnitionState . ON ) {
@@ -3439,10 +3442,10 @@ object EVHardware {
}
}
}
}
fun isKatman4Ready ( ) : Boolean =
fun isLayer4Ready ( ) : Boolean =
if ( FirmwareInfo . isVsmBased ( ) ) sVsm != null && sVsmService != null
if ( FirmwareInfo . isVsmBased ( ) ) sVsm != null && sVsmService != null
else sVpm != null && sVpmService != null
else sVpm != null && sVpmService != null
fun isKatman4VpmCreated ( ) : Boolean = sVpm != null || sVsm != null
fun isLayer4VpmCreated ( ) : Boolean = sVpm != null || sVsm != null
fun isCarPropertyManagerReady ( ) : Boolean = sCarPropertyManager != null
fun isCarPropertyManagerReady ( ) : Boolean = sCarPropertyManager != null
fun isCarHvacManagerReady ( ) : Boolean = sCarHvacManager != null
fun isCarHvacManagerReady ( ) : Boolean = sCarHvacManager != null
@@ -3540,7 +3543,7 @@ object EVHardware {
}
}
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Katman 5 SWI69/SWI131 — ICarGeneralService via CarAdapterClient (queryClient(0x1))
// Layer 5 SWI69/SWI131 — ICarGeneralService via CarAdapterClient (queryClient(0x1))
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
private data class Swi69Ctx (
private data class Swi69Ctx (
@@ -3566,16 +3569,16 @@ object EVHardware {
return null
return null
}
}
private fun initKatman5Swi69 ( context : Context ) {
private fun initLayer5Swi69 ( context : Context ) {
if ( sVcmCallbackRegistered ) return
if ( sVcmCallbackRegistered ) return
val classes = findSwi69Classes ( context ) ?: run {
val classes = findSwi69Classes ( context ) ?: run {
AppLogger . w ( TAG , " Katman 5 SWI69: CarAdapterClient/CarGeneralClient not found — retry in 10s " )
AppLogger . w ( TAG , " Layer 5 SWI69: CarAdapterClient/CarGeneralClient not found — retry in 10s " )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initKatman5Swi69 ( context . applicationContext ) } , 10_000 )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initLayer5Swi69 ( context . applicationContext ) } , 10_000 )
return
return
}
}
val ( _ , adapterClass , generalClientClass ) = classes
val ( _ , adapterClass , generalClientClass ) = classes
AppLogger . i ( TAG , " Katman 5 SWI69: classes loaded ✓ " )
AppLogger . i ( TAG , " Layer 5 SWI69: classes loaded ✓ " )
fun trySetupGeneralClient ( ) : Boolean {
fun trySetupGeneralClient ( ) : Boolean {
if ( sVcmCallbackRegistered ) return true
if ( sVcmCallbackRegistered ) return true
@@ -3589,14 +3592,14 @@ object EVHardware {
adapterClass . getMethod ( " queryClient " , Int :: class . javaPrimitiveType !! )
adapterClass . getMethod ( " queryClient " , Int :: class . javaPrimitiveType !! )
. invoke ( adapter , BIND _CODE _CAR _GENERAL ) as ? IBinder
. invoke ( adapter , BIND _CODE _CAR _GENERAL ) as ? IBinder
} catch ( _ : Exception ) { null } ?: run {
} catch ( _ : Exception ) { null } ?: run {
AppLogger . d ( TAG , " Katman 5 SWI69: queryClient(0x ${BIND_CODE_CAR_GENERAL.toString(16)} ) → null " )
AppLogger . d ( TAG , " Layer 5 SWI69: queryClient(0x ${BIND_CODE_CAR_GENERAL.toString(16)} ) → null " )
return false
return false
}
}
val client = try {
val client = try {
generalClientClass . getConstructor ( IBinder :: class . java ) . newInstance ( ibinder )
generalClientClass . getConstructor ( IBinder :: class . java ) . newInstance ( ibinder )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 5 SWI69: CarGeneralClient ctor error: ${e.message} " )
AppLogger . w ( TAG , " Layer 5 SWI69: CarGeneralClient ctor error: ${e.message} " )
return false
return false
}
}
sCarGeneral = client // kept for A9 screen brightness (setScreenBrightness)
sCarGeneral = client // kept for A9 screen brightness (setScreenBrightness)
@@ -3604,13 +3607,13 @@ object EVHardware {
val registMethod = generalClientClass . methods . firstOrNull {
val registMethod = generalClientClass . methods . firstOrNull {
it . name == " registListener " && it . parameterCount == 1
it . name == " registListener " && it . parameterCount == 1
} ?: run {
} ?: run {
AppLogger . w ( TAG , " Katman 5 SWI69: registListener not found " )
AppLogger . w ( TAG , " Layer 5 SWI69: registListener not found " )
return false
return false
}
}
val callbackType = registMethod . parameterTypes [ 0 ]
val callbackType = registMethod . parameterTypes [ 0 ]
if ( ! callbackType . isInterface ) {
if ( ! callbackType . isInterface ) {
AppLogger . w ( TAG , " Katman 5 SWI69: callback non-interface — proxy impossible " )
AppLogger . w ( TAG , " Layer 5 SWI69: callback non-interface — proxy impossible " )
return false
return false
}
}
@@ -3629,7 +3632,7 @@ object EVHardware {
0x7 -> { // TRANSACTION_onIgnitionStateChange
0x7 -> { // TRANSACTION_onIgnitionStateChange
data . enforceInterface ( " com.saicmotor.carapi.general.ICarGeneralCallback " )
data . enforceInterface ( " com.saicmotor.carapi.general.ICarGeneralCallback " )
val ignition = data . readInt ( )
val ignition = data . readInt ( )
AppLogger . i ( TAG , " Katman 5 SWI69: onTransact ignition= $ignition ( ${carIgnitionName(ignition)} ) " )
AppLogger . i ( TAG , " Layer 5 SWI69: onTransact ignition= $ignition ( ${carIgnitionName(ignition)} ) " )
dispatchVehicleConditionIgnition ( ignition )
dispatchVehicleConditionIgnition ( ignition )
reply ?. writeNoException ( )
reply ?. writeNoException ( )
true
true
@@ -3648,7 +3651,7 @@ object EVHardware {
// In-process path (rare) — the service calls the interface directly
// In-process path (rare) — the service calls the interface directly
val ignition = args ?. get ( 0 ) as ? Int
val ignition = args ?. get ( 0 ) as ? Int
if ( ignition != null ) {
if ( ignition != null ) {
AppLogger . i ( TAG , " Katman 5 SWI69: ignition= $ignition ( ${carIgnitionName(ignition)} ) " )
AppLogger . i ( TAG , " Layer 5 SWI69: ignition= $ignition ( ${carIgnitionName(ignition)} ) " )
dispatchVehicleConditionIgnition ( ignition )
dispatchVehicleConditionIgnition ( ignition )
}
}
}
}
@@ -3657,7 +3660,7 @@ object EVHardware {
null
null
}
}
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 5 SWI69: proxy creation error: ${e.message} " )
AppLogger . w ( TAG , " Layer 5 SWI69: proxy creation error: ${e.message} " )
return false
return false
}
}
@@ -3665,27 +3668,27 @@ object EVHardware {
registMethod . invoke ( client , proxy )
registMethod . invoke ( client , proxy )
sVcmListener = callbackBinder // strong reference to the Binder to avoid GC
sVcmListener = callbackBinder // strong reference to the Binder to avoid GC
sVcmCallbackRegistered = true
sVcmCallbackRegistered = true
AppLogger . i ( TAG , " Katman 5 SWI69: ICarGeneralCallback registered ✓ " )
AppLogger . i ( TAG , " Layer 5 SWI69: ICarGeneralCallback registered ✓ " )
val toNotify = katman5ReadyListeners . toList ( )
val toNotify = layer5ReadyListeners . toList ( )
katman5ReadyListeners . clear ( )
layer5ReadyListeners . clear ( )
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( {
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( {
try {
try {
val ignition = generalClientClass . getMethod ( " getIgnitionState " ) . invoke ( client ) as ? Int
val ignition = generalClientClass . getMethod ( " getIgnitionState " ) . invoke ( client ) as ? Int
if ( ignition != null ) {
if ( ignition != null ) {
AppLogger . i ( TAG , " Katman 5 SWI69: initial state ignition= $ignition " )
AppLogger . i ( TAG , " Layer 5 SWI69: initial state ignition= $ignition " )
dispatchVehicleConditionIgnition ( ignition )
dispatchVehicleConditionIgnition ( ignition )
}
}
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 5 SWI69: getIgnitionState error: ${e.message} " )
AppLogger . d ( TAG , " Layer 5 SWI69: getIgnitionState error: ${e.message} " )
}
}
} , 500L )
} , 500L )
true
true
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 5 SWI69: registListener error: ${e.message} " )
AppLogger . w ( TAG , " Layer 5 SWI69: registListener error: ${e.message} " )
false
false
}
}
}
}
@@ -3699,11 +3702,11 @@ object EVHardware {
}
}
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
// Katman 5 — VehicleConditionManager (IVehicleConditionService via IHubService)
// Layer 5 — VehicleConditionManager (IVehicleConditionService via IHubService)
// SWI133 / SWI68 / SWI165
// SWI133 / SWI68 / SWI165
// -------------------------------------------------------------------------
// -------------------------------------------------------------------------
private fun initKatman5 ( context : Context ) {
private fun initLayer5 ( context : Context ) {
if ( sVcmCallbackRegistered ) return
if ( sVcmCallbackRegistered ) return
val launcherCtx = listOf ( LAUNCHER68 _PKG , LAUNCHER69 _PKG ) . firstNotNullOfOrNull { pkg ->
val launcherCtx = listOf ( LAUNCHER68 _PKG , LAUNCHER69 _PKG ) . firstNotNullOfOrNull { pkg ->
@@ -3714,15 +3717,15 @@ object EVHardware {
)
)
} catch ( _ : Exception ) { null }
} catch ( _ : Exception ) { null }
} ?: run {
} ?: run {
AppLogger . w ( TAG , " Katman 5: launcher package not found " )
AppLogger . w ( TAG , " Layer 5: launcher package not found " )
return
return
}
}
val vcmClass = try {
val vcmClass = try {
launcherCtx . classLoader . loadClass ( VCM _CLASS )
launcherCtx . classLoader . loadClass ( VCM _CLASS )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 5: VCM class not found: ${e.message} — retry in 10s " )
AppLogger . w ( TAG , " Layer 5: VCM class not found: ${e.message} — retry in 10s " )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initKatman5 ( context . applicationContext ) } , 10_000 )
Handler ( Looper . getMainLooper ( ) ) . postDelayed ( { initLayer5 ( context . applicationContext ) } , 10_000 )
return
return
}
}
@@ -3734,7 +3737,7 @@ object EVHardware {
} catch ( _ : Exception ) { null }
} catch ( _ : Exception ) { null }
if ( existing != null ) {
if ( existing != null ) {
AppLogger . i ( TAG , " Katman 5: VCM singleton already exists ✓ " )
AppLogger . i ( TAG , " Layer 5: VCM singleton already exists ✓ " )
sVcm = existing
sVcm = existing
setupVcmCallback ( existing , launcherCtx )
setupVcmCallback ( existing , launcherCtx )
return
return
@@ -3755,7 +3758,7 @@ object EVHardware {
) { _ , method , args ->
) { _ , method , args ->
when ( method . name ) {
when ( method . name ) {
" onServiceConnected " -> {
" onServiceConnected " -> {
AppLogger . i ( TAG , " Katman 5: onServiceConnected ✓ " )
AppLogger . i ( TAG , " Layer 5: onServiceConnected ✓ " )
val mgr = args ?. getOrNull ( 0 )
val mgr = args ?. getOrNull ( 0 )
?. takeIf { it . javaClass . name . contains ( " VehicleConditionManager " ) }
?. takeIf { it . javaClass . name . contains ( " VehicleConditionManager " ) }
val instance = mgr ?: try {
val instance = mgr ?: try {
@@ -3769,7 +3772,7 @@ object EVHardware {
}
}
}
}
" onServiceDisconnected " -> {
" onServiceDisconnected " -> {
AppLogger . w ( TAG , " Katman 5: onServiceDisconnected " )
AppLogger . w ( TAG , " Layer 5: onServiceDisconnected " )
sVcmCallbackRegistered = false
sVcmCallbackRegistered = false
sVcmListener = null
sVcmListener = null
}
}
@@ -3777,19 +3780,19 @@ object EVHardware {
null
null
}
}
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . d ( TAG , " Katman 5: proxy init error: ${e.message} " )
AppLogger . d ( TAG , " Layer 5: proxy init error: ${e.message} " )
null
null
}
}
} else null
} else null
try {
try {
initMethod . invoke ( null , context . applicationContext , listenerArg )
initMethod . invoke ( null , context . applicationContext , listenerArg )
AppLogger . i ( TAG , " Katman 5: VehicleConditionManager.init() called " )
AppLogger . i ( TAG , " Layer 5: VehicleConditionManager.init() called " )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 5: init() error: ${e.message} " )
AppLogger . w ( TAG , " Layer 5: init() error: ${e.message} " )
}
}
} else {
} else {
AppLogger . w ( TAG , " Katman 5: init(Context, listener) not found " )
AppLogger . w ( TAG , " Layer 5: init(Context, listener) not found " )
}
}
// Retries — the singleton is available after the async connection
// Retries — the singleton is available after the async connection
@@ -3803,7 +3806,7 @@ object EVHardware {
f . get ( null )
f . get ( null )
} catch ( _ : Exception ) { null }
} catch ( _ : Exception ) { null }
if ( mgr != null && sVcm == null ) {
if ( mgr != null && sVcm == null ) {
AppLogger . i ( TAG , " Katman 5: singleton obtained @ ${delay} ms " )
AppLogger . i ( TAG , " Layer 5: singleton obtained @ ${delay} ms " )
sVcm = mgr
sVcm = mgr
setupVcmCallback ( mgr , launcherCtx )
setupVcmCallback ( mgr , launcherCtx )
} else if ( sVcm != null && ! sVcmCallbackRegistered ) {
} else if ( sVcm != null && ! sVcmCallbackRegistered ) {
@@ -3824,14 +3827,14 @@ object EVHardware {
m . parameterTypes [ 0 ] . isInterface &&
m . parameterTypes [ 0 ] . isInterface &&
m . parameterTypes [ 0 ] . methods . any { it . name . contains ( " ConditionChange " , ignoreCase = true ) }
m . parameterTypes [ 0 ] . methods . any { it . name . contains ( " ConditionChange " , ignoreCase = true ) }
} ?: run {
} ?: run {
AppLogger . w ( TAG , " Katman 5: registerVehicleConditionCallback not found — methods: $ {
AppLogger . w ( TAG , " Layer 5: registerVehicleConditionCallback not found — methods: $ {
vcm . javaClass . methods . filter { it . name . startsWith ( " register " ) } . joinToString { it . name }
vcm . javaClass . methods . filter { it . name . startsWith ( " register " ) } . joinToString { it . name }
} " )
} " )
return
return
}
}
val callbackType = registerMethod . parameterTypes [ 0 ]
val callbackType = registerMethod . parameterTypes [ 0 ]
AppLogger . i ( TAG , " Katman 5: callback type = ${callbackType.name} " )
AppLogger . i ( TAG , " Layer 5: callback type = ${callbackType.name} " )
val proxy = try {
val proxy = try {
java . lang . reflect . Proxy . newProxyInstance (
java . lang . reflect . Proxy . newProxyInstance (
@@ -3846,18 +3849,18 @@ object EVHardware {
// moved. Only the transitions are logged; every event is still
// moved. Only the transitions are logged; every event is still
// dispatched, because suppressing one would change behaviour.
// dispatched, because suppressing one would change behaviour.
if ( sIgnitionLog . accept ( ignition ) ) {
if ( sIgnitionLog . accept ( ignition ) ) {
AppLogger . i ( TAG , " Katman 5 event: ignition= $ignition ( ${carIgnitionName(ignition)} ) " )
AppLogger . i ( TAG , " Layer 5 event: ignition= $ignition ( ${carIgnitionName(ignition)} ) " )
}
}
dispatchVehicleConditionIgnition ( ignition )
dispatchVehicleConditionIgnition ( ignition )
}
}
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 5: onChangeEvent parse error: ${e.message} " )
AppLogger . w ( TAG , " Layer 5: onChangeEvent parse error: ${e.message} " )
}
}
}
}
null
null
}
}
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 5: proxy creation error: ${e.message} " )
AppLogger . w ( TAG , " Layer 5: proxy creation error: ${e.message} " )
return
return
}
}
@@ -3865,10 +3868,10 @@ object EVHardware {
registerMethod . invoke ( vcm , proxy )
registerMethod . invoke ( vcm , proxy )
sVcmListener = proxy
sVcmListener = proxy
sVcmCallbackRegistered = true
sVcmCallbackRegistered = true
AppLogger . i ( TAG , " Katman 5: callback registered ✓ " )
AppLogger . i ( TAG , " Layer 5: callback registered ✓ " )
val toNotify = katman5ReadyListeners . toList ( )
val toNotify = layer5ReadyListeners . toList ( )
katman5ReadyListeners . clear ( )
layer5ReadyListeners . clear ( )
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
Handler ( Looper . getMainLooper ( ) ) . post { toNotify . forEach { it ( ) } }
// Immediate read (the callback only fires on CHANGE)
// Immediate read (the callback only fires on CHANGE)
@@ -3877,13 +3880,13 @@ object EVHardware {
vcm . javaClass . getMethod ( " getVehicleIgnition " ) . invoke ( vcm ) as ? Int
vcm . javaClass . getMethod ( " getVehicleIgnition " ) . invoke ( vcm ) as ? Int
} catch ( _ : Exception ) { null }
} catch ( _ : Exception ) { null }
if ( ignition != null ) {
if ( ignition != null ) {
AppLogger . i ( TAG , " Katman 5: initial state = $ignition ( ${carIgnitionName(ignition)} ) " )
AppLogger . i ( TAG , " Layer 5: initial state = $ignition ( ${carIgnitionName(ignition)} ) " )
dispatchVehicleConditionIgnition ( ignition )
dispatchVehicleConditionIgnition ( ignition )
}
}
} , 500L )
} , 500L )
} catch ( e : Exception ) {
} catch ( e : Exception ) {
AppLogger . w ( TAG , " Katman 5: registerVehicleConditionCallback error: ${e.message} " )
AppLogger . w ( TAG , " Layer 5: registerVehicleConditionCallback error: ${e.message} " )
}
}
}
}
@@ -3938,15 +3941,15 @@ object EVHardware {
sb . appendLine ( )
sb . appendLine ( )
sb . appendLine ( " ── Services ── " )
sb . appendLine ( " ── Services ── " )
sb . appendLine ( " Katman 1 CPM : ${if (sCarPropertyManager != null) "✓" else "✗"} " )
sb . appendLine ( " Layer 1 CPM : ${if (sCarPropertyManager != null) "✓" else "✗"} " )
sb . appendLine ( " Katman 1 HVAC : ${if (sCarHvacManager != null) "✓" else "✗"} " )
sb . appendLine ( " Layer 1 HVAC : ${if (sCarHvacManager != null) "✓" else "✗"} " )
sb . appendLine ( " Katman 4 created : ${if (isKatman 4VpmCreated()) "✓" else "✗"} " )
sb . appendLine ( " Layer 4 created : ${if (isLayer 4VpmCreated()) "✓" else "✗"} " )
sb . appendLine ( " Katman 4 ready : ${if (isKatman 4Ready()) "✓" else "✗"} " )
sb . appendLine ( " Layer 4 ready : ${if (isLayer 4Ready()) "✓" else "✗"} " )
sb . appendLine ( " Binder vsett : ${if (sVehicleBinder != null) "✓ OK" else "✗ null"} " )
sb . appendLine ( " Binder vsett : ${if (sVehicleBinder != null) "✓ OK" else "✗ null"} " )
val katman5Path = if ( FirmwareInfo . isNewGenVsm ( ) || gen == FirmwareInfo . Gen . SWI132 )
val layer5Path = if ( FirmwareInfo . isNewGenVsm ( ) || gen == FirmwareInfo . Gen . SWI132 )
" ICarGeneralService " else " VehicleConditionMgr "
" ICarGeneralService " else " VehicleConditionMgr "
sb . appendLine ( " Katman 5 ready : ${if (sVcmCallbackRegistered) "✓ ($katman 5Path)" else "✗ ($katman 5Path)"} " )
sb . appendLine ( " Layer 5 ready : ${if (sVcmCallbackRegistered) "✓ ($layer 5Path)" else "✗ ($layer 5Path)"} " )
sb . appendLine ( " Katman 5 ign : ${carIgnitionName(sLastVcmIgnitionState)} ( $sLastVcmIgnitionState ) " )
sb . appendLine ( " Layer 5 ign : ${carIgnitionName(sLastVcmIgnitionState)} ( $sLastVcmIgnitionState ) " )
sb . appendLine ( )
sb . appendLine ( )
if ( gen == FirmwareInfo . Gen . SWI132 ) {
if ( gen == FirmwareInfo . Gen . SWI132 ) {
@@ -4013,7 +4016,7 @@ object EVHardware {
}
}
sb . appendLine ( )
sb . appendLine ( )
// ── CarVehicleSettingClient (Katman 4) ─────────────────────────
// ── CarVehicleSettingClient (Layer 4) ─────────────────────────
sb . appendLine ( " ── SWI132 CarVehicleSettingClient (sVsm= ${if (sVsm != null) "✓" else "✗ null"} ) ── " )
sb . appendLine ( " ── SWI132 CarVehicleSettingClient (sVsm= ${if (sVsm != null) "✓" else "✗ null"} ) ── " )
fun vsmGet ( method : String ) : Int = try {
fun vsmGet ( method : String ) : Int = try {
( callVsm ( method ) as ? Int ) ?: - 1
( callVsm ( method ) as ? Int ) ?: - 1
@@ -4177,7 +4180,7 @@ object EVHardware {
fun initAudio ( context : Context ) {
fun initAudio ( context : Context ) {
// The caradapter bind only concerns the A9. On the old SDK, loudness goes through
// The caradapter bind only concerns the A9. On the old SDK, loudness goes through
// SmartSoundManager (initialised in the Katman 4 flow), so nothing to bind here.
// SmartSoundManager (initialised in the Layer 4 flow), so nothing to bind here.
if ( !is A9Sound ( ) ) return
if ( !is A9Sound ( ) ) return
if ( sAudioHelper ?. isBinderAlive == true ) return
if ( sAudioHelper ?. isBinderAlive == true ) return
if ( sAudioServiceConn != null ) return
if ( sAudioServiceConn != null ) return