User HAL Properties

Many current vehicle architectures contain multiple electronic control units (ECUs) outside the infotainment system that control ergonomics, such as seat settings and mirror adjustments. Based on current hardware and power architectures, many ECUs power up before the Android-based infotainment system is powered up. These ECUs can interface with an Android-based infotainment system through the Vehicle Hardware Abstraction Layer (VHAL).

Starting in Android 11, Android Automotive OS (AAOS) introduced a new set of properties on the VHAL for creating, switching, removing, and associating external accessories to identify Users. For example, these new properties enable a driver to pair-tie an external accessory, such as a key fob, to their Android User. Then, when the driver approaches the vehicle, an ECU wakes up and detects the key fob. This ECU indicates to the HAL which Android User the infotainment should start a boot up, which reduces the time a driver waits for their Android User to load.

Enable the User HAL

The User HAL properties must be explicitly enabled by ensuring the system property android.car.user_hal_enabled is set to true. (This can also be done in the car.mk file, so that it need not be manually set.) Check that user-hal_enabbled=true is enabled by dumping the UserHalService:

$ adb shell dumpsys car_service --hal UserHalService|grep enabled
user_hal_enabled=true

You can also check user-hal_enabbled by ussuingadb shell getprop android.car.user_hal_enabled or adb logcat CarServiceHelper *:s. If the property is disabled, a message like the following is displayed when system_server starts:

I CarServiceHelper: Not using User HAL

To manually enable user-hal_enabbled, set the android.car.user_hal_enabled system property and restart system_server:

$ adb shell setprop android.car.user_hal_enabled true
$ adb shell stop && adb shell start

The logcat output appears as follows:

I CarServiceHelper: User HAL enabled with timeout of 5000ms
D CarServiceHelper: Got result from HAL: OK
I CarServiceHelper: User HAL returned DEFAULT behavior

User HAL properties

User lifecycle properties

The following properties provide the HAL information for User lifecycle states, which enable User lifecycle synchronization between the Android system and an external ECU. These properties use a request and response protocol, in which the Android system makes a request by setting a property value and the HAL responds by issuing a property change event.

Note: When User HAL is supported, all of the following properties must be implemented.

HAL Property Description
INITIAL_USER_INFO
(READ/WRITE)
This property is called by the Android system to determine which Android User the system will start when the device boots or resumes from Suspend-to-RAM (STR). When called, the HAL must respond with one of these options:
  • The default behavior set by Android (switching to the last used User or creating a new User if this is the first boot).
  • Switch to an existing User.
  • Create a new User (with the optional properties of name, flags, system locale, and so on) and switch to that new User.

Note: If the HAL doesn't respond, the default behavior is to execute after a timeout period (five (5) seconds by default), which delays the boot. If the HAL does reply, but the Android system fails to execute the action (for example, if the maximum number of Users has been reached), the default behavior is used.

For example, by default, the Android system starts in the last active User upon boot. If a key fob for a different User is detected, the ECU overrides the HAL property and, during start up, the Android system switches to start in that specified User.

SWITCH_USER
(READ/WRITE)
This property is called when switching the active foreground Android User. The property can be called by either the Android system or by the HAL to request a User switch. The three workflows are:
  • Modern. Switch started from CarUserManager.
  • Legacy. Switch started from ActivityManager.
  • Vehicle. Called by the HAL to request a User switch.

The Modern workflow uses a two-phase commit approach to ensure the Android system and external ECU are synchronized. When Android initiates the switch:

  1. Check the HAL to determine if User can be switched.

    The HAL responds with SUCCESS or FAILURE, so that Android knows whether to proceed or not.

  2. Complete the Android User switch.

    Android sends an ANDROID_POST_SWITCH response to the HAL to indicate switch success or failure.

The HAL should wait until after the ANDROID_POST_SWITCH response to update its state to synchronize ECUs or update other HAL properties.

For example, while in motion, a driver attempts to switch Android Users in the infotainment UI. However, because car seat settings are tied to the Android User, the seat will move during the User switch. Thus, ECU controlling the seats does not confirm the switch, the HAL responds with a failure, and the Android User is not switched.

The Legacy workflow is a one-way call sent after the User is switched (so the HAL cannot block the switch). It's only called on boot (after the initial user switch) or for apps that calling ActivityManager.switchUser() instead of CarUserManager.switchUser(). The reference Settings and SystemUI apps already use the latter, but if an OEM provides their own Settings apps to switch Users, OEMs should change the usage.

For example, if an app uses ActivityManager.switchUser() to switch Users, then a one-way call is sent to the HAL to inform that a User switch has taken place.

The Vehicle workflow originates from the HAL, not from the Android system:

  1. The HAL requests a User switch.
  2. System completes Android User switch.
  3. Android sends an ANDROID_POST_SWITCH response to the HAL to indicate switch success or failure.

For example, Bob used Alice's key fob to open the car and the HAL replied to the INITIAL_USER_INFO request with Alice's User ID. Next, a biometric sensor ECU identified the driver as Bob, so the User HAL sent a SWITCH_USER request to switch users.

