| name | android14-release |
| description | Use when: implementing, debugging, or mocking Android 14 VHAL properties. Includes CarPropertyManager patterns, safe callbackFlow implementations, Car IPC lifecycle, and ADB commands. |
Android 14 Automotive OS Release Skill for VHAL
<system_directives>
- Absolute Truth Protocol: The
# Properties reference strictly defines supported properties. You MUST use the properties-reference.md file in the current folder to check the supported properties. Do not invent properties, IDs, or types. Unlisted properties are unsupported or vendor-specific (0x2...), do not hallucinate hex values for them.
- Framework Compliance: ALWAYS use
android.car.VehiclePropertyIds.<CONSTANT>. NEVER hardcode raw IDs into application code.
- Unit Validation: When manipulating float values (e.g.
PERF_VEHICLE_SPEED), remind the developer that VHAL units (e.g. m/s) must be converted for UI display (to km/h or mph).
- Hardware State Interlocks (Dependencies): Always evaluate the physical state of the car before attempting mutations. Do NOT mutate dependent properties (e.g.,
HVAC_FAN_SPEED) without verifying the prerequisite state (e.g., HVAC_POWER_ON == true). Enforce gear or speed checks (e.g., GEAR_SELECTION == PARK, PERF_VEHICLE_SPEED == 0f) for safety-critical actions like TRUNK_DOOR_MOVE or PARKING_BRAKE_ON.
- AOSP Permissions Awareness: When generating VHAL code, proactively comment which
android.car.permission.CAR_* manifest permission the developer needs to declare to use the requested property.
</system_directives>
Implementation Patterns (Best Practices)
<code_generation_rules>
Adhere to production-grade AAOS architecture: strictly handle IPC connection state (Car lifecycle) and avoid crashing reactive streams. Use a sealed interface to wrap sensor states securely.
</code_generation_rules>
1. Unified Result State (Sealed Interface)
Never close a Flow due to missing properties or permissions, as this crashes downstream UI collectors in Automotive apps. Yield states instead:
import android.car.hardware.CarPropertyValue
sealed interface VhalResult<out T> {
data class Success<T>(val property: CarPropertyValue<T>) : VhalResult<T>
data class Unavailable(val reason: String) : VhalResult<Nothing>
object PermissionDenied : VhalResult<Nothing>
}
1b. Lifecycle-Aware UI Collection
When collecting VHAL flows in an Activity or Fragment, ALWAYS use repeatOnLifecycle to prevent the UI from processing foreground sensor updates while backgrounded. This mitigates battery drain and avoids ANRs.
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
observeVehicleSpeedSafe(context).collect { result ->
when (result) {
is VhalResult.Success -> { }
is VhalResult.Unavailable -> { }
is VhalResult.PermissionDenied -> { }
}
}
}
}
2. IPC Lifecycle & Reactive Observation
Connecting to the VHAL requires strict Car.createCar lifecycle management. CarPropertyManager instances can become stale if the Car Service crashes. Always generate code that handles Car connection lifecycle and uses safe callbackFlow implementations.
import android.car.VehiclePropertyIds
import android.car.hardware.CarPropertyValue
import android.car.hardware.property.CarPropertyManager
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.buffer
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
fun observeVehicleSpeedSafe(carPropertyManager: CarPropertyManager): Flow<VhalResult<Float>> = callbackFlow {
val callback = object : CarPropertyManager.CarPropertyEventCallback {
override fun onChangeEvent(value: CarPropertyValue<*>) {
@Suppress("UNCHECKED_CAST")
trySend(VhalResult.Success(value as CarPropertyValue<Float>))
}
override fun onErrorEvent(propId: Int, zone: Int) {
trySend(VhalResult.Unavailable("Hardware error for zone $zone"))
}
}
try {
carPropertyManager.registerCallback(
callback,
VehiclePropertyIds.PERF_VEHICLE_SPEED,
CarPropertyManager.SENSOR_RATE_NORMAL
)
} catch (e: SecurityException) {
trySend(VhalResult.PermissionDenied)
close(e)
} catch (e: IllegalArgumentException) {
trySend(VhalResult.Unavailable("Property unsupported to observe"))
close(e)
}
awaitClose {
carPropertyManager.unregisterCallback(callback)
}
}.buffer(capacity = Channel.CONFLATED)
.flowOn(Dispatchers.IO)
3. Safe Synchronous Reads
import android.car.VehiclePropertyIds
import android.car.hardware.property.CarPropertyManager
fun readVehicleSpeedSafe(carPropertyManager: CarPropertyManager): Float {
return try {
val config = carPropertyManager.getCarPropertyConfig(VehiclePropertyIds.PERF_VEHICLE_SPEED)
if (config == null) return 0f
val propValue = carPropertyManager.getFloatProperty(
VehiclePropertyIds.PERF_VEHICLE_SPEED,
0
)
propValue
} catch (e: SecurityException) {
0f
} catch (e: IllegalArgumentException) {
0f
}
}
5. Actuation Latency & State Flickering (The "Echo" Race Condition)
Vehicle networks (CAN/LIN/Ethernet) have physical latency (100ms - 500ms). When you mutate a property via setProperty, the ECU takes time to actuate. During this window, CarPropertyEventCallback will often emit the old value, causing the UI to flicker (e.g., ON -> OFF -> ON).
When generating UI or ViewModel code that binds to VHAL properties, ALWAYS instruct the developer to implement Optimistic UI with a debounce:
ADB & VHAL Debugging Commands (Testing)
<adb_testing_instructions>
Proactively suggest using the Automotive OS Emulator Extended UI for testing sensor combinations. When ADB terminal commands are required, note that devices must have root access (adb root).
</adb_testing_instructions>
Inject/Spoof a VHAL Property Event:
adb root
adb shell cmd car_service inject-vhal-event 0x1120040A 0 1
Get Current VHAL Property Value:
adb root
adb shell cmd car_service get-property-value 0x11401020 0
List all Car Service Properties Available:
adb shell dumpsys car_service --services CarPropertyService --list
Vehicle Area ID Reference (Bitmasks)
<area_computation_protocol>
Many vehicle properties are zoned (e.g., they apply uniquely to specific doors, seats, or windows).
Dynamic Area IDs: Instead of hardcoding the static bitmasks (which is an anti-pattern as vehicles have different hardware components), ALWAYS instruct developers to dynamically query supported areas at runtime:
val supportedAreaIds = carPropertyManager.getCarPropertyConfig(VehiclePropertyIds.PROPERTY_NAME)?.areaIds
Never attempt to guess area IDs. Always query the vehicle hardware properties directly dynamically.
</area_computation_protocol>