Skip to content

Vehicle Data & VHAL

Anatomy of a vehicle property ID

That hex number is not a serial number — it is four facts packed into 32 bits. Learn to read one by eye and a large part of VHAL debugging becomes arithmetic instead of guesswork.

Intermediate8 minVHAL · Properties

You will spend a lot of time looking at lines like this:

0x25400501    intensity=3

To most people that hex number is meaningless — just an identifier to copy and paste. It is not. It contains four separate facts, and once you can read them, a category of debugging stops requiring documentation.

Why encode anything in the number at all#

A reasonable question: why not just number properties 1, 2, 3?

Because the number has to be understood by two sides that cannot ask each other questions. The car maker's and Google's Android are built by different companies, years apart, and shipped to a vehicle that will not be updated together.

Packing the essential facts into the ID means both sides can agree on what a property is without consulting a shared document that might be out of date.

The four fields#

A vehicle property ID is a packed bitfield, not an arbitrary integerbit 31bit 0Group4 bits0xf000_0000SYSTEM · VENDOR · BACKPORTEDArea4 bits0x0f00_0000GLOBAL, SEAT, DOOR…Type8 bits0x00ff_0000INT32, FLOAT, MIXED…Unique id16 bits0x0000_ffffyou allocate this0x2540_0501 = VENDOR | SEAT | INT32 | 0x0501
A property ID is a packed bitfield32 bits, four fields. The masks come straight from the AIDL enums in AOSP — this is not a convention, it is enforced by the platform.

Now each one in turn.

Field 1 — Group (4 bits)#

VehiclePropertyGroup.aidl
SYSTEM     = 0x10000000,   // Google defined it; every AAOS vehicle agrees
VENDOR     = 0x20000000,   // a car maker invented it
BACKPORTED = 0x30000000,   // a newer SYSTEM property, implemented early
MASK       = 0xf0000000,

Field 2 — Area (4 bits)#

VehicleArea.aidl
GLOBAL = 0x01000000,   // one value for the whole vehicle
WINDOW = 0x03000000,
MIRROR = 0x04000000,
SEAT   = 0x05000000,
DOOR   = 0x06000000,
WHEEL  = 0x07000000,
MASK   = 0x0f000000,

This says what kind of thing the property applies to. Speed is GLOBAL — a car has one speed. Seat heating is SEAT — there is one value per seat.

Field 3 — Type (8 bits)#

VehiclePropertyType.aidl
STRING    = 0x00100000,
BOOLEAN   = 0x00200000,
INT32     = 0x00400000,
INT32_VEC = 0x00410000,
INT64     = 0x00500000,
INT64_VEC = 0x00510000,
FLOAT     = 0x00600000,
FLOAT_VEC = 0x00610000,
BYTES     = 0x00700000,
MIXED     = 0x00e00000,
MASK      = 0x00ff0000,

This is what tells you which array in the value struct to read. Get it wrong and you read zero, silently.

Field 4 — Unique ID (16 bits)#

Sixteen bits — 65,536 possibilities — that you allocate yourself for vendor properties.

Decoding one by hand#

Take 0x25400501. Here is the whole process, step by step.

Decoding 0x25400501
Start:            0x25400501
 
Group:  0x25400501 & 0xf0000000  =  0x20000000  →  VENDOR
Area:   0x25400501 & 0x0f000000  =  0x05000000  →  SEAT
Type:   0x25400501 & 0x00ff0000  =  0x00400000  →  INT32
Id:     0x25400501 & 0x0000ffff  =  0x00000501  →  vendor id 0x0501

The shortcut for reading by eye#

You do not have to do the arithmetic. Look at the hex digits in position:

0x 2 5 4 0 0 5 0 1
   │ │ └─┴─ type
   │ └───── area
   └─────── group
           └─┴─┴─ unique id (last four digits)
  • First digit1 is SYSTEM, 2 is VENDOR, 3 is BACKPORTED.
  • Second digit1 GLOBAL, 5 SEAT, 6 DOOR, 3 WINDOW, 7 WHEEL.
  • Third and fourth — the type. 40 is INT32, 60 is FLOAT, 20 is BOOLEAN.
  • Last four — the unique id.

Composing one in code#

When you define a property, write the fields out. Do not paste a magic constant.

VendorProperties.kt
object VendorProperties {
 
    // The bit fields, straight from the AIDL enums.
    private const val GROUP_VENDOR = 0x2000_0000.toInt()
    private const val AREA_GLOBAL  = 0x0100_0000
    private const val AREA_SEAT    = 0x0500_0000
    private const val TYPE_INT32   = 0x0040_0000
 
    /** Seat massage intensity, 0-5, one value per seat. */
    const val SEAT_MASSAGE_INTENSITY =
        GROUP_VENDOR or AREA_SEAT or TYPE_INT32 or 0x0501   // 0x25400501
 
    /** Cabin scent level, 0-3, one value for the whole car. */
    const val CABIN_SCENT_LEVEL =
        GROUP_VENDOR or AREA_GLOBAL or TYPE_INT32 or 0x0502  // 0x21400502
}

Area IDs, one more time#

This is worth repeating in a second place, because it is the most common source of real defects.

Each seat is one bit — not a position in a listROW_1_LEFT0x000100000001driverROW_1_CENTER0x000200000010ROW_1_RIGHT0x000400000100front passengerROW_2_LEFT0x001000010000ROW_2_CENTER0x002000100000ROW_2_RIGHT0x004001000000bit 7bit 0both front seats = ROW_1_LEFT | ROW_1_RIGHT = 0x0005 — NOT 0x0002
Area IDs are individual bitsEach seat is one bit. They are not positions in a list, and they can be combined.
VehicleAreaSeat.aidl (excerpt)
ROW_1_LEFT   = 0x0001,
ROW_1_CENTER = 0x0002,
ROW_1_RIGHT  = 0x0004,
ROW_2_LEFT   = 0x0010,
ROW_2_CENTER = 0x0020,
ROW_2_RIGHT  = 0x0040,

GLOBAL properties still have an area#

A GLOBAL property has exactly one area, and its area ID is 0.

It still needs an area config entry if you want to declare valid minimum and maximum values. Omitting the config means "no declared range", and the platform will not range-check writes for you.

Before defining anything, check it does not exist#

There are several hundred SYSTEM properties already. Reusing one gives you, for free:

  • the framework's existing permission mapping
  • established unit conventions other apps already expect
  • and test coverage
  • documentation you did not have to write
Two places to search
# The HAL-side enum — the authoritative list of IDs
grep -i "SEAT_" hardware/interfaces/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleProperty.aidl
 
# The app-facing catalogue with Javadoc explaining each one
grep -i "SEAT_" packages/services/Car/car-lib/src/android/car/VehiclePropertyIds.java

Next#

Building one end to end — config, permission, security policy, and the app that finally reads it.

References & further reading

Code links target the main branch on cs.android.com. AOSP moves — if a path 404s, search the symbol instead.