CREATE_USER
(READ/WRITE)
This property is called by the Android system when a new Android User is created (using the CarUserManager.createUser() API).

The HAL responds with SUCCESS or FAILURE. If the HAL responds with a failure, the Android system removes the User.

For example, a driver taps an infotainment UI icon to create a new Android User. This sends a request to the HAL and the rest of the vehicle subsystems. ECUs are informed of the newly created User. Other subsystems and ECUs then associate their internal user ID with the Android User ID.

REMOVE_USER
(WRITE only)
The Android system calls this property after an Android User is removed (with the CarUserManager.removeUser() API).

This is a one-way call — no response is expected from the HAL.

For example, a driver taps to remove an existing Android User in the infotainment UI. The HAL is informed and other vehicle subsystems and ECUs are informed of the User removal so they can remove their internal user ID.

Additional properties

The following are additional properties, unrelated to User lifecycle states. Each can be implemented without supporting the User HAL.

HAL Property Description
USER_IDENTIFICATION_ASSOCIATION
(READ/WRITE)
Use this property to associate any Android User with an identification mechanism, such as a key fob or phone. Use this same property to get or set associations.

For example, a driver taps an infotainment UI icon to associate the key fob used to open the vehicle (KEY_123) to the current active Android User (USER_11).

Helper libraries

All objects used in the request and response messages (such as UserInfo, InitialUserInfoRequest, InitialUSerInfoResponse, and so on) have a high level representation using C++ struct, but removal must be flattened into standard VehiclePropValue objects (see the examples below). For ease of development, a C++ helper library is provided in AOSP to automatically convert User HAL structs into a VehiclePropValue (and vice versa).

Examples

INITIAL_USER_INFO

Request example (on first boot)

VehiclePropValue { // flattened from InitialUserInfoRequest
prop: 299896583 // INITIAL_USER_INFO
prop.values.int32Values:
 [0] = 1 // Request ID
 [1] = 1 // InitialUserInfoRequestType.FIRST_BOOT
 [2] = 0 // user id of current user
 [3] = 1 // flags of current user (SYSTEM)
 [4] = 1 // number of existing users
 [5] = 0 // existingUser[0].id
 [6] = 1 // existingUser[0].flags
}

Response example (create Admin User)

VehiclePropValue { // flattened from InitialUserInfoResponse
prop: 299896583 // INITIAL_USER_INFO
prop.values.int32Values:
  [0] = 1      // Request ID (must match request)
  [1] = 2      // InitialUserInfoResponseAction.CREATE
  [2] = -10000 // user id (not used on CREATE)
  [3] = 8      // user flags (ADMIN)
prop.values.stringValue: "en-US||Car Owner" // User locale and User name
}

SWITCH_USER

The actual name of the classes and properties differs slightly but the overall workflow is the same, as illustrated below:

Workflow

Figure 1. User HAL Properties Workflow

Modern workflow request example

VehiclePropValue { // flattened from SwitchUserRequest
prop: 299896585 // SWITCH_USER
prop.values.int32Values:
 [0]     = 42    // Request ID
 [1]     = 2     // SwitchUserMessageType::ANDROID_SWITCH ("modern")
 [2,3]   = 11,0  // target user id (11) and flags (none in this case)
 [4,5]   = 10,8  // current user id (10) and flags (ADMIN)
 [6]     = 3     // number of existing users (0, 10, 11)
 [7,8]   = 0,1   // existingUser[0] (id=0, flags=SYSTEM)
 [9,10]  = 10,8  // existingUser[1] (id=10, flags=ADMIN)
 [11,12] = 11,0  // existingUser[2] (id=11, flags=NONE)
}

Modern workflow response example

VehiclePropValue { // flattened from SwitchUserResponse
prop: 299896584 // SWITCH_USER
prop.values.int32Values:
 [0] = 42        // Request ID (must match request)
 [1] = 3         // SwitchUserMessageType::VEHICLE_RESPONSE
 [2] = 1         // SwitchUserStatus::SUCCESS
}

Modern workflow post-switch response example

This response typically occurs when an Android switch succeeds:

VehiclePropValue { // flattened from SwitchUserRequest
prop: 299896584 // SWITCH_USER
prop.values.int32Values:
 [0]     = 42    // Request ID (must match "pre"-SWITCH_USER request )
 [1]     = 5     // SwitchUserMessageType::ANDROID_POST_SWITCH
 [2,3]   = 11,0  // target user id (11) and flags (none in this case)
 [4,5]   = 11,0  // current user id (11) and flags (none in this case)
 [6]     = 3     // number of existing users (0, 10, 11)
 [7,8]   = 0,1   // existingUser[0] (id=0, flags=SYSTEM)
 [9,10]  = 10,8  // existingUser[1] (id=10, flags=ADMIN)
 [11,12] = 11,0  // existingUser[2] (id=11, flags=NONE)
}

Modern workflow post-switch response

This response typically occurs when an Android switch fails:

