Skip to content

Vehicle Data & VHAL

Vehicle HAL fundamentals

The single doorway between Android and the car, explained slowly — what a vehicle property actually is, why the interface is so small, and the three fields in a config that cause most bugs.

Intermediate11 minVHAL · HAL · AIDL

Everything the car knows reaches Android through one component. Not a collection of drivers, not a messaging system — a single interface offering a list of named values.

This page explains what that means, why it was designed that way, and the parts that trip people up.

The problem the VHAL solves#

Picture what Android would need to do without it.

A car has an engine computer that broadcasts speed as a two-byte number on a wire called , scaled by 0.01. A different car uses a three-byte number scaled by 0.1, on a different wire, from a different supplier. A third sends it over an entirely different network protocol.

Android cannot possibly know all of that. If it tried, every new vehicle would require changes to Android itself.

What "one property" actually is#

A is one named piece of information. That is genuinely all it is.

Examples of real properties:

PropertyWhat it holds
PERF_VEHICLE_SPEEDHow fast the car is going, in metres per second
GEAR_SELECTIONWhich gear is selected
HVAC_TEMPERATURE_SETThe temperature the driver asked for, per seat
DOOR_LOCKWhether a specific door is locked
INFO_VINThe vehicle identification number

Each property has:

  • an ID — a number identifying it
  • a type — is it a whole number, a decimal, text?
  • an area — does it apply to the whole car, or to one specific seat or door?
  • access rules — can Android read it, write it, or both?
  • a change mode — is it fixed, does it change occasionally, or continuously?

The entire interface#

Strip away the code-generation ceremony and the Vehicle HAL offers four things:

OperationIn plain terms
getAllPropConfigs()"What do you support, and what are the rules for each?"
getValues()"What is the value of these right now?"
setValues()"Please change these to this."
subscribe()"Tell me whenever these change."

That is it. Four operations, one list of values.

Config is the contract#

Here is the part that causes the most confusion for newcomers.

Before anyone reads a value, Android asks the VHAL: what properties do you have? The answer is a list of configs — and that answer is the real interface contract.

The fields of a property config that matter
parcelable VehiclePropConfig {
    int prop;                       // which property
    VehiclePropertyAccess access;   // READ, WRITE or READ_WRITE
    VehiclePropertyChangeMode changeMode;   // STATIC, ON_CHANGE or CONTINUOUS
    VehicleAreaConfig[] areaConfigs;        // which seats/doors, and valid ranges
    float minSampleRate;            // CONTINUOUS only
    float maxSampleRate;            // CONTINUOUS only
}

access — who is allowed to do what#

Three choices: READ, WRITE, or READ_WRITE.

The temptation is to declare everything READ_WRITE to keep options open. Do not. If the car owns a value and Android only observes it, declare READ.

changeMode — how the value behaves over time#

This one has three options and choosing wrong has real consequences.

STATIC — never changes after startup. The vehicle identification number. The number of doors. Android may read it once and cache it forever.

ON_CHANGE — announced whenever it moves. Gear position. Door open or closed. Most things are this.

CONTINUOUS — sampled at a rate, like a sensor. Speed. Engine RPM. Fuel level.

areaConfigs — which seat, and what range#

Many properties exist several times over: one per seat, one per door. The area config says which ones are supported and what values are valid for each.

This brings us to the thing that causes more VHAL bugs than anything else.

Area IDs are bits, not numbers#

An is a set of flags, not a counting number.

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
Seat area IDs are individual bitsThe driver's seat is bit 0, the front passenger is bit 2. They are not seat 1 and seat 2 — they are separate switches that can be on together.

The two ways to declare seats, and why they differ#

Two independently controlled seats
.areaConfigs = {
    { .areaId = ROW_1_LEFT,  .minInt32Value = 0, .maxInt32Value = 5 },
    { .areaId = ROW_1_RIGHT, .minInt32Value = 0, .maxInt32Value = 5 },
}
One value covering both seats
.areaConfigs = {
    { .areaId = ROW_1_LEFT | ROW_1_RIGHT, .minInt32Value = 0, .maxInt32Value = 5 },
}

Both are legal. They mean different things.

The first says: the driver and passenger each have their own setting, and you can change one without affecting the other.

The second says: there is one setting, shared, and both seats follow it.

What a value looks like#

When Android reads a property, or receives a change, it gets a VehiclePropValue:

VehiclePropValue
parcelable VehiclePropValue {
    long timestamp;                 // when this value was measured
    int areaId;                     // which seat/door it applies to
    int prop;                       // which property
    VehiclePropertyStatus status;   // AVAILABLE, UNAVAILABLE or ERROR
    RawPropValues value;            // the actual number, in one of several arrays
}

Two fields here cause real problems.

timestamp is not what most people assume#

It must be CLOCK_BOOTTIME, in nanoseconds — that is, nanoseconds since the device booted, not since 1970.

status is not decoration#

Three values, and they mean genuinely different things:

  • AVAILABLE — this is a real measurement, trust it.
  • UNAVAILABLE — the property exists, but there is no meaningful value right now. The sensor is warming up. The subsystem is asleep. The trailer is not attached.
  • ERROR — something failed.

The value union#

RawPropValues is not a clever tagged union — it is a struct containing several arrays, and the property's declared type tells you which one to read.

Declared typeRead from
BOOLEAN, INT32int32Values[0]
INT32_VECall of int32Values
FLOATfloatValues[0]
INT64int64Values[0]
BYTESbyteValues
STRINGstringValue
MIXEDseveral at once, described elsewhere

Where the framework meets it#

On the Android side, exactly one thing talks to the VHAL: CarPropertyService, inside .

It does four jobs:

  1. Asks for the config list at startup and remembers it.
  2. Checks permissions on every app request.
  3. Opens one subscription per property, no matter how many apps want it.
  4. Refuses anything the VHAL never declared.
Three apps subscribe. The HAL is asked once — at the fastest rate any of them wanted.App Awants 5 HzApp Bwants 1 HzApp Cwants 10 HzCar Servicemerges the requestsVehicle HALpublishes at 10 Hzevery event is delivered to all threeone careless app asking for 100 Hz raises the cost for the whole system
One subscription, many listenersCar Service merges every app's request into a single subscription to the HAL, at the fastest rate any of them asked for. This is why one badly written app affects everybody.

Read the reference implementation#

AOSP ships a complete, working VHAL that the emulator uses. It is the best documentation for this interface that exists.

automotive/vehicle/aidl/impl

Two files repay reading:

  • FakeVehicleHardware — how config and value storage fit together. This is the shape your own implementation will take.
  • DefaultVehicleHal — the request handling and subscription plumbing. You will reuse this rather than rewriting it.

Next#

Decoding a property ID by eye — which turns a lot of debugging into arithmetic.

References & further reading

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