VehiclePropValue { // flattened from SwitchUserRequest
prop: 299896584 // SWITCH_USER
prop.values.int32Values:
 [0]     = 42    // Request ID (must match "pre"-SWITCH_USER request )
 [1]     = 5     // SwitchUserMessageType::ANDROID_POST_SWITCH
 [2,3]   = 11,0  // target user id (11) and flags (none in this case)
 [4,5]   = 10,8  // current user id (10) and flags (ADMIN)
 [6]     = 3     // number of existing users (0, 10, 11)
 [7,8]   = 0,1   // existingUser[0] (id=0, flags=SYSTEM)
 [9,10]  = 10,8  // existingUser[1] (id=10, flags=ADMIN)
 [11,12] = 11,0  // existingUser[2] (id=11, flags=NONE)
}

Legacy workflow request example

VehiclePropValue { // flattened from SwitchUserRequest
prop: 299896584 // SWITCH_USER
prop.values.int32Values:
 [0]     = 2     // Request ID
 [1]     = 1     // SwitchUserMessageType::LEGACY_ANDROID_SWITCH
 [2,3]   = 10,8  // target user id (10) and flags (ADMIN)
 [4,5]   = 0,1   // current user id (0) and flags (SYSTEM)
 [6]     = 3     // number of existing users (0, 10, 11)
 [7,8]   = 0,1   // existingUser[0] (id=0, flags=SYSTEM)
 [9,10]  = 10,8  // existingUser[1] (id=10, flags=ADMIN)
 [11,12] = 11,0  // existingUser[2] (id=11, flags=NONE)
}

Vehicle workflow request example

VehiclePropValue { // flattened from SwitchUserRequest
prop: 299896584 // SWITCH_USER
prop.values.int32Values:
 [0]     = -108  // Request ID (must be negative)
 [1]     = 4     // SwitchUserMessageType::VEHICLE_REQUEST
 [2]     = 11    // target user id
}

Legacy workflow post-switch response

This response typically occurs when an Android switch succeeds:

VehiclePropValue { // flattened from SwitchUserRequest
prop: 299896584 // SWITCH_USER
prop.values.int32Values:
 [0]     = -108  // Request ID (must match from vehicle request )
 [1]     = 5     // SwitchUserMessageType::ANDROID_POST_SWITCH
 [2,3]   = 11,0  // target user id (11) and flags (none in this case)
 [4,5]   = 11,0  // current user id (11) and flags (none in this case)
 [6]     = 3     // number of existing users (0, 10, 11)
 [7,8]   = 0,1   // existingUser[0] (id=0, flags=SYSTEM)
 [9,10]  = 10,8  // existingUser[1] (id=10, flags=ADMIN)
 [11,12] = 11,0  // existingUser[2] (id=11, flags=NONE)
}

CREATE_USER

Request example

VehiclePropValue { // flattened from CreateUserRequest
prop: 299896585 // CREATE_USER
prop.values.int32Values:
 [0]      = 42  // Request ID
 [1,2]    = 11,6     // Android id of the created user and flags (id=11, flags=GUEST, EPHEMERAL)
 [3,4]    = 10,0  // current user id (10) and flags (none in this case)
 [5]      = 3  // number of existing users (0, 10, 11)
 [6,7]    = 0,1   // existingUser[0] (id=0, flags=SYSTEM)
 [8,9]    = 10,8  // existingUser[1] (id=10, flags=ADMIN)
 [10,11] = 11,6 // newUser[2] (id=11, flags=GUEST,EPHEMERAL)
}

Response example

VehiclePropValue { // flattened from CreateUserResponse
prop: 299896585 // CREATE_USER
prop.values.int32Values:
 [0] = 42        // Request ID (must match request)
 [1] = 3         // CreateUserStatus::SUCCESS
}

REMOVE_USER

Request example

VehiclePropValue { // flattened from RemoveUserRequest
prop: 299896586 // REMOVE_USER
prop.values.int32Values:
 [0]      = 42  // Request ID
 [1,2]    = 11,0     // Android id of the removed user and flags (none in this case)
 [3,4]    = 10,0  // current user id (10) and flags (none in this case)
 [5]      = 2  // number of existing users (0, 10)
 [6,7]    = 0,1   // existingUser[0] (id=0, flags=SYSTEM)
 [8,9]    = 10,8  // existingUser[1] (id=10, flags=ADMIN)
}

USER_IDENTIFICATION_ASSOCIATION

Set example (key fob associated with User 10)

VehiclePropValue { // flattened from UserIdentificationSetRequest
prop: 299896587 // USER_IDENTIFICATION_ASSOCIATION
prop.values.int32Values:
 [0]      = 43  // Request ID
 [1,2]    = 10,0     // Android id (10) and flags (none in this case)
 [3]    = 1  // number of associations being set
 [4]      = 1  // 1st type: UserIdentificationAssociationType::KEY_FOB
 [5]    = 1   // 1st value: UserIdentificationAssociationSetValue::ASSOCIATE_CURRENT_USER
}