Chapter 62: Device Form Factors¶
Android is not a single-device operating system. The same platform that powers phones also drives car dashboards, living-room televisions, wrist-worn wearables, and -- increasingly -- head-worn XR devices. Each form factor imposes radically different constraints. An instrument cluster must never crash, a TV must respond to a D-pad, and a watch must survive on a tiny battery for days. Yet all of them share the core Android framework.
This chapter dissects how AOSP adapts itself to its most divergent device form factors. These are Android Automotive OS (AAOS), Android TV, Wear OS, and the emerging Android XR surface. It then turns to the Software Defined Vehicle (SDV) platform that arrives in Android 17. SDV is a separate, vehicle-spanning stack. It runs a headless "Core" VM of vehicle service bundles beside AAOS. The stack has its own middleware, SOME/IP transport, and gateway into the AAOS image.
We will trace each vertical from the HAL layer through system services, window management, specialized UI shells, and the SDV service fabric. We quote real source code and reference actual file paths throughout.
62.1 Automotive (AAOS)¶
Android Automotive OS is the most architecturally ambitious form-factor adaptation in AOSP. Unlike Android Auto (a phone-projection protocol), AAOS runs as the primary head-unit operating system. It controls HVAC, reads vehicle telemetry, manages multiple displays for driver and passengers, handles power states tied to ignition, and enforces driver-distraction restrictions. The core automotive code lives under:
packages/services/Car/ -- CarService and all car-specific system services
packages/apps/Car/ -- Car launcher, SystemUI, Settings, Cluster
hardware/interfaces/automotive/ -- Vehicle HAL, EVS, audio control, CAN
62.1.1 CarService: The Central Automotive Daemon¶
CarService is a persistent Android service that bootstraps all car-specific subsystems. Its
lifecycle begins in CarServiceImpl, which is the actual Service subclass:
// packages/services/Car/service/src/com/android/car/CarServiceImpl.java
@Keep
public class CarServiceImpl extends ProxiedService {
public static final String CAR_SERVICE_INIT_TIMING_TAG = "CAR.InitTiming";
public static final int CAR_SERVICE_INIT_TIMING_MIN_DURATION_MS = 5;
private ICarImpl mICarImpl;
private VehicleStub mVehicle;
@Override
public void onCreate() {
// ...
mVehicle = VehicleStub.newVehicleStub();
mICarImpl = new ICarImpl.Builder()
.setServiceContext(this)
.setBuiltInContext(getBuiltinPackageContext())
.setVehicle(mVehicle)
.setSystemInterface(
SystemInterface.Builder.defaultSystemInterface(this).build())
.setVehicleInterfaceName(mVehicleInterfaceName)
.build();
mICarImpl.init();
ServiceManagerHelper.addService("car_service", mICarImpl);
SystemPropertiesHelper.set("boot.car_service_created", "1");
}
}
The key pattern: CarServiceImpl creates a VehicleStub (the connection to the Vehicle HAL).
Then it constructs ICarImpl, the actual ICar.Stub binder service that clients connect to.
The car_service name is published to ServiceManager, which makes it globally accessible.
ICarImpl is where every individual car subsystem is instantiated. The constructor uses a
CarServiceCreator helper that manages dependency injection and tracing:
// packages/services/Car/service/src/com/android/car/ICarImpl.java
public class ICarImpl extends ICar.Stub {
private final VehicleHal mHal;
private final CarPowerManagementService mCarPowerManagementService;
private final CarOccupantZoneService mCarOccupantZoneService;
private final FixedActivityService mFixedActivityService;
private final CarAudioService mCarAudioService;
private final CarPropertyService mCarPropertyService;
private final CarDrivingStateService mCarDrivingStateService;
private final CarUxRestrictionsManagerService mCarUXRestrictionsService;
private final InstrumentClusterService mInstrumentClusterService;
private final ClusterHomeService mClusterHomeService;
private final CarEvsService mCarEvsService;
private final GarageModeService mGarageModeService;
private final CarBluetoothService mCarBluetoothService;
private final CarUserService mCarUserService;
private final CarWatchdogService mCarWatchdogService;
private final CarTelemetryService mCarTelemetryService;
private final CarActivityService mCarActivityService;
private final CarOccupantConnectionService mCarOccupantConnectionService;
private final CarRemoteDeviceService mCarRemoteDeviceService;
private final CarRemoteAccessService mCarRemoteAccessService;
// ... approximately 40 services total
}
The initialization ordering is critical. The HAL must be up before property services, which must
be up before driving-state services, which must be up before UX-restrictions. The
CarServiceCreator.createService() method makes sure each service is added to a global list in
construction order:
// packages/services/Car/service/src/com/android/car/ICarImpl.java
private ICarImpl(Builder builder) {
// ...
mHal = constructWithTrace(t, VehicleHal.class,
() -> new VehicleHal(mContext, builder.mVehicle), allServices);
mCarPropertyService = carServiceCreator.createService(
CarPropertyService.class,
() -> new CarPropertyService.Builder()
.setContext(mContext)
.setPropertyHalService(mHal.getPropertyHal())
.build());
mCarDrivingStateService = carServiceCreator.createService(
CarDrivingStateService.class,
() -> new CarDrivingStateService(mContext, mCarPropertyService));
mCarOccupantZoneService = carServiceCreator.createService(
CarOccupantZoneService.class,
() -> new CarOccupantZoneService(mContext));
mCarUXRestrictionsService = carServiceCreator.createService(
CarUxRestrictionsManagerService.class,
() -> new CarUxRestrictionsManagerService(mContext, mCarDrivingStateService,
mCarPropertyService, mCarOccupantZoneService));
// ...
}
The following diagram shows the overall CarService architecture:
graph TB
subgraph "CarService Process"
CSI["CarServiceImpl<br/>extends ProxiedService"]
ICI["ICarImpl<br/>extends ICar.Stub"]
subgraph "Vehicle Abstraction"
VS[VehicleStub]
AVS[AidlVehicleStub]
HVS[HidlVehicleStub]
FVS[FakeVehicleStub]
end
subgraph "HAL Layer"
VH[VehicleHal]
PHal[PowerHalService]
PropHal[PropertyHalService]
IHal[InputHalService]
UHal[UserHalService]
CHal[ClusterHalService]
EHal[EvsHalService]
end
subgraph "Car Services"
CPS[CarPropertyService]
CPMS[CarPowerManagementService]
COZS[CarOccupantZoneService]
CDSS[CarDrivingStateService]
CUXS[CarUxRestrictionsService]
CAS[CarAudioService]
CBS[CarBluetoothService]
CUS[CarUserService]
FAS[FixedActivityService]
GMS[GarageModeService]
ICS[InstrumentClusterService]
CHS[ClusterHomeService]
CEVS[CarEvsService]
CTS[CarTelemetryService]
CWS[CarWatchdogService]
CACS[CarActivityService]
end
end
subgraph "Hardware"
VHAL["Vehicle HAL<br/>hardware/interfaces/automotive/vehicle/"]
EVS_HAL["EVS HAL<br/>Camera subsystem"]
AC_HAL[AudioControl HAL]
end
CSI --> ICI
ICI --> VS
VS --> AVS
VS --> HVS
VS --> FVS
ICI --> VH
VH --> PHal
VH --> PropHal
VH --> IHal
VH --> UHal
VH --> CHal
VH --> EHal
VH --> VS
CPS --> PropHal
CPMS --> PHal
CHS --> CHal
CEVS --> EHal
AVS --> VHAL
HVS --> VHAL
CEVS --> EVS_HAL
CAS --> AC_HAL
62.1.2 The Vehicle HAL¶
The Vehicle HAL is the boundary between Android and the vehicle's electronic control units (ECUs).
It defines a property-based abstraction. Every piece of vehicle data (speed, gear, HVAC
temperature, door lock status) is exposed as a VehicleProperty. Each property has a property ID, area ID,
value type, and change mode.
The AIDL interface is defined at:
hardware/interfaces/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl
@VintfStability
interface IVehicle {
const long INVALID_MEMORY_ID = 0;
const int MAX_SHARED_MEMORY_FILES_PER_CLIENT = 3;
VehiclePropConfigs getAllPropConfigs();
VehiclePropConfigs getPropConfigs(in int[] props);
void getValues(IVehicleCallback callback, in GetValueRequests requests);
void setValues(IVehicleCallback callback, in SetValueRequests requests);
void subscribe(in IVehicleCallback callback, in SubscribeOptions[] options,
int maxSharedMemoryFileCount);
void unsubscribe(in IVehicleCallback callback, in int[] propIds);
void returnSharedMemory(in IVehicleCallback callback, long sharedMemoryId);
SupportedValuesListResults getSupportedValuesLists(in List<PropIdAreaId> propIdAreaIds);
MinMaxSupportedValueResults getMinMaxSupportedValue(in List<PropIdAreaId> propIdAreaIds);
void registerSupportedValueChangeCallback(
in IVehicleCallback callback, in List<PropIdAreaId> propIdAreaIds);
void unregisterSupportedValueChangeCallback(
in IVehicleCallback callback, in List<PropIdAreaId> propIdAreaIds);
}
Key design decisions in the Vehicle HAL:
-
Asynchronous operations:
getValues()andsetValues()use callbacks, not blocking returns. This prevents the car service from stalling on slow ECU communication. -
Shared memory for large payloads: The
returnSharedMemorymechanism allows efficient transfer of bulk sensor data without repeated binder parceling. -
Property change modes: Properties are either
STATIC(never change, like VIN number),ON_CHANGE(event-driven, like door state), orCONTINUOUS(polled, like vehicle speed). -
Area IDs: Properties can be scoped to vehicle areas. HVAC temperature might have different area IDs for driver-side and passenger-side. Seat-related properties use
VehicleAreaSeatbit masks.
On the Java side, VehicleHal dispatches incoming property events to specialized
HalServiceBase implementations:
// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
public class VehicleHal implements VehicleHalCallback, CarSystemService {
private final PowerHalService mPowerHal;
private final PropertyHalService mPropertyHal;
private final InputHalService mInputHal;
private final VmsHalService mVmsHal;
private final UserHalService mUserHal;
private final DiagnosticHalService mDiagnosticHal;
private final ClusterHalService mClusterHalService;
private final EvsHalService mEvsHal;
private final TimeHalService mTimeHalService;
// ...
}
Each HalServiceBase subscribes to the VHAL properties it cares about. It converts raw
HalPropValue events into higher-level data. Then it routes that data to the corresponding
Car*Service.
The event dispatch mechanism uses specialized DispatchList classes that route events
to the correct HalServiceBase on its dedicated executor:
// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
private final class HalEventsDispatchList extends
DispatchList<HalServiceBase, HalPropValue> {
@Override
protected void dispatchToClient(HalServiceBase service,
List<HalPropValue> events) {
var eventsCopy = List.copyOf(events);
var executor = getExecutorForService(service);
if (executor == null) return;
executor.execute(() -> {
service.onHalEvents(eventsCopy);
});
}
}
private final class PropertySetErrorDispatchList extends
DispatchList<HalServiceBase, VehiclePropError> {
@Override
protected void dispatchToClient(HalServiceBase service,
List<VehiclePropError> events) {
var eventsCopy = List.copyOf(events);
var executor = getExecutorForService(service);
if (executor == null) return;
executor.execute(() -> {
service.onPropertySetError(eventsCopy);
});
}
}
The subscription system tracks rates and variable update rate (VUR) settings per property-area pair:
// packages/services/Car/service/src/com/android/car/hal/VehicleHal.java
/* package */ static final class HalSubscribeOptions {
private final int mHalPropId;
private final int[] mAreaIds;
private final float mUpdateRateHz;
private final boolean mEnableVariableUpdateRate;
private final float mResolution;
HalSubscribeOptions(int halPropId, int[] areaIds, float updateRateHz,
boolean enableVariableUpdateRate, float resolution) {
mHalPropId = halPropId;
mAreaIds = areaIds;
mUpdateRateHz = updateRateHz;
mEnableVariableUpdateRate = enableVariableUpdateRate;
mResolution = resolution;
}
}
Variable Update Rate (VUR) is an important optimization. When enabled, the VHAL only delivers events when the property value actually changes by more than the specified resolution. This holds even if the polling rate would trigger more frequent deliveries. This reduces CPU and binder overhead for high-frequency properties like vehicle speed.
The VehicleStub abstraction supports both AIDL and legacy HIDL interfaces:
// packages/services/Car/service/src/com/android/car/VehicleStub.java
public abstract class VehicleStub {
public interface SubscriptionClient {
void subscribe(SubscribeOptions[] options)
throws RemoteException, ServiceSpecificException;
void unsubscribe(int prop)
throws RemoteException, ServiceSpecificException;
void registerSupportedValuesChange(List<PropIdAreaId> propIdAreaIds);
void unregisterSupportedValuesChange(List<PropIdAreaId> propIdAreaIds);
}
// ...
}
The concrete implementations AidlVehicleStub and HidlVehicleStub handle protocol-specific
details. A FakeVehicleStub (a VehicleStubWrapper) backs the fake-VHAL mode for testing
without real hardware; a separate SimulationVehicleStub is created by VehicleHal for
property simulation.
graph LR
subgraph "VehicleStub Hierarchy"
VS["VehicleStub<br/>abstract"]
AVS[AidlVehicleStub]
HVS[HidlVehicleStub]
FVS[FakeVehicleStub]
end
subgraph "VHAL Property Model"
VP["VehicleProperty<br/>ID + AreaID"]
VT["Value Types<br/>INT32, FLOAT, STRING,<br/>INT32_VEC, MIXED"]
CM["Change Modes<br/>STATIC, ON_CHANGE,<br/>CONTINUOUS"]
AT["Area Types<br/>GLOBAL, WINDOW,<br/>MIRROR, SEAT,<br/>DOOR, WHEEL"]
end
VS --> AVS
VS --> HVS
VS --> FVS
VP --> VT
VP --> CM
VP --> AT
62.1.3 Car Property System¶
The car property system is the primary interface for applications to read and write vehicle data.
CarPropertyService exposes properties to apps through CarPropertyManager:
// packages/services/Car/service/src/com/android/car/CarPropertyService.java
// (Referenced via ICarImpl constructor)
mCarPropertyService = carServiceCreator.createService(
CarPropertyService.class,
() -> new CarPropertyService.Builder()
.setContext(mContext)
.setPropertyHalService(mHal.getPropertyHal())
.build());
The property flow from app to hardware:
sequenceDiagram
participant App as Application
participant CPM as CarPropertyManager
participant CPS as CarPropertyService
participant PHS as PropertyHalService
participant VH as VehicleHal
participant VS as VehicleStub
participant VHAL as Vehicle HAL (Native)
participant ECU as Vehicle ECU
App->>CPM: getProperty(PERF_VEHICLE_SPEED, GLOBAL_AREA)
CPM->>CPS: getProperty() [Binder IPC]
CPS->>PHS: getProperty()
PHS->>VH: get(halPropValue)
VH->>VS: getValues(callback, requests)
VS->>VHAL: getValues() [AIDL/HIDL]
VHAL->>ECU: CAN/LIN bus read
ECU-->>VHAL: Raw value
VHAL-->>VS: GetValueResult
VS-->>VH: Callback with result
VH-->>PHS: HalPropValue
PHS-->>CPS: CarPropertyValue
CPS-->>CPM: CarPropertyValue [Binder]
CPM-->>App: CarPropertyValue<Float>
For subscription-based access (event-driven properties like speed or gear), apps register callbacks that fire when the HAL pushes new values:
sequenceDiagram
participant App as Application
participant CPM as CarPropertyManager
participant CPS as CarPropertyService
participant PHS as PropertyHalService
participant VH as VehicleHal
participant VHAL as Vehicle HAL
App->>CPM: registerCallback(PERF_VEHICLE_SPEED, rate)
CPM->>CPS: registerListener()
CPS->>PHS: subscribeProperty()
PHS->>VH: subscribeProperty()
VH->>VHAL: subscribe(options)
Note over VHAL: Vehicle speed changes
VHAL-->>VH: onPropertyEvent()
VH-->>PHS: onHalEvents()
PHS-->>CPS: onPropertyChange()
CPS-->>CPM: onChangeEvent [Binder callback]
CPM-->>App: onChangeEvent(CarPropertyValue)
62.1.4 Occupant Zones¶
Multi-zone vehicles have separate displays and user sessions for different seating positions.
CarOccupantZoneService manages the mapping between physical seat positions, displays, Android
users, and input devices.
// packages/services/Car/service/src/com/android/car/CarOccupantZoneService.java
public final class CarOccupantZoneService extends ICarOccupantZone.Stub
implements CarServiceBase {
public static final class DisplayConfig {
public final int displayType;
public final int occupantZoneId;
public final int[] inputTypes;
DisplayConfig(int displayType, int occupantZoneId, IntArray inputTypes) {
this.displayType = displayType;
this.occupantZoneId = occupantZoneId;
this.inputTypes = inputTypes == null
? EMPTY_INPUT_SUPPORT_TYPES : inputTypes.toArray();
}
}
@VisibleForTesting
static class OccupantConfig {
public int userId = CarOccupantZoneManager.INVALID_USER_ID;
public final ArrayList<DisplayInfo> displayInfos = new ArrayList<>();
public int audioZoneId = CarAudioManager.INVALID_AUDIO_ZONE;
}
/** key : zoneId */
@GuardedBy("mLock")
private final SparseArray<OccupantConfig> mActiveOccupantConfigs = new SparseArray<>();
@GuardedBy("mLock")
private int mDriverZoneId = OccupantZoneInfo.INVALID_ZONE_ID;
}
The occupant zone model has several key concepts:
-
OccupantZoneInfo: Represents a physical seating position (driver, front passenger, rear left, rear right, etc.), each identified by a unique zone ID.
-
DisplayConfig: Maps a display type (main, instrument cluster, HUD) to an occupant zone and specifies what input types that display supports.
-
OccupantConfig: The runtime state linking a zone to an Android user ID, a set of displays, and an audio zone.
graph TB
subgraph "Occupant Zone Model"
subgraph "Zone 0: Driver"
Z0["OccupantZoneInfo<br/>zoneId=0, DRIVER"]
D0["Display: Main<br/>displayType=MAIN"]
D1["Display: Cluster<br/>displayType=INSTRUMENT_CLUSTER"]
U0["User: owner (userId=0)"]
A0[Audio Zone 0]
end
subgraph "Zone 1: Front Passenger"
Z1["OccupantZoneInfo<br/>zoneId=1, FRONT_PASSENGER"]
D2["Display: Passenger<br/>displayType=MAIN"]
U1["User: passenger (userId=10)"]
A1[Audio Zone 1]
end
subgraph "Zone 2: Rear Left"
Z2["OccupantZoneInfo<br/>zoneId=2, REAR_LEFT"]
D3["Display: Rear Left<br/>displayType=MAIN"]
U2["User: child (userId=11)"]
A2[Audio Zone 2]
end
end
Z0 --> D0
Z0 --> D1
Z0 --> U0
Z0 --> A0
Z1 --> D2
Z1 --> U1
Z1 --> A1
Z2 --> D3
Z2 --> U2
Z2 --> A2
The service listens for display changes and user lifecycle events to dynamically reconfigure zones:
// packages/services/Car/service/src/com/android/car/CarOccupantZoneService.java
@VisibleForTesting
final UserLifecycleListener mUserLifecycleListener = event -> {
if (DBG) Slogf.d(TAG, "onEvent(%s)", event);
boolean isUserSwitching =
(event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
handleUserChange(isUserSwitching);
};
@VisibleForTesting
final DisplayManager.DisplayListener mDisplayListener =
new DisplayManager.DisplayListener() {
@Override
public void onDisplayAdded(int displayId) {
handleDisplayChange(displayId);
}
@Override
public void onDisplayRemoved(int displayId) {
handleDisplayChange(displayId);
}
@Override
public void onDisplayChanged(int displayId) {
// nothing to do
}
};
When a display is hotplugged (a rear-seat entertainment screen is connected, for example), the service re-evaluates the zone-to-display mapping and notifies all registered callbacks.
The init() method shows the full initialization sequence:
// packages/services/Car/service/src/com/android/car/CarOccupantZoneService.java
@Override
public void init() {
Car car = new Car(mContext, /* service= */null, /* handler= */ null);
CarInfoManager infoManager = new CarInfoManager(car,
CarLocalServices.getService(CarPropertyService.class));
int driverSeat = infoManager.getDriverSeat();
synchronized (mLock) {
mDriverSeat = driverSeat;
parseOccupantZoneConfigsLocked(); // Read zone config from RRO
parseDisplayConfigsLocked(); // Map displays to zones
handleActiveDisplaysLocked(); // Activate connected displays
handleAudioZoneChangesLocked(); // Set up audio routing
handleUserChangesLocked(); // Assign users to zones
}
mCarUserService = CarLocalServices.getService(CarUserService.class);
UserLifecycleEventFilter userEventFilter =
new UserLifecycleEventFilter.Builder()
.addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
.addEventType(USER_LIFECYCLE_EVENT_TYPE_STOPPING)
.build();
mCarUserService.addUserLifecycleListener(userEventFilter,
mUserLifecycleListener);
mDisplayManager.registerDisplayListener(mDisplayListener,
new Handler(Looper.getMainLooper()));
CarServiceHelperWrapper.getInstance().runOnConnection(
() -> doSyncWithCarServiceHelper(
/* updateDisplay= */ true, /* updateUser= */ true));
}
The occupant zone configuration is read from the RRO config resource
config_occupant_zones. If this resource is empty, the service automatically creates a
single driver zone. The configuration specifies seat positions, display types, and input
support for each zone.
The profile user assignment feature (mEnableProfileUserAssignmentForMultiDisplay) allows
different Android user profiles to be assigned to different zones. A child profile might
be assigned to the rear-seat display while the primary user controls the driver display.
This requires both the enableProfileUserAssignmentForMultiDisplay config boolean and the
FEATURE_MANAGED_USERS system feature.
62.1.5 Instrument Cluster¶
The instrument cluster is the display behind the steering wheel. AAOS supports rendering
navigation, phone-call, and media information on this display. The
InstrumentClusterService binds to a vendor-provided rendering service:
// packages/services/Car/service/src/com/android/car/cluster/InstrumentClusterService.java
@SystemApi
public class InstrumentClusterService implements CarServiceBase, KeyEventListener,
ClusterNavigationService.ClusterNavigationServiceCallback {
private static final long RENDERER_SERVICE_WAIT_TIMEOUT_MS = 5000;
private static final long RENDERER_WAIT_MAX_RETRY = 2;
private final Context mContext;
private final CarInputService mCarInputService;
private final ClusterNavigationService mClusterNavigationService;
// ...
@GuardedBy("mLock")
private IInstrumentCluster mRendererService;
}
The newer ClusterHomeService provides a more modern approach. The cluster display runs
a full Android activity (the "Cluster Home" app). Content is rendered via
ClusterHalService, which communicates cluster state through VHAL properties.
The sample cluster application lives at:
packages/apps/Car/Cluster/ClusterOsDouble/
This ClusterOsDouble acts as a testing app for the Cluster2 framework, handling Cluster VHAL properties and performing Cluster OS role functions. It includes:
ClusterOsDoubleActivity-- Main activity displaying cluster informationNavStateController-- Handles navigation state displayClusterViewModel-- ViewModel for cluster data- Sensor integration classes for vehicle telemetry visualization
The cluster rendering flow:
sequenceDiagram
participant NavApp as Navigation App
participant CNS as ClusterNavigationService
participant ICS as InstrumentClusterService
participant CHS as ClusterHalService
participant CHomeS as ClusterHomeService
participant Renderer as Cluster Renderer (Vendor App)
participant ClusterDisplay as Instrument Cluster Display
NavApp->>CNS: sendNavigationState(bundle)
CNS->>ICS: onNavigationStateChanged()
ICS->>Renderer: IInstrumentClusterNavigation.onNavigationStateChanged() (via getNavigationBinder())
Renderer->>ClusterDisplay: Render navigation turn card
Note over CHS,CHomeS: Cluster2 state changes via VHAL
CHS-->>CHomeS: onSwitchUi()
CHomeS-->>ClusterDisplay: onClusterStateChanged() to registered callbacks
62.1.6 FixedActivityService¶
In automotive, certain displays must always show specific activities. The driver's instrument
cluster must always show the cluster UI; a rear-seat entertainment screen might always show a
media player. FixedActivityService guarantees that a designated activity is always in the
foreground on a given display. It re-launches the activity if it crashes or is covered.
The service uses multiple monitoring mechanisms to detect when the fixed activity is no longer visible and needs to be relaunched:
// packages/services/Car/service/src/com/android/car/am/FixedActivityService.java
/**
* Monitors top activity for a display and guarantee activity in fixed mode is
* re-launched if it has crashed or gone to background for whatever reason.
*
* This component also monitors the update of the target package and re-launch
* it once update is complete.
*/
public final class FixedActivityService implements CarServiceBase {
private static final long RECHECK_INTERVAL_MS = 500;
private static final int MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY = 5;
private static final long CRASH_FORGET_INTERVAL_MS = 2 * 60 * 1000; // 2 mins
private static class RunningActivityInfo {
@NonNull public final Intent intent;
@NonNull public final Bundle activityOptions;
@UserIdInt public final int userId;
public boolean isVisible;
public boolean isStarted;
public long lastLaunchTimeMs;
public int consecutiveRetries;
public int taskId = INVALID_TASK_ID;
public int previousTaskId = INVALID_TASK_ID;
public boolean inBackground;
}
}
The service maintains RunningActivityInfo records per display. Every 500ms it rechecks
whether the expected activity is on top. If the activity has crashed more than 5 times
consecutively, it backs off. After 2 minutes without a crash, the consecutive-retry counter
resets.
The monitoring infrastructure is comprehensive. FixedActivityService registers four
different event sources to detect when intervention is needed:
// packages/services/Car/service/src/com/android/car/am/FixedActivityService.java
// 1. Process lifecycle monitoring
private final ProcessObserverCallback mProcessObserver = new ProcessObserverCallback() {
@Override
public void onForegroundActivitiesChanged(int pid, int uid,
boolean foregroundActivities) {
launchIfNecessary();
}
@Override
public void onProcessDied(int pid, int uid) {
launchIfNecessary();
}
};
// 2. Package update monitoring
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
|| Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
// Reset crash counter and relaunch
}
}
};
// 3. Display state monitoring
private final DisplayListener mDisplayListener = new DisplayListener() {
@Override
public void onDisplayChanged(int displayId) {
launchForDisplay(displayId);
}
};
// 4. Power state monitoring
private final CarPowerManager.CarPowerStateListener mCarPowerStateListener =
(state) -> {
if (state != CarPowerManager.STATE_ON) return;
// Reset crash counters and relaunch on power on
launchIfNecessary();
};
The mRunningActivities SparseArray maps display IDs to their RunningActivityInfo. The
default capacity is 1, optimized for the common case of a single instrument cluster:
// key: displayId
@GuardedBy("mLock")
private final SparseArray<RunningActivityInfo> mRunningActivities =
new SparseArray<>(/* capacity= */ 1); // default to one cluster only case
When launchIfNecessary() fires, it checks each monitored display, compares the current top
activity against the expected activity, and calls startActivity() if they differ. The
activityOptions Bundle in RunningActivityInfo contains the display ID targeting
information.
stateDiagram-v2
[*] --> Monitoring: startFixedActivityModeForDisplay
Monitoring --> ActivityVisible: Top activity matches
ActivityVisible --> ActivityGone: Activity crashes/backgrounded
ActivityGone --> Relaunching: recheck timer fires 500ms
Relaunching --> ActivityVisible: startActivity succeeds
Relaunching --> CrashBackoff: consecutiveRetries > 5
CrashBackoff --> Relaunching: CRASH_FORGET_INTERVAL 2min
Monitoring --> [*]: stopFixedActivityMode
62.1.7 CarActivityService¶
CarActivityService manages activity placement across multiple displays, handles launch-on-
display routing, and provides the CarSystemUIProxy interface that lets Car SystemUI control
task organization:
// packages/services/Car/service/src/com/android/car/am/CarActivityService.java
public class CarActivityService extends ICarActivityService.Stub
implements CarServiceBase {
// Manages per-display task placement, blocking activities for
// distraction optimization, and SystemUI proxy registration
}
This service works closely with CarPackageManagerService to enforce which activities are
allowed on which displays based on driving state and UX restrictions.
The CarActivityService provides several critical capabilities:
-
Display-specific activity launching: When an app targets a specific occupant zone, the service routes the activity to the correct display using
ActivityOptions: -
SystemUI proxy registration: Car SystemUI registers itself as a proxy through
ICarSystemUIProxy, allowing the service to control task presentation: -
Blocking activity management: When a non-distraction-optimized activity attempts to display while driving, the service intercepts it. The service replaces it with a blocking activity that shows a safety message. The display ID is passed via:
-
Task mirroring and movement: Tasks can be moved between displays (e.g., moving a passenger's navigation session to the driver's display).
sequenceDiagram
participant App as Application
participant AMS as ActivityManagerService
participant CAS as CarActivityService
participant CPMS2 as CarPackageManagerService
participant CUXS2 as CarUxRestrictionsService
participant Display as Target Display
App->>AMS: startActivity(intent, displayId)
AMS->>CAS: Check display routing
CAS->>CPMS2: isActivityAllowed(activity, displayId)?
CPMS2->>CUXS2: getCurrentRestrictions(displayId)
alt Activity is distraction-optimized
CUXS2-->>CPMS2: Restrictions OK
CPMS2-->>CAS: Allowed
CAS-->>AMS: Proceed
AMS->>Display: Launch activity
else Activity is NOT distraction-optimized AND driving
CUXS2-->>CPMS2: Restrictions ACTIVE
CPMS2-->>CAS: Blocked
CAS->>AMS: Launch blocking activity instead
AMS->>Display: Show blocking UI
end
62.1.8 Car Power Management¶
Automotive power management differs fundamentally from mobile. A car's power state is driven
by the vehicle's ignition system, not a user pressing a power button. The
CarPowerManagementService manages transitions between power states:
// packages/services/Car/service/src/com/android/car/power/CarPowerManagementService.java
public class CarPowerManagementService extends ICarPower.Stub implements
CarServiceBase, PowerHalService.PowerEventListener {
// Power state constants
private static final int ACTION_ON_FINISH_SHUTDOWN = 0;
private static final int ACTION_ON_FINISH_DEEP_SLEEP = 1;
private static final int ACTION_ON_FINISH_HIBERNATION = 2;
// Suspend retry with exponential backoff
private static final long INITIAL_SUSPEND_RETRY_INTERVAL_MS = 10;
private static final long MAX_RETRY_INTERVAL_MS = 100;
// Garage mode constraints
private static final int MIN_GARAGE_MODE_DURATION_MS = 15 * 60 * 1000; // 15 min
}
The automotive power state machine:
stateDiagram-v2
[*] --> WaitForVhal: Boot
WaitForVhal --> On: VHAL ready
On --> ShutdownPrepare: AP_POWER_STATE_REQ = SHUTDOWN_PREPARE
On --> PreShutdownPrepare: PRE_SHUTDOWN_PREPARE
PreShutdownPrepare --> ShutdownPrepare: Listeners complete
ShutdownPrepare --> GarageMode: canPostpone=true
ShutdownPrepare --> Shutdown: canPostpone=false
GarageMode --> DeepSleep: Garage tasks complete + DEEP_SLEEP requested
GarageMode --> Hibernation: HIBERNATION requested
GarageMode --> Shutdown: SHUTDOWN requested
DeepSleep --> WaitForVhal: Resume from suspend
Hibernation --> WaitForVhal: Resume from hibernate
Shutdown --> [*]: Power off
note right of GarageMode
Runs deferred tasks:
App updates,
Data sync,
Optimization jobs
end note
The power policy system controls which hardware components are powered on in each state. For example, during deep sleep, displays and audio might be off, but the cellular modem stays on for remote access:
// packages/services/Car/service/src/com/android/car/power/CarPowerManagementService.java
private static final String WIFI_STATE_FILENAME = "wifi_state";
private static final String TETHERING_STATE_FILENAME = "tethering_state";
private static final String COMPONENT_STATE_MODIFIED = "forcibly_disabled";
private static final String COMPONENT_STATE_ORIGINAL = "original";
Power policy definitions interact with the native power policy daemon at:
android.frameworks.automotive.powerpolicy.internal.ICarPowerPolicySystemNotification
62.1.9 Garage Mode¶
Garage Mode is the period after the driver turns off the ignition but before the vehicle fully shuts down. During this window, AAOS performs maintenance tasks:
// packages/services/Car/service/src/com/android/car/garagemode/GarageModeService.java
/**
* Main service container for car Garage Mode.
* Garage Mode enables idle time in cars.
*/
public class GarageModeService implements CarServiceBase {
private final GarageModeController mController;
// ...
}
The GarageModeController is the brain of garage mode, implementing ICarPowerStateListener
to respond to power state transitions:
// packages/services/Car/service/src/com/android/car/garagemode/GarageModeController.java
public class GarageModeController extends ICarPowerStateListener.Stub {
private final GarageMode mGarageMode;
private CarPowerManagementService mCarPowerService;
public void init() {
mCarPowerService = CarLocalServices.getService(
CarPowerManagementService.class);
mCarPowerService.registerInternalListener(GarageModeController.this);
mGarageMode.init();
}
@Override
public void onStateChanged(int state, long expirationTimeMs) {
switch (state) {
case CarPowerManager.STATE_SHUTDOWN_CANCELLED:
resetGarageMode(null);
break;
case CarPowerManager.STATE_SHUTDOWN_ENTER:
case CarPowerManager.STATE_SUSPEND_ENTER:
case CarPowerManager.STATE_HIBERNATION_ENTER:
resetGarageMode(() -> {
mCarPowerService.completeHandlingPowerStateChange(state,
GarageModeController.this);
});
break;
case CarPowerManager.STATE_SHUTDOWN_PREPARE:
initiateGarageMode(
() -> mCarPowerService.completeHandlingPowerStateChange(
state, GarageModeController.this));
break;
default:
break;
}
}
}
The critical state transition is STATE_SHUTDOWN_PREPARE, which triggers
initiateGarageMode(). Garage mode completes when either all jobs finish or the timeout
expires. Then it calls completeHandlingPowerStateChange() to signal that the power service
can proceed with the actual shutdown or suspend.
The controller coordinates with JobScheduler to run deferred jobs that have
the REQUIRE_DEVICE_IDLE constraint. OEMs configure the maximum garage mode duration
through the overlayable resource maxGarageModeRunningDurationInSecs (default 900 seconds,
i.e. 15 minutes). The system property android.car.garagemodeduration is available as an
override on top of that.
Garage mode also handles edge cases:
-
STATE_SHUTDOWN_CANCELLED: If the driver turns the ignition back on during shutdown preparation, garage mode is immediately canceled. -
STATE_SUSPEND_ENTER/STATE_HIBERNATION_ENTER: Different paths for deep sleep vs. hibernate, both requiring garage mode cleanup before proceeding. -
The completion callback pattern makes sure the power state machine does not proceed until garage mode has properly cleaned up.
sequenceDiagram
participant Ignition as Vehicle Ignition
participant VHAL as Vehicle HAL
participant CPMS as CarPowerManagementService
participant GMC as GarageModeController
participant GM as GarageMode
participant JS as JobScheduler
Ignition->>VHAL: Ignition OFF
VHAL->>CPMS: AP_POWER_STATE_REQ = SHUTDOWN_PREPARE
CPMS->>GMC: onStateChanged(STATE_SHUTDOWN_PREPARE)
GMC->>GM: initiateGarageMode()
GM->>JS: Schedule deferred jobs
Note over GM,JS: Jobs run: app updates, data sync, optimization
GM-->>GMC: All jobs complete / timeout
GMC-->>CPMS: completeHandlingPowerStateChange()
CPMS->>VHAL: AP_POWER_STATE_REPORT = DEEP_SLEEP_ENTRY
Note over VHAL: System enters deep sleep
62.1.10 Car Audio Multi-Zone Architecture¶
Automotive audio is fundamentally more complex than phone audio. A car may have separate
speaker zones for driver, passenger, and rear seats, each playing different media. The
CarAudioService manages this through audio zone abstraction:
graph TB
subgraph "Car Audio Architecture"
subgraph "Apps"
NavAudio["Navigation Audio<br/>Turn-by-turn"]
MediaD["Media Player<br/>Driver zone"]
MediaP["Media Player<br/>Passenger zone"]
Phone["Phone Audio<br/>Driver zone"]
end
subgraph "CarAudioService"
ZM["Zone Manager<br/>Route audio by zone"]
FG["Focus Group<br/>Per-zone focus"]
DG["Duck Group<br/>Cross-zone ducking"]
end
subgraph "AudioControl HAL"
AC["AudioControl<br/>hardware/interfaces/<br/>automotive/audiocontrol/"]
end
subgraph "Speaker Zones"
SZ0["Driver Speakers<br/>Audio Zone 0"]
SZ1["Passenger Speakers<br/>Audio Zone 1"]
SZ2["Rear Speakers<br/>Audio Zone 2"]
end
end
NavAudio --> ZM
MediaD --> ZM
MediaP --> ZM
Phone --> ZM
ZM --> FG
ZM --> DG
ZM --> AC
AC --> SZ0
AC --> SZ1
AC --> SZ2
Audio zones are mapped to occupant zones, so each passenger gets independent volume control and audio focus. Navigation audio in the driver zone can duck the driver's music without affecting the passenger's audio.
62.1.11 Driver Distraction and UX Restrictions¶
AAOS enforces safety by restricting UI complexity while driving. The
CarDrivingStateService monitors vehicle speed and gear to determine whether the car is
parked, idling, or moving. The CarUxRestrictionsManagerService translates driving state into
concrete UX restrictions that apps must obey:
graph LR
CPS["CarPropertyService<br/>PERF_VEHICLE_SPEED<br/>GEAR_SELECTION"] --> CDSS["CarDrivingStateService<br/>PARKED / IDLING / MOVING"]
CDSS --> CUXRS[CarUxRestrictionsService]
CUXRS --> UXR["UX Restrictions<br/>NO_KEYBOARD<br/>NO_FILTERING<br/>LIMIT_STRING_LENGTH<br/>NO_VIDEO<br/>LIMIT_CONTENT"]
UXR --> Apps["Applications<br/>must check restrictions"]
UXR --> CPMS2["CarPackageManagerService<br/>blocks non-compliant activities"]
When the driving state is MOVING, activities that are not marked as distraction-optimized
are blocked and replaced with a blocking activity that informs the user.
62.1.12 Car-Specific SystemUI¶
AAOS replaces the phone's SystemUI with a car-specific variant located at:
packages/apps/Car/SystemUI/
This variant replaces the status bar with a car-specific system bar. It also adds HVAC controls, volume controls tailored for multi-zone audio, and a user picker for multi-user vehicles.
// packages/apps/Car/SystemUI/pods/systembar/base/controller/src/com/android/systemui/car/systembar/base/CarSystemBar.java
@SysUISingleton
public class CarSystemBar implements CoreStartable {
private final CarSystemBarController mCarSystemBarController;
@Inject
public CarSystemBar(CarSystemBarController carSystemBarController) {
mCarSystemBarController = carSystemBarController;
}
@Override
public void start() {
mCarSystemBarController.init();
}
}
The Car SystemUI connects to CarService through CarServiceProvider:
// packages/apps/Car/SystemUI/src/com/android/systemui/car/CarServiceProvider.java
@Singleton
public class CarServiceProvider {
@Inject
public CarServiceProvider(@CarSysUIDumpable Context context) {
mCar = Car.createCar(mContext, null, Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
(car, ready) -> {
synchronized (mCarLock) {
synchronized (mListeners) {
mIsCarReady = ready;
mCar = car;
if (ready) {
for (CarServiceOnConnectedListener listener : mListeners) {
listener.onConnected(mCar);
}
}
}
}
});
}
}
Key Car SystemUI components:
| Component | Directory | Purpose |
|---|---|---|
| System Bar | car/systembar/ |
Navigation bar replacement with car-specific buttons |
| HVAC Panel | car/hvac/ |
Climate control overlay |
| Volume UI | car/volume/ |
Multi-zone audio volume control |
| User Picker | car/userpicker/ |
Switch between vehicle occupant users |
| Keyguard | car/keyguard/ |
Car-specific lock screen |
| Notifications | car/notification/ |
Automotive notification handling |
| Status Icons | car/statusicon/ |
Vehicle status indicators |
The HVAC module demonstrates how Car SystemUI integrates with vehicle properties:
packages/apps/Car/SystemUI/pods/hvac/
controller/src/com/android/systemui/car/hvac/
HvacController.java -- HVAC property access and dispatch
HvacView.java -- Base HVAC view interface
ui/src/com/android/systemui/car/hvac/
HvacButtonController.java -- Handles HVAC button interactions
HvacPanelOverlayViewMediator.java -- Manages HVAC panel visibility
HvacPanelView.java -- Full HVAC panel layout
TemperatureControlView.java -- Temperature adjustment widget
referenceui/
FanSpeedBar.java -- Fan speed control
FanDirectionButtons.java -- Air direction buttons
62.1.13 Car Launcher¶
The automotive launcher is significantly different from the phone launcher. It provides a home screen designed for large touchscreens with minimal distraction:
packages/apps/Car/Launcher/
libs/
appgrid/lib/src/com/android/car/carlauncher/
AppLauncherUtils.java -- App listing and filtering
AppItem.java -- Data model for launcher items
LauncherItemDiffCallback.java -- Efficient list updates
recyclerview/
AppGridAdapter.java -- Grid display adapter
AppGridLayoutManager.java -- Car-specific grid layout
docklib/src/com/android/car/docklib/
events/DockEventsReceiver.java -- Dock state handling
task/DockTaskStackChangeListener.java -- Task stack monitoring
The Car Launcher differs from phone Launcher3 in several fundamental ways:
-
No home screen widgets: The automotive home screen emphasizes quick app access and essential information (maps, media) rather than customizable widget grids.
-
Dock-based navigation: The dock at the bottom provides persistent access to navigation, phone, media, and app grid.
-
Task stack awareness: The
DockTaskStackChangeListenermonitors the task stack to keep the dock state synchronized with what is actually running. -
Package filtering:
AppLauncherUtilsfilters the app list to show only distraction-optimized applications when driving restrictions are active. -
Multi-display awareness: The launcher must account for activities launching on different displays (driver vs passenger) and adjust its behavior accordingly.
graph TB
subgraph "Car Launcher Components"
subgraph HomeScreen["Home Screen"]
Maps["Maps Card<br/>Always visible"]
Media["Media Card<br/>Now playing"]
AppGrid["App Grid<br/>Filtered by UX state"]
end
subgraph DockGroup["Dock"]
NavBtn[Navigation]
PhoneBtn[Phone]
MediaBtn[Media]
GridBtn[All Apps]
end
subgraph Infra["Infrastructure"]
ALU["AppLauncherUtils<br/>App filtering"]
DER["DockEventsReceiver<br/>Dock state"]
TSL["DockTaskStackChangeListener<br/>Task monitoring"]
end
end
DockGroup --> HomeScreen
Infra --> HomeScreen
Infra --> DockGroup
Many of the apps the launcher lists are "templated" car apps written against the Android-for-Cars App Library. These apps run inside a separate host process. They do not draw their own UI.
Android 17 bundles that host as a prebuilt: packages/apps/Car/TemplatesPrebuilt/Android.bp declares an android_app_import named CarAppHost for CarAppHost.apk, installed platform-signed (certificate: "platform") and privileged. There is no source for the host in AOSP. The project ships only the build glue that imports the binary APK. So an AAOS build pulls in the host without building it.
62.1.14 External View System (EVS)¶
The Exterior View System provides camera-based features like rearview, surround view, and
parking assistance. The EVS HAL is defined at:
hardware/interfaces/automotive/evs/
It supports both AIDL (current) and HIDL (legacy 1.1) interfaces. The CarEvsService in
CarService manages camera lifecycle, display routing, and integrates with the occupant zone
system to determine which display should show the camera feed.
62.1.15 Automotive HAL Directory Structure¶
The full set of automotive HAL interfaces:
hardware/interfaces/automotive/
vehicle/ -- Vehicle property abstraction (AIDL + HIDL 2.0)
evs/ -- Exterior View System cameras
audiocontrol/ -- Multi-zone audio routing
can/ -- CAN bus interface
ivn_android_device/ -- In-Vehicle Networking
occupant_awareness/ -- Occupant detection (presence, attention)
remoteaccess/ -- Remote wake and task execution
62.1.16 Product Configuration¶
Automotive product builds are configured through makefiles in:
packages/services/Car/car_product/build/
# packages/services/Car/car_product/build/car.mk
PRODUCT_PACKAGES += \
Bluetooth \
CarActivityResolver \
CarDeveloperOptions \
CarSettingsIntelligence \
CarManagedProvisioning \
StatementService \
SystemUpdater
PRODUCT_PROPERTY_OVERRIDES += \
ro.carrier=unknown \
ro.hardware.type=automotive
The ro.hardware.type=automotive property is the fundamental flag that tells the framework
this is an automotive build. Feature flags, SEPolicy, and overlay configurations branch
on this property throughout the system.
Runtime Resource Overlays (RROs) customize the look and feel:
packages/services/Car/car_product/rro/
CarSystemUIRRO/ -- SystemUI visual overrides
CarSystemUIPassengerRRO/ -- SystemUI overrides for passenger displays
PermissionControllerRRO/ -- Permission UI adjustments
CarResourceCommon/ -- Shared resource definitions
overlay-config/
androidRRO/ -- Framework resource overrides
SettingsProviderRRO/ -- Default settings values
TelecommRRO/ -- Telecom UI adjustments
oem-design-tokens/
OEMDesignTokenRRO/ -- OEM visual design tokens
62.2 Android TV¶
Android TV transforms Android into a 10-foot UI experience. The framework additions focus on three areas. The first is a TV Input Framework (TIF) for managing broadcast and HDMI sources. The second is HDMI-CEC control for device coordination. The third is a specialized windowing system for D-pad navigation and picture-in-picture.
62.2.1 TV Input Framework (TIF) Architecture¶
The TV Input Framework is the cornerstone of Android TV. It abstracts TV input sources --
built-in tuners, HDMI ports, IP streams, and third-party inputs -- into a uniform model.
The key system service is TvInputManagerService:
// frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
public final class TvInputManagerService extends SystemService {
private static final String TAG = "TvInputManagerService";
private static final String DVB_DIRECTORY = "/dev/dvb";
// ...
}
The TIF has four layers:
graph TB
subgraph "Application Layer"
TVApp[TV App / Live TV]
TVView["TvView<br/>android.media.tv.TvView"]
TIM["TvInputManager<br/>Client API"]
end
subgraph "Framework Layer"
TIMS["TvInputManagerService<br/>System Service"]
TIHM["TvInputHardwareManager<br/>Hardware input routing"]
TIH["TvInputHal<br/>Native HAL bridge"]
TRMS["TunerResourceManagerService<br/>Tuner resource arbitration"]
end
subgraph "Provider Layer"
TIS1["TvInputService<br/>Built-in Tuner"]
TIS2["TvInputService<br/>HDMI Input"]
TIS3["TvInputService<br/>IP TV Provider"]
TIS4["TvInputService<br/>Third-party"]
end
subgraph "HAL Layer"
TVHAL["tv_input HAL<br/>hardware/interfaces/tv/input/"]
TunerHAL["Tuner HAL<br/>hardware/interfaces/tv/tuner/"]
CECHAL["CEC HAL<br/>hardware/interfaces/tv/cec/"]
end
TVApp --> TVView
TVView --> TIM
TIM --> TIMS
TIMS --> TIHM
TIHM --> TIH
TIMS --> TIS1
TIMS --> TIS2
TIMS --> TIS3
TIMS --> TIS4
TIH --> TVHAL
TIS1 --> TunerHAL
TIHM --> CECHAL
TIMS --> TRMS
62.2.2 TvInputService¶
TvInputService is the abstract base class that all TV input providers must extend. It follows
a pattern similar to InputMethodService -- each provider runs as a bound service that creates
sessions on demand:
// frameworks/base/media/java/android/media/tv/TvInputService.java
public abstract class TvInputService extends Service {
public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
public static final String SERVICE_META_DATA = "android.media.tv.input";
// Priority hint use case types for tuner resource management
@IntDef(prefix = "PRIORITY_HINT_USE_CASE_TYPE_",
value = {PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND,
PRIORITY_HINT_USE_CASE_TYPE_SCAN,
PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK,
PRIORITY_HINT_USE_CASE_TYPE_LIVE,
PRIORITY_HINT_USE_CASE_TYPE_RECORD})
public @interface PriorityHintUseCaseType {}
}
Each TvInputService creates Session objects that handle individual tuning requests.
The session lifecycle:
sequenceDiagram
participant User as User
participant TVApp as TV Application
participant TvView as TvView
participant TIMS as TvInputManagerService
participant TIS as TvInputService
participant Session as TvInputService.Session
User->>TVApp: Select channel
TVApp->>TvView: tune(inputId, channelUri)
TvView->>TIMS: createSession(inputId)
TIMS->>TIS: Bind to service
TIS->>Session: onCreateSession()
Session-->>TIMS: Session created
TIMS->>Session: onSetSurface(surface)
TIMS->>Session: onTune(channelUri)
Session->>Session: Start decoding/rendering
Session-->>TvView: Video frames on surface
Note over Session: Channel change
User->>TVApp: Next channel
TVApp->>TvView: tune(inputId, newChannelUri)
TvView->>TIMS: tune(sessionToken, newChannelUri)
TIMS->>Session: onTune(newChannelUri)
A TvInputService declares itself in the manifest with the BIND_TV_INPUT permission:
<service android:name=".MyTvInputService"
android:permission="android.permission.BIND_TV_INPUT">
<intent-filter>
<action android:name="android.media.tv.TvInputService" />
</intent-filter>
<meta-data android:name="android.media.tv.input"
android:resource="@xml/tv_input" />
</service>
62.2.3 TvInputManagerService Internals¶
Looking deeper at TvInputManagerService, the service manages per-user state and handles
DVB device discovery:
// frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
public final class TvInputManagerService extends SystemService {
private static final String DVB_DIRECTORY = "/dev/dvb";
// DVB frontend device patterns:
// Format 1: /dev/dvb%d.frontend%d
// Format 2: /dev/dvb/adapter%d/frontend%d
private static final Pattern sFrontEndDevicePattern =
Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
private static final Pattern sAdapterDirPattern =
Pattern.compile("^adapter([0-9]+)$");
private static final Pattern sFrontEndInAdapterDirPattern =
Pattern.compile("^frontend([0-9]+)$");
private final TvInputHardwareManager mTvInputHardwareManager;
private final UserManager mUserManager;
@GuardedBy("mLock")
private int mCurrentUserId = UserHandle.USER_SYSTEM;
@GuardedBy("mLock")
private String mOnScreenInputId = null;
@GuardedBy("mLock")
private SessionState mOnScreenSessionState = null;
// Per-user state management
@GuardedBy("mLock")
private final SparseArray<UserState> mUserStates = new SparseArray<>();
@GuardedBy("mLock")
private final Map<String, SessionState> mSessionIdToSessionStateMap =
new HashMap<>();
private HdmiControlManager mHdmiControlManager = null;
private HdmiTvClient mHdmiTvClient = null;
private MediaQualityManager mMediaQualityManager = null;
}
The service constructor initializes the HDMI-CEC integration:
// frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
public TvInputManagerService(Context context) {
super(context);
mTvInputHardwareManager = new TvInputHardwareManager(context,
new HardwareListener());
mHdmiControlManager = mContext.getSystemService(HdmiControlManager.class);
if (mHdmiControlManager != null) {
mHdmiTvClient = mHdmiControlManager.getTvClient();
}
// ...
}
@Override
public void onStart() {
publishBinderService(Context.TV_INPUT_SERVICE, new BinderService());
// Register for CEC active source management:
// Monitors SCREEN_ON/SCREEN_OFF to claim active source status
}
When the TV wakes up, the service sends a delayed message to claim CEC active source status. This message is canceled if the TV switches inputs or goes back to sleep. This prevents unnecessary CEC traffic.
62.2.4 TvInputHardwareManager¶
TvInputHardwareManager bridges the framework with physical TV input hardware. It manages
HDMI port connections, routes audio/video, and tracks hardware-backed TV inputs:
// frameworks/base/services/core/java/com/android/server/tv/TvInputHardwareManager.java
class TvInputHardwareManager implements TvInputHal.Callback {
private final TvInputHal mHal = new TvInputHal(this);
@GuardedBy("mLock")
private final SparseArray<Connection> mConnections = new SparseArray<>();
@GuardedBy("mLock")
private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>();
@GuardedBy("mLock")
private final List<HdmiDeviceInfo> mHdmiDeviceList = new ArrayList<>();
/* A map from a device ID to the matching TV input ID. */
@GuardedBy("mLock")
private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>();
/* A map from a HDMI logical address to the matching TV input ID. */
@GuardedBy("mLock")
private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>();
}
When an HDMI device is connected or disconnected, the hardware manager receives callbacks from the HDMI-CEC service and updates the input list accordingly. This enables automatic input source discovery -- when a user plugs in a Blu-ray player, it appears as a TV input without manual configuration.
62.2.5 Tuner Resource Manager¶
The TunerResourceManagerService arbitrates access to limited hardware tuner resources
(frontends, demuxes, LNBs, CAS sessions) among competing clients:
// frameworks/base/services/core/java/com/android/server/tv/
// tunerresourcemanager/TunerResourceManagerService.java
public class TunerResourceManagerService extends SystemService
implements IBinder.DeathRecipient {
public static final int INVALID_CLIENT_ID = -1;
private static final int MAX_CLIENT_PRIORITY = 1000;
}
The resource manager uses a priority system. Live TV viewing gets higher priority than background recording. When a higher-priority client needs a tuner that is already in use, the resource manager can reclaim it from the lower-priority client:
graph TB
subgraph "Tuner Resources"
FE["Frontend Resources<br/>DVB-T, DVB-S, ATSC, etc."]
DMX["Demux Resources<br/>TS demultiplexing"]
LNB["LNB Resources<br/>Satellite dish control"]
CAS["CAS Sessions<br/>Conditional access / DRM"]
CICAM["CI-CAM Resources<br/>Common Interface modules"]
end
subgraph "Clients by Priority"
LIVE["Live TV Playback<br/>Priority: HIGH"]
REC["Recording<br/>Priority: MEDIUM"]
SCAN["Channel Scan<br/>Priority: MEDIUM"]
BG["Background Tasks<br/>Priority: LOW"]
end
subgraph "Tuner Resource Manager"
TRM[TunerResourceManagerService]
PRI["Priority Arbitration<br/>foreground > background<br/>use-case hints"]
RCL["Resource Reclaim<br/>IResourcesReclaimListener"]
end
LIVE --> TRM
REC --> TRM
SCAN --> TRM
BG --> TRM
TRM --> PRI
PRI --> RCL
TRM --> FE
TRM --> DMX
TRM --> LNB
TRM --> CAS
TRM --> CICAM
Resource types managed by the service:
frameworks/base/services/core/java/com/android/server/tv/tunerresourcemanager/
FrontendResource.java -- Tuner frontend (demodulator) resources
DemuxResource.java -- Demultiplexer resources
LnbResource.java -- Low-noise block (satellite) resources
CasResource.java -- Conditional Access System resources
CiCamResource.java -- Common Interface CAM resources
ClientProfile.java -- Client registration and priority
UseCasePriorityHints.java -- Use-case to priority mapping
TunerResourceBasic.java -- Base resource class
62.2.6 HDMI-CEC¶
Consumer Electronics Control (CEC) allows HDMI-connected devices to control each other.
When you turn on a TV, CEC can automatically turn on the connected soundbar and switch
inputs. The CEC HAL is defined at:
hardware/interfaces/tv/cec/1.0/IHdmiCec.hal
interface IHdmiCec {
addLogicalAddress(CecLogicalAddress addr) generates (Result result);
clearLogicalAddress();
getPhysicalAddress() generates (Result result, uint16_t addr);
sendMessage(CecMessage message) generates (SendMessageResult result);
setCallback(IHdmiCecCallback callback);
getCecVersion() generates (int32_t version);
getVendorId() generates (uint32_t vendorId);
getPortInfo() generates (vec<HdmiPortInfo> infos);
setOption(OptionKey key, bool value);
setLanguage(string language);
enableAudioReturnChannel(int32_t portId, bool enable);
isConnected(int32_t portId) generates (bool status);
};
The Java-side CEC implementation lives in the HDMI control service:
frameworks/base/services/core/java/com/android/server/hdmi/
HdmiCecLocalDeviceTv.java -- TV-type CEC device implementation
HdmiCecLocalDevice.java -- Base CEC device
HdmiCecMessage.java -- CEC message representation
HdmiCecMessageBuilder.java -- Message construction helpers
HdmiControlService.java -- Main HDMI control service
HdmiCecStandbyModeHandler.java -- Standby mode CEC handling
ActiveSourceHandler.java -- Active source switching
DeviceSelectActionFromTv.java -- Device selection flow
RoutingControlAction.java -- Input routing
ArcInitiationActionFromAvr.java -- Audio Return Channel setup
NewDeviceAction.java -- New device discovery
The HdmiCecLocalDeviceTv represents the TV endpoint in CEC communication:
// frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
public class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
// Whether ARC is available. "true" means ARC is established between
// TV and AVR as audio receiver.
@ServiceThreadOnly
private boolean mArcEstablished = false;
// Stores whether ARC feature is enabled per port.
private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray();
}
CEC message flow for "one-touch play" (user presses Play on a Blu-ray remote):
sequenceDiagram
participant BluRay as Blu-ray Player
participant CEC_BUS as CEC Bus
participant TV_HAL as TV CEC HAL
participant HDMI_SVC as HdmiControlService
participant TV_DEV as HdmiCecLocalDeviceTv
participant TIMS as TvInputManagerService
participant TVApp as TV Application
BluRay->>CEC_BUS: <Active Source> (physical addr)
BluRay->>CEC_BUS: <Image View On>
CEC_BUS->>TV_HAL: CEC message received
TV_HAL->>HDMI_SVC: onCecMessageReceived()
HDMI_SVC->>TV_DEV: handleActiveSource()
TV_DEV->>TIMS: Switch to HDMI input
TIMS->>TVApp: Input state changed
TVApp->>TVApp: Switch to Blu-ray input
Note over TV_DEV: If TV was in standby
TV_DEV->>TV_DEV: handleImageViewOn()
TV_DEV->>TV_DEV: Wake up display
62.2.7 TV HAL Interfaces¶
The complete TV HAL surface:
hardware/interfaces/tv/
input/ -- TV input hardware abstraction
cec/
1.0/ -- CEC HAL v1.0 (HIDL)
IHdmiCec.hal
IHdmiCecCallback.hal
types.hal
1.1/ -- CEC HAL v1.1 (HIDL, adds CEC 2.0)
IHdmiCec.hal
IHdmiCecCallback.hal
types.hal
hdmi/ -- HDMI connection management
tuner/ -- Digital TV tuner HAL (AIDL)
aidl/ -- Frontends, demuxes, filters, DVRs
mediaquality/ -- Media quality processing HAL
The Tuner HAL (AIDL-based) provides a comprehensive digital TV stack:
graph TB
subgraph "Tuner HAL Components"
FE["Frontend<br/>Demodulation<br/>DVB-T/S/C, ATSC,<br/>ISDB, DTMB"]
DMX["Demux<br/>TS Demultiplexing<br/>PID filtering"]
FILT["Filters<br/>Section, PES,<br/>Audio, Video,<br/>Record, Download"]
DVR["DVR<br/>Playback from<br/>file/Record to file"]
DESC["Descrambler<br/>CA decryption"]
LNB2["LNB<br/>Satellite dish<br/>control"]
end
FE --> DMX
DMX --> FILT
FILT --> DVR
DESC --> DMX
LNB2 --> FE
62.2.8 TV Picture-in-Picture (PIP)¶
Android TV has its own PIP implementation tailored for the big-screen experience. Unlike the phone PIP (which shows a small floating window), TV PIP places the secondary content in a fixed position appropriate for the lean-back experience:
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
public class TvPipController implements PipTransitionController.PipTransitionCallback,
TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
DisplayController.OnDisplaysChangedListener, ConfigurationChangeListener,
UserChangeListener {
private static final String TAG = "TvPipController";
}
The TV PIP implementation consists of:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/
TvPipController.java -- Main PIP controller for TV
TvPipBoundsState.java -- PIP window position/size state
TvPipBoundsAlgorithm.java -- Position calculation for TV layout
TvPipBoundsController.java -- Coordinates bounds changes
TvPipMenuController.java -- PIP overlay menu (play/close/etc.)
TvPipMenuView.java -- Menu visual layout
TvPipNotificationController.java -- Notification when PIP is active
TvPipTransition.java -- Animations for PIP enter/exit
TvPipAction.java -- PIP action definitions
TvPipCustomAction.java -- App-provided custom actions
TvPipActionsProvider.java -- Action list management
TvPipSystemAction.java -- System-level PIP actions
TvPipBackgroundView.java -- Dimmed background behind PIP
TvPipInterpolators.java -- Animation curves
TvPipMenuEduTextDrawer.java -- Educational tooltip rendering
The TvPipController maintains a clear state machine for PIP lifecycle:
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"STATE_"}, value = {
STATE_NO_PIP,
STATE_PIP,
STATE_PIP_MENU,
})
public @interface State {}
private static final int STATE_NO_PIP = 0; // No PIP window
private static final int STATE_PIP = 1; // PIP at normal position
private static final int STATE_PIP_MENU = 2; // PIP menu open, window centered
static final String ACTION_SHOW_PIP_MENU =
"com.android.wm.shell.pip.tv.notification.action.SHOW_PIP_MENU";
static final String ACTION_CLOSE_PIP =
"com.android.wm.shell.pip.tv.notification.action.CLOSE_PIP";
static final String ACTION_MOVE_PIP =
"com.android.wm.shell.pip.tv.notification.action.MOVE_PIP";
static final String ACTION_TOGGLE_EXPANDED_PIP =
"com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP";
static final String ACTION_TO_FULLSCREEN =
"com.android.wm.shell.pip.tv.notification.action.FULLSCREEN";
The TV PIP state machine:
stateDiagram-v2
[*] --> STATE_NO_PIP
STATE_NO_PIP --> STATE_PIP: App enters PIP mode
STATE_PIP --> STATE_PIP_MENU: User presses select on PIP
STATE_PIP_MENU --> STATE_PIP: Menu dismissed
STATE_PIP_MENU --> STATE_NO_PIP: User closes PIP
STATE_PIP --> STATE_NO_PIP: App exits PIP
STATE_PIP_MENU --> [*]: ACTION_TO_FULLSCREEN returns to full screen
note right of STATE_PIP
Window at corner position,
D-pad left/right to select PIP,
Background content visible
end note
note right of STATE_PIP_MENU
Window moves to center,
Scaled up slightly,
Action buttons visible,
Background dimmed
end note
The controller collaborates with an extensive set of components. The constructor takes 20 dependencies, demonstrating the complexity of TV PIP management:
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
private TvPipController(
Context context,
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
PipDisplayLayoutState pipDisplayLayoutState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
PipTransitionState pipTransitionState,
PipAppOpsListener pipAppOpsListener,
PipTaskOrganizer pipTaskOrganizer,
PipTransitionController pipTransitionController,
TvPipMenuController tvPipMenuController,
PipMediaController pipMediaController,
TvPipNotificationController pipNotificationController,
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
DisplayController displayController,
WindowManagerShellWrapper wmShellWrapper,
Handler mainHandler,
ShellExecutor mainExecutor) {
// ... initialization of all components
}
TV PIP key differences from phone PIP:
- Position is typically a fixed corner, not user-draggable
- Menu is accessed via D-pad, not touch gestures
- Background content dims to avoid visual competition
- Actions include media controls (play/pause) prominent in the menu
- Broadcast-based actions (
ACTION_SHOW_PIP_MENU,ACTION_CLOSE_PIP) allow the notification system to control PIP remotely
The TvPip1Module provides Dagger dependency injection for this legacy implementation:
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPip1Module.java
@Module(includes = {
WMShellBaseModule.class,
Pip1SharedModule.class})
public abstract class TvPip1Module {
@WMSingleton
@Provides
static Optional<TvPipController.TvPipImpl> providePip1(
Context context,
ShellInit shellInit,
ShellController shellController,
TvPipBoundsState tvPipBoundsState,
// ... many dependencies
) { /* ... */ }
}
TvPipModule itself is a thin selector -- @Module(includes = {TvPip1Module.class,
TvPip2Module.class}) -- that provides PipTransitionController (and the active Pip
implementation) based on PipFlags.isPip2ExperimentEnabled().
62.2.9 D-pad Navigation¶
Android TV uses D-pad (directional pad) navigation instead of touch. This fundamentally changes how focus management works in the framework. The key infrastructure:
-
Focus search algorithm:
View.focusSearch()usesFocusFinderto determine which view should receive focus when the user presses Up/Down/Left/Right. -
Touch mode: TV devices are always in "non-touch" mode. Views must handle focus state drawing (focused rings, highlights) explicitly.
-
BrowseFragment / Leanback library: The
androidx.leanbacklibrary provides pre-built UI components optimized for D-pad navigation:BrowseFragment,DetailsFragment,SearchFragment,PlaybackFragment. -
Sound feedback: D-pad presses trigger audible click sounds for spatial feedback.
graph LR
subgraph "D-pad Input Flow"
Remote["TV Remote<br/>D-pad"]
InputReader["InputReader<br/>KeyEvent"]
WMS["WindowManagerService<br/>Key dispatch"]
FocusedWindow["Focused Window<br/>ViewRootImpl"]
FocusFinder["FocusFinder<br/>Next focus calculation"]
NewFocus["New Focused View<br/>Highlight drawn"]
end
Remote --> InputReader
InputReader --> WMS
WMS --> FocusedWindow
FocusedWindow --> FocusFinder
FocusFinder --> NewFocus
62.2.10 TvSettings¶
Android TV uses a specialized settings application rather than the standard phone Settings app.
The TV Settings app (packages/apps/TvSettings/) provides a
sidebar navigation pattern appropriate for D-pad control, with large text and high-contrast
visual design.
TV-specific settings include:
- Input management: HDMI input naming and ordering
- Display and Sound: Resolution, HDR, Audio output (HDMI ARC, Bluetooth, etc.)
- CEC controls: Enable/disable HDMI-CEC, one-touch play, system audio
- Screen saver (Daydream): Ambient mode displays (photos, clock, etc.)
- Accessibility: Large text, high contrast, TalkBack navigation
OEM TV apps such as TvSettings and tuner apps need platform capabilities, but they do not hold
full platform-signature access. For these apps, the TV form factor ships a thin system-API
bridge library, the java_sdk_library com.android.libraries.tv.tvsystem
(frameworks/opt/tv/tvsystem/). This lets the apps reach those capabilities without that access. The library exposes TV-flavored shims over hidden system APIs
such as TvUserManager, TvAudioManager, TvWifiManager, and TvPackageInstaller.
62.2.11 TV Interactive App Framework¶
The TV Interactive App framework extends TIF to support hybrid broadcast/broadband (HBB-TV), interactive advertisements, and two-screen experiences:
frameworks/base/services/core/java/com/android/server/tv/interactive/
TvInteractiveAppManagerService.java
This service manages TvInteractiveAppService instances that can overlay interactive content
on top of TV video streams, responding to both broadcast signals and internet data.
62.2.12 Media Quality HAL¶
The Media Quality HAL (hardware/interfaces/tv/mediaquality/) enables TV-specific video
processing features:
- Picture mode presets (Standard, Cinema, Vivid, Game)
- Dynamic backlight control
- Content-adaptive processing
- Ambient light backlight adjustment
// frameworks/base/media/java/android/media/quality/AmbientBacklightSettings.java
// -- TV ambient backlight configuration
// frameworks/base/media/java/android/media/quality/AmbientBacklightEvent.java
// -- Ambient backlight event notifications
62.3 Wear OS¶
Wear OS adapts Android for wrist-worn devices with tiny circular displays, extreme battery constraints, and a UI paradigm centered on glanceable information. While much of Wear OS's proprietary implementation lives outside AOSP (in Google Play Services for Wear), the framework-level adaptations are visible in the base platform.
62.3.1 Round Display Support¶
The most visible Wear adaptation is support for circular displays. The framework provides several mechanisms:
Window Insets for Round Screens
The WindowInsets system reports whether the display is round via
WindowInsets.isRound(). Apps use this to adjust padding so content is not clipped
by the curved edges:
graph TB
subgraph "Round Display Handling"
Display["Round Display<br/>diameter: 390px"]
Insets["WindowInsets<br/>isRound()=true"]
WIC["WatchViewStub /<br/>BoxInsetLayout"]
SafeArea["Safe Content Area<br/>Inscribed square"]
end
Display --> Insets
Insets --> WIC
WIC --> SafeArea
The BoxInsetLayout (from the Wear support library) automatically applies insets to child
views, ensuring content stays within the inscribed rectangle of a circular display. Without
this, content at the edges would be clipped or unreadable.
Configuration Reporting
The framework reports Configuration.UI_MODE_TYPE_WATCH for Wear devices. This allows
apps and the system to branch behavior:
int uiMode = context.getResources().getConfiguration().uiMode;
boolean isWatch = (uiMode & Configuration.UI_MODE_TYPE_MASK)
== Configuration.UI_MODE_TYPE_WATCH;
Resource qualifiers (-watch, -round, -notround) enable dimension, layout, and drawable
overrides per display shape.
62.3.2 Ambient Mode and Always-On Display¶
Wear devices support an ambient mode where the watch face continues to be visible but in a low-power state. This involves:
- Reduced refresh rate: The display drops to 1 Hz or lower update rate.
-
Limited color palette: The screen switches to a grayscale or limited-color mode to reduce OLED pixel power consumption.
-
Simplified rendering: Watch faces switch from full-color interactive mode to a simplified ambient rendering.
The AmbientModeSupport class (from the Wear support library) provides the lifecycle
callbacks:
stateDiagram-v2
[*] --> Interactive: Wrist raise / tap
Interactive --> Ambient: Timeout / wrist down
Ambient --> Interactive: Wrist raise / tap
Ambient --> Off: Extended inactivity
Off --> Interactive: Button press / wrist raise
note right of Interactive
Full color rendering,
Full frame rate,
Touch input active,
Sensor sampling active
end note
note right of Ambient
Simplified rendering,
1 Hz refresh,
Grayscale / low color,
No touch input,
Reduced sensor sampling
end note
62.3.3 Burn-in Protection¶
OLED displays on watches are susceptible to burn-in if static pixels remain illuminated continuously. The framework implements burn-in protection through:
-
Pixel shifting: In ambient mode, the entire display content shifts by a few pixels periodically (every minute or so). This is handled at the WindowManager level.
-
Outline-only rendering: Watch faces in ambient mode use outlined digits rather than filled shapes, reducing the number of lit pixels.
-
Low-bit ambient: Some displays support a true low-bit mode where each pixel is either fully on or fully off (no anti-aliasing), further reducing burn-in risk.
The watch face framework exposes burn-in protection information via WatchFaceService:
graph LR
subgraph "Burn-in Protection Strategies"
PS["Pixel Shift<br/>Content moves<br/>periodically"]
OL["Outline Rendering<br/>No solid fills<br/>in ambient"]
LB["Low-bit Mode<br/>1-bit per pixel<br/>no anti-aliasing"]
TC["Time Limiting<br/>Screen off after<br/>extended ambient"]
end
subgraph "Implementation Points"
WM["WindowManager<br/>Applies pixel offset"]
WFS["WatchFaceService<br/>Ambient drawing mode"]
DP["Display Policy<br/>Screen timeout"]
end
PS --> WM
OL --> WFS
LB --> WFS
TC --> DP
62.3.4 Watch Face Framework¶
Watch faces are the most distinctive Wear UI element. The framework defines a
WatchFaceService that extends WallpaperService to provide always-visible, continuously
updating face rendering:
The watch face lifecycle:
sequenceDiagram
participant WFS as WatchFaceService
participant Engine as Engine (WallpaperService.Engine)
participant Canvas as Canvas / GL Surface
participant WM as WindowManager
participant AMS as AmbientModeSupport
Note over WFS: Service starts
WFS->>Engine: onCreateEngine()
Engine->>Canvas: onSurfaceCreated()
Engine->>Engine: onDraw() [interactive mode]
Note over AMS: User lowers wrist
AMS->>Engine: onEnterAmbient(burnInProtection)
Engine->>Canvas: Draw simplified face
Engine->>Engine: Reduce update frequency to 1/min
Note over AMS: User raises wrist
AMS->>Engine: onExitAmbient()
Engine->>Canvas: Draw full interactive face
Engine->>Engine: Resume normal update frequency
Note over WM: Burn-in protection active
WM->>WM: Apply pixel shift offset
Watch face complications (small data displays showing weather, steps, battery, etc.) are provided through the Complication API:
graph TB
subgraph "Watch Face Complications"
WF["Watch Face<br/>WatchFaceService"]
CP1["Complication Provider 1<br/>Weather"]
CP2["Complication Provider 2<br/>Step Count"]
CP3["Complication Provider 3<br/>Battery"]
CP4["Complication Provider 4<br/>Next Calendar Event"]
end
subgraph "Complication Types"
SHORT["SHORT_TEXT<br/>72F"]
LONG["LONG_TEXT<br/>Meeting at 2:00 PM"]
ICON["ICON<br/>Small icon"]
RANGE["RANGED_VALUE<br/>Progress arc"]
IMG["SMALL_IMAGE<br/>Photo or icon"]
end
WF --> CP1
WF --> CP2
WF --> CP3
WF --> CP4
CP1 --> SHORT
CP2 --> SHORT
CP3 --> RANGE
CP4 --> LONG
62.3.5 Tiles API¶
Wear OS Tiles provide glanceable information surfaces that users swipe between from the
watch face. Unlike full activities, Tiles are declaratively defined using a layout DSL and
updated by a TileService:
graph LR
subgraph "Tiles Architecture"
TS["TileService<br/>Provider app"]
TR["TileRenderer<br/>Layout rendering"]
TH["Tile Host<br/>System UI"]
end
subgraph "Tile Lifecycle"
REQ["onTileRequest()"]
RES["onResourcesRequest()"]
UPD["Update interval<br/>or user swipe"]
end
TH --> REQ
REQ --> TS
TS --> RES
RES --> TR
TR --> TH
UPD --> REQ
Tiles are built using a protobuf-based layout schema:
- LayoutElement: Row, Column, Box, Spacer, Image, Text, Arc
- TimelineEntry: Tiles can define time-based layouts that automatically switch
- Clickable: Elements can trigger actions (launch activity, send message)
62.3.6 Reduced Windowing¶
Wear OS significantly simplifies the windowing system compared to phone:
-
Single-task model: Only one activity is visible at a time. There is no split-screen, freeform, or PIP support.
-
No navigation bar: The system back gesture is handled by the physical button or a swipe gesture, not an on-screen button.
-
Simplified recent apps: The recent apps list is either absent or a simple vertical scroll, not the full phone-style overview.
-
Reduced display areas: No status bar in the traditional sense. Notifications appear as cards swiped in from the bottom.
graph TB
subgraph "Phone Windowing"
PStatusBar[Status Bar]
PContent[App Content Area]
PNavBar[Navigation Bar]
PSplit[Split Screen Support]
PPip[PIP Support]
PFreeform[Freeform Windows]
end
subgraph "Wear Windowing"
WFace["Watch Face<br/>always behind"]
WContent["Single App<br/>full screen"]
WNotif["Notification Cards<br/>swipe up"]
WTiles["Tiles<br/>swipe left/right"]
end
subgraph "Simplifications"
NoSplit[No split screen]
NoPip[No PIP]
NoFreeform[No freeform]
NoNavBar[No navigation bar]
end
62.3.7 Battery Optimization for Wearables¶
Wear OS employs aggressive battery optimization beyond standard Android:
-
Doze on wrist-down: When the accelerometer detects the wrist is lowered, the device enters a doze-like state much faster than a phone would.
-
Network efficiency: Wearable devices preferentially route network requests through a connected phone (Bluetooth proxy) rather than use their own Wi-Fi or cellular radio. This saves significant power.
-
Sensor batching: Sensors batch readings and deliver them in bursts rather than continuously, allowing the processor to sleep between batches.
-
Reduced background activity:
JobSchedulerconstraints are tighter on Wear. Fewer concurrent background services are allowed. -
Bedtime mode: A special mode that disables always-on display, notifications, and tilt-to-wake during sleep hours.
graph TB
subgraph "Wear Battery Optimization Stack"
subgraph "Hardware Level"
OLED["OLED Display<br/>Per-pixel power control"]
ULP["Ultra-Low-Power<br/>Co-processor"]
BLE["Bluetooth LE<br/>Low-energy comms"]
end
subgraph "Framework Level"
AOD["Always-On Display<br/>1Hz update, grayscale"]
BIP["Burn-in Protection<br/>Pixel shifting"]
DOZE["Aggressive Doze<br/>Wrist-down trigger"]
BATCH["Sensor Batching<br/>Periodic bulk delivery"]
PROXY["BT Network Proxy<br/>Route through phone"]
end
subgraph "App Level"
COMP["Complications<br/>Push updates, not poll"]
TILES["Tiles<br/>Declarative, no Activity"]
AMBI["Ambient Mode<br/>Simplified rendering"]
end
end
OLED --> AOD
ULP --> DOZE
BLE --> PROXY
AOD --> AMBI
DOZE --> BATCH
62.3.8 Wear-Specific Resource Qualifiers and Configuration¶
Wear devices use a comprehensive set of resource qualifiers for adapting layouts:
| Qualifier | Values | Purpose |
|---|---|---|
-watch |
N/A | Applied to watch devices |
-round |
N/A | Round display shape |
-notround |
N/A | Square or rectangular display |
UI_MODE_TYPE_WATCH |
6 | Configuration UI mode |
smallestScreenWidthDp |
~180-220dp | Typical watch screen sizes |
The framework reports several watch-specific configuration values:
// Configuration checks in framework code:
boolean isWatch = (config.uiMode & Configuration.UI_MODE_TYPE_MASK)
== Configuration.UI_MODE_TYPE_WATCH;
// Screen shape check:
boolean isRound = config.isScreenRound();
// Typical watch display metrics:
// 390x390 pixels at ~300+ dpi for round
// 320x320 pixels at ~280 dpi for smaller models
Layout adaptations for round displays follow a specific pattern:
graph TB
subgraph "Round Display Layout Strategy"
subgraph "Full Circle"
FC["Total display area<br/>pi * r^2"]
end
subgraph "Safe Rectangle"
SR["Inscribed square<br/>side = diameter / sqrt(2)<br/>~70.7% of diameter"]
end
subgraph "Content Zones"
CZ1["Center: primary content<br/>Full readable area"]
CZ2["Edges: decorative only<br/>Arc progress, bezels"]
end
end
FC --> SR
SR --> CZ1
FC --> CZ2
Apps targeting Wear must account for the ~30% of screen area near the edges of a round
display being partially clipped. The BoxInsetLayout and curved text APIs help manage
this constraint automatically.
62.3.9 Wearable Sensing Framework¶
AOSP includes a framework for wearable-specific sensing capabilities:
frameworks/base/services/core/java/com/android/server/wearable/
WearableSensingManagerService.java -- System service
WearableSensingManagerPerUserService.java -- Per-user management
RemoteWearableSensingService.java -- Remote service connection
WearableSensingSecureChannel.java -- Secure data channel
WearableSensingShellCommand.java -- Debug shell commands
// frameworks/base/core/java/android/app/wearable/WearableSensingManager.java
// -- Client API for wearable sensing
// frameworks/base/core/java/android/service/wearable/WearableSensingService.java
// -- Service that processes wearable sensor data
The wearable sensing framework provides a secure channel for processing sensitive health and activity data from wearable sensors. It supports:
- Accelerometer and gyroscope data for activity recognition
- Heart rate and SpO2 monitoring
- Fall detection algorithms
- Context-aware ambient computing
graph TB
subgraph "Wearable Sensing Architecture"
Sensors["Wearable Sensors<br/>Accel, Gyro, HR, SpO2"]
WSS["WearableSensingService<br/>On-device processing"]
SC["Secure Channel<br/>WearableSensingSecureChannel"]
WSMS["WearableSensingManagerService<br/>System service"]
WSM["WearableSensingManager<br/>Client API"]
App[Health/Fitness App]
end
Sensors --> WSS
WSS --> SC
SC --> WSMS
WSMS --> WSM
WSM --> App
62.4 Form Factor Customization Points¶
The key architectural insight across all three form factors is that AOSP does not use
compile-time #ifdef branching. Instead, customization is achieved through runtime
overlays, Dagger module substitution, product configuration, and abstraction layers. This
section catalogs the specific customization points.
62.4.1 SystemUI Variants¶
SystemUI is the most visibly customized component. AOSP provides three variants:
frameworks/base/packages/SystemUI/ -- Phone/tablet SystemUI (default)
packages/apps/Car/SystemUI/ -- Automotive SystemUI
packages/apps/TvSystemUI/ -- TV SystemUI
The phone SystemUI is the default and most feature-rich. Car SystemUI replaces it entirely with automotive-specific UI components. TV SystemUI is much simpler, focusing on a minimal notification system and settings access.
The selection is made at build time through product configuration:
# For automotive builds:
PRODUCT_PACKAGES += CarSystemUI
# Instead of the default:
# PRODUCT_PACKAGES += SystemUI
62.4.2 WMShell Module Variants¶
The Window Manager Shell provides Dagger module variants for different form factors. The base module is shared, with form-factor-specific modules layered on top:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/
WMShellBaseModule.java -- Shared dependencies (all form factors)
WMShellModule.java -- Phone/tablet specific
TvWMShellModule.java -- TV specific
WMShellConcurrencyModule.java -- Thread pool configuration
The TvWMShellModule substitutes TV-specific implementations:
// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/
// TvWMShellModule.java
@Module(includes = {TvPipModule.class})
public class TvWMShellModule {
@WMSingleton
@Provides
@DynamicOverride
static StartingWindowTypeAlgorithm provideStartingWindowTypeAlgorithm() {
return new TvStartingWindowTypeAlgorithm();
}
@WMSingleton
@Provides
@DynamicOverride
static SplitScreenController provideSplitScreenController(/* ... */) {
return new TvSplitScreenController(/* ... */);
}
}
Key substitutions made by TvWMShellModule:
| Component | Phone | TV |
|---|---|---|
| StartingWindowTypeAlgorithm | Default | TvStartingWindowTypeAlgorithm |
| SplitScreenController | SplitScreenController | TvSplitScreenController |
| PIP | Pip1Module / Pip2Module | TvPipModule |
| PIP Controller | PipController | TvPipController |
| PIP Bounds | PipBoundsAlgorithm | TvPipBoundsAlgorithm |
graph TB
subgraph "WMShell Module Architecture"
BASE["WMShellBaseModule<br/>Shared infrastructure"]
subgraph "Form Factor Modules"
PHONE["WMShellModule<br/>Phone/Tablet"]
TV["TvWMShellModule<br/>TV"]
AUTO["Car uses separate<br/>SystemUI entirely"]
end
subgraph "PIP Modules"
PIP1["Pip1Module<br/>Phone PIP v1"]
PIP2["Pip2Module<br/>Phone PIP v2"]
TVPIP["TvPipModule<br/>TV PIP"]
end
end
BASE --> PHONE
BASE --> TV
PHONE --> PIP1
PHONE --> PIP2
TV --> TVPIP
62.4.3 Device Overlays (Runtime Resource Overlays)¶
Runtime Resource Overlays (RROs) are the primary mechanism for visual and behavioral customization without source code changes. Each form factor uses RROs extensively:
Automotive RROs:
packages/services/Car/car_product/rro/
CarSystemUIRRO/ -- SystemUI visual overrides
CarSystemUIPassengerRRO/ -- SystemUI overrides for passenger displays
PermissionControllerRRO/ -- Permission UI adjustments
CarResourceCommon/ -- Shared resource definitions
overlay-config/
androidRRO/ -- Framework defaults
SettingsProviderRRO/ -- Settings provider defaults
CertInstallerRRO/ -- Certificate installer
TelecommRRO/ -- Telecom UI
oem-design-tokens/
OEMDesignTokenRRO/ -- OEM design system tokens
OEMDesignTokenFrameworkResRRO/ -- Framework token overlays
OEMDesignTokenCarUiPluginRRO/ -- Car UI plugin tokens
Distant Display RROs (for secondary screens):
packages/services/Car/car_product/distant_display/rro/
distant_display_rro.mk
MediashellRRO/
CarServiceRRO/
DriverUiRRO/
RROs work by overlaying resource values at runtime without modifying the target APK. An overlay package declares which target package and resources it overrides:
<!-- Example: CarSystemUIRRO/AndroidManifest.xml -->
<manifest>
<overlay android:targetPackage="com.android.systemui"
android:resourcesMap="@xml/overlays"
android:isStatic="true" />
</manifest>
The overlay can then replace any resource -- colors, dimensions, layouts, strings, booleans -- in the target package. This is how OEMs customize the look and feel of the car UI without forking SystemUI source code.
62.4.4 Product Configuration¶
Product configuration is where form-factor selection begins. The build system reads makefile variables to determine which packages, overlays, and properties to include.
Automotive product configuration:
# packages/services/Car/car_product/build/car_base.mk
# packages/services/Car/car_product/build/car.mk
# Key automotive properties:
PRODUCT_PROPERTY_OVERRIDES += \
ro.hardware.type=automotive
# Key automotive packages:
PRODUCT_PACKAGES += \
CarService \
CarSystemUI \
CarLauncher \
CarSettings
The car_product/build/ directory hierarchy:
| File | Purpose |
|---|---|
car.mk |
Common packages for all car builds |
car_base.mk |
Base product definition |
car_product.mk |
Full product packages |
car_system.mk |
System partition packages |
car_system_ext.mk |
System extension packages |
car_vendor.mk |
Vendor partition packages |
car_generic_system.mk |
Generic system image |
TV product configuration typically includes:
# (vendor-specific or device-specific makefile)
PRODUCT_PACKAGES += \
TvSettings \
TvSystemUI \
TvProvider \
TvLauncher
Note that ro.hardware.type is an automotive-only property -- TV builds are identified
by the PackageManager features android.software.leanback and
android.hardware.type.television, not by a system property.
Wear product configuration typically includes:
# (vendor-specific)
# Watch-specific features
PRODUCT_PACKAGES += \
WearSettings \
ClockworkHome \
WearSystemUI
Wear builds are likewise identified by the android.hardware.type.watch feature.
62.4.5 Feature Flags and Configuration¶
Beyond properties and overlays, form-factor behavior is controlled through:
- PackageManager feature flags: Each form factor declares features in
system/etc/ permissions/:
<!-- Automotive -->
<feature name="android.hardware.type.automotive" />
<!-- TV -->
<feature name="android.software.leanback" />
<feature name="android.hardware.type.television" />
<!-- Wear -->
<feature name="android.hardware.type.watch" />
- Config resources:
frameworks/base/core/res/res/values/config.xmlcontains hundreds of configurable values. Form-factor overlays change these:
<!-- Example: config_enableCarDockHomeLaunch -->
<!-- When true, docking launches the car dock home activity -->
Note that some capabilities are expressed as PackageManager features rather than
config booleans -- picture-in-picture support, for instance, is the
android.software.picture_in_picture feature.
- SELinux policies: Each form factor has specific SELinux policies:
62.4.6 How OEMs Customize Per Form Factor¶
The OEM customization stack for any form factor follows a layered pattern:
graph TB
subgraph "Customization Stack (Bottom to Top)"
AOSP["AOSP Base<br/>frameworks/base, packages/"]
FF["Form Factor Layer<br/>packages/services/Car/<br/>TV/Wear framework additions"]
PROD["Product Configuration<br/>device/vendor/product.mk<br/>Package selection, properties"]
RRO["Runtime Resource Overlays<br/>Visual customization<br/>Default value overrides"]
OEM_APK["OEM Replacement APKs<br/>Custom Launcher, SystemUI<br/>Custom Settings"]
VENDOR["Vendor Partition<br/>HAL implementations<br/>Proprietary services"]
end
AOSP --> FF
FF --> PROD
PROD --> RRO
RRO --> OEM_APK
OEM_APK --> VENDOR
Specific OEM customization patterns by form factor:
Automotive OEM Customization:
- Custom
IVehicleHAL implementation mapping to their specific ECU protocol - Custom instrument cluster renderer bound by
InstrumentClusterService - Custom HVAC control panel via RRO on Car SystemUI
- Custom car launcher with brand-specific widgets
- OEM design tokens for brand-consistent visual identity
- Custom audio routing through AudioControl HAL
TV OEM Customization:
- Custom
TvInputServiceimplementations for proprietary tuner hardware - Custom TV launcher with brand-specific content recommendations
- Custom CEC behavior for their specific device ecosystem
- Picture quality processing via Media Quality HAL
- Custom remote control integration
Wear OEM Customization:
- Custom watch face packs
- Custom sensor implementations for health features
- Custom tiles for device-specific features
- Battery optimization tuning for specific hardware
- Custom complications providers for device sensors
62.4.7 Multi-Display Architecture Across Form Factors¶
Multi-display support varies dramatically across form factors:
graph TB
subgraph "Phone"
PD1[Primary Display]
PD2[Optional: Cast / External]
end
subgraph "Automotive"
AD1[Driver Main Display]
AD2[Instrument Cluster]
AD3[Passenger Display]
AD4[Rear Seat Left]
AD5[Rear Seat Right]
AD6[HUD Display]
end
subgraph "TV"
TD1["Main TV Output<br/>HDMI"]
TD2["Optional PIP<br/>Same display, separate task"]
end
subgraph "Wear"
WD1["Single Round Display<br/>Always-on capable"]
end
Automotive has the most complex multi-display needs, which is why the occupant zone system exists exclusively in the Car framework. TV handles multi-content through PIP on a single display. Wear has the simplest model with a single, small display.
62.4.8 Service Registration Differences¶
Each form factor registers different system services during SystemServer startup:
graph TB
subgraph "Common Services (All Form Factors)"
AMS[ActivityManagerService]
WMS[WindowManagerService]
PMS[PackageManagerService]
IPMS[InputMethodManagerService]
NMS[NotificationManagerService]
end
subgraph "Automotive Additional Services"
CS["CarService<br/>~40 internal services"]
OAS[OccupantAwarenessService]
VHAL_S[Vehicle HAL Service]
end
subgraph "TV Additional Services"
TIMS2[TvInputManagerService]
TRMS2[TunerResourceManagerService]
HDMI[HdmiControlService]
end
subgraph "TV Optional Services<br/>(frameworks/opt/tv/tvsystem repo)"
TVWS["TvWatchdogService<br/>(not started by SystemServer)"]
end
subgraph "Wear Additional Services"
WSMS2[WearableSensingManagerService]
ACM[AmbientContextManagerService]
end
The services are conditionally started based on device features:
// Automotive: started when ro.hardware.type == automotive
// CarService is started as a persistent service
// TV: started when this is a TV device (isTv = leanback feature) OR
// the FEATURE_LIVE_TV feature is present -- NOT gated on FEATURE_LEANBACK alone
// (frameworks/base/services/java/com/android/server/SystemServer.java)
if (isTv || pm.hasSystemFeature(PackageManager.FEATURE_LIVE_TV)) {
mSystemServiceManager.startService(TvInputManagerService.class);
}
// HDMI-CEC: started on TV devices
if (pm.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
// HdmiControlService starts
}
// TunerResourceManagerService: gated on FEATURE_TUNER
if (pm.hasSystemFeature(PackageManager.FEATURE_TUNER)) {
// TunerResourceManagerService starts
}
62.4.9 Input Model Differences¶
Each form factor has a fundamentally different input model:
graph TB
subgraph "Phone Input"
Touch[Multi-touch Screen]
Gesture["System Gestures<br/>Back, Home, Recents"]
VoiceP[Voice Assistant]
end
subgraph "Automotive Input"
TouchA["Touchscreen<br/>Driver + Passenger"]
Rotary["Rotary Controller<br/>Knob input"]
Steering[Steering Wheel Buttons]
VoiceA["Voice Assistant<br/>Primary while driving"]
end
subgraph "TV Input"
DPad["D-pad Remote<br/>Up/Down/Left/Right/Select"]
VoiceT[Voice Remote]
GamePad[Game Controller]
Mouse["Air Mouse / Pointer<br/>Optional"]
end
subgraph "Wear Input"
TouchW["Touch Screen<br/>Limited due to size"]
Crown["Rotary Side Button<br/>Scroll/Navigation"]
Buttons["Physical Buttons<br/>Back, Home"]
WristG[Wrist Gestures]
end
For automotive, the RotaryController app (packages/apps/Car/RotaryController/) handles
rotary knob input, translating it into focus navigation similar to D-pad on TV. The
CarInputService in CarService manages input routing across occupant zones:
62.5 Android 17: Automotive Windowing, Visibility Barriers, and the Road to SDV¶
Android 17 invests heavily in the automotive form factor. Most of the new code in
packages/services/Car/ for this release is not about new vehicle properties or new HALs --
it is about windowing. AAOS head units increasingly run several apps side by side on a single
large display. An example is a map next to a media player next to a climate panel. They also
drive multiple physical displays for multiple occupants. Each display uses an OEM-authored,
fully configurable layout.
The phone WindowManager Shell was never designed for any of this. So 17 ships a dedicated
automotive shell library and a declarative panel framework on top of it. This section walks the three pieces that landed for 17. They are the Car-WindowManager-Shell
library, the auto visibility barrier, and the Scalable UI panel framework. The section then
points to where the larger Software Defined Vehicle (SDV) story is told.
62.5.1 The Car WindowManager Shell Library¶
Phone and tablet windowing is built from frameworks/base/libs/WindowManager/Shell/
(WMShellModule, WMShellBaseModule), as Section 62.4.2 described.
Automotive needs a different model. Every container is a multi-window root task (there is no single fullscreen task that owns
the display). The system UI must layer and bound containers explicitly. A container must be
hideable without an opaque activity painted on top of it. Android 17 factors this into a
standalone library, Car-WindowManager-Shell, declared in
packages/services/Car/libs/car-wm-shell-lib/Android.bp. Its public surface is small and lives
under packages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/.
The central abstraction is the AutoTaskStack -- a task stack that is always in multi-window
mode -- and its concrete form, the RootTaskStack:
// packages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/
// AutoTaskStack.kt
/**
* Represents an auto task stack, which is always in multi-window mode.
*/
interface AutoTaskStack {
val id: Int
val displayId: Int
var leash: SurfaceControl
val name: String
}
data class AutoTaskStackState(
val bounds: Rect = Rect(),
val isAboveBarrier: Boolean,
val layer: Int
)
data class RootTaskStack(
override val id: Int,
override val displayId: Int,
override var leash: SurfaceControl,
override val name: String,
var rootTaskInfo: ActivityManager.RunningTaskInfo
) : AutoTaskStack
Two fields in AutoTaskStackState are the heart of the new model. layer gives the system UI
explicit Z-order control over containers (the phone shell mostly relies on activity order).
isAboveBarrier ties the container to the visibility barrier described in Section 62.5.2.
WindowManager hides a stack below the barrier without any occluding surface. The bounds
field lets the system UI place and resize each container deterministically, which is what makes
fixed multi-pane car layouts possible.
Clients drive the shell through the AutoTaskStackController interface:
// packages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/
// AutoTaskStackController.kt
interface AutoTaskStackController {
var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate?
val taskStackStateMap: Map<Int, AutoTaskStackState>
fun createRootTaskStack(
displayId: Int,
name: String,
listener: RootTaskStackListener
): RootTaskStack?
fun destroyTaskStack(taskStackId: Int)
fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?)
@ShellMainThread
fun startTransition(transaction: AutoTaskStackTransaction): IBinder?
}
The controller follows a transaction-and-transition discipline rather than imperative window
moves. A caller composes an AutoTaskStackTransaction (reparent a task into a stack, change a
stack's AutoTaskStackState, send a PendingIntent into a stack) and calls startTransition.
WindowManager then drives the change through a normal Shell transition. It calls back into the
caller's AutoTaskStackTransitionHandlerDelegate (handleRequest, startAnimation,
onTransitionConsumed, mergeAnimation). This keeps the animation in sync with the underlying
window operations. setDefaultRootTaskStackOnDisplay registers a stack as the launch root for a
display, so newly started activities are routed into a managed container instead of going
fullscreen.
Container lifecycle is reported through RootTaskStackListener, which adapts
ShellTaskOrganizer.TaskListener so that callbacks split cleanly between the root container and
its child tasks:
// packages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/
// RootTaskStackListener.kt
interface RootTaskStackListener : ShellTaskOrganizer.TaskListener {
fun onRootTaskStackAppeared(rootTaskStack: RootTaskStack) {}
fun onRootTaskStackInfoChanged(rootTaskStack: RootTaskStack) {}
fun onRootTaskStackDestroyed(rootTaskStack: RootTaskStack) {}
}
On the CarService side, CarActivityService grew its own lightweight RootTaskListener
interface plus registerRootTaskListener/unregisterRootTaskListener. With these, automotive
system services can observe root-task appear/vanish events without a direct dependency on the
shell library:
// packages/services/Car/service/src/com/android/car/am/CarActivityService.java
/** Listener for root task callbacks. */
public interface RootTaskListener {
void onRootTaskVanished(String name);
void onRootTaskAppeared(String name);
}
The library and shell architecture:
graph TB
subgraph "System UI Process"
DELEG["AutoTaskStackTransitionHandlerDelegate<br/>(implemented by Car SystemUI)"]
CTRL["AutoTaskStackController<br/>car-wm-shell-lib"]
REPO["AutoTaskRepository<br/>tracks stacks + barrier tokens"]
BAR["AutoVisibilityBarrierController"]
end
subgraph "WindowManager / Shell Core"
STO["ShellTaskOrganizer"]
TR["Transitions"]
WM["WindowManagerService"]
end
subgraph "Containers on a Display"
RTS1["RootTaskStack A<br/>isAboveBarrier=true, layer=2"]
RTS2["RootTaskStack B<br/>isAboveBarrier=true, layer=1"]
BARRIER["Visibility Barrier Task<br/>(empty, Shell-organized)"]
RTS3["RootTaskStack C<br/>isAboveBarrier=false (hidden)"]
end
DELEG --> CTRL
CTRL --> STO
CTRL --> TR
BAR --> STO
BAR --> REPO
CTRL --> REPO
STO --> WM
TR --> WM
CTRL --> RTS1
CTRL --> RTS2
BAR --> BARRIER
CTRL --> RTS3
62.5.2 The Auto Visibility Barrier¶
A recurring automotive problem is hiding a container reliably. On phones a task is hidden because
something opaque covers it. In a multi-pane car layout there may be nothing opaque to cover a pane
you want to dismiss. Leaving it visible underneath is both a UX and a driver-distraction
problem. Android 17 solves this with the visibility barrier, implemented by
AutoVisibilityBarrierController under
packages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/visibilitybarrier/.
The mechanism is deliberately simple. The controller creates one empty, Shell-organized task per
display -- the barrier. It relies on a WindowManager rule: siblings ordered below the
barrier are made invisible. Any container can therefore be hidden by ordering it under the
barrier (its AutoTaskStackState.isAboveBarrier becomes false) with no occluding activity
required. The class documentation states the contract directly:
// packages/services/Car/libs/car-wm-shell-lib/src/com/android/wm/shell/automotive/
// visibilitybarrier/AutoVisibilityBarrierController.kt
/**
* Controller to manage the visibility barrier for each display.
*
* The visibility barrier is an empty Shell-organized task. Siblings below the visibility barrier
* are made invisible by the WindowManager. This provides a reliable mechanism to hide tasks
* without needing to occlude them with a fullscreen visible activity.
*
* Note: A home activity can co-exist with the visibility barrier and either will not affect the
* other.
*/
@WMSingleton
class AutoVisibilityBarrierController @Inject constructor(/* ... */) :
DisplayController.OnDisplaysChangedListener, AutoShellInitializable {
override fun initialize() {
if (!Flags.enableAutoVisibilityBarrier()) {
return
}
// ...register for display add/remove and create a barrier per display
}
}
The controller listens for displays through DisplayController.OnDisplaysChangedListener and
maintains one barrier per display, recording each barrier's token in AutoTaskRepository and
dropping it on onTaskVanished. Like nearly every new windowing behavior in 17, it is gated by
an aconfig flag so OEMs and the platform can stage the rollout:
# packages/services/Car/libs/car-wm-shell-lib/aconfig/flags.aconfig
package: "com.android.wm.shell.automotive"
container: "system_ext"
flag {
name: "enable_mumd_car_wm_shell"
namespace: "car_framework"
description: "Enables proxy and host implementation in MUMD car-wm-shell for automotive"
}
flag {
name: "enable_auto_visibility_barrier"
namespace: "car_framework"
description: "Enables the dedicated auto visibility barrier controller on all displays"
}
The enable_mumd_car_wm_shell flag is the second half of the story. In a Multi-User
Multi-Display (MUMD) vehicle, the shell runs a proxy-and-host split. This lets each per-occupant
system UI process drive its own display's containers through the same
AutoTaskStackController API.
62.5.3 Scalable UI: Declarative Car Panels¶
The shell library gives Car SystemUI primitives, but OEMs do not want to write transition code.
Android 17 layers a declarative Scalable UI framework over the shell. This lets a head-unit layout be
described as a set of configurable panels with states, variants, and animations, rather than
imperative window calls. The framework spans two locations. The first is the reusable model and
panel library at packages/apps/Car/systemlibs/car-scalable-ui-lib/ (package
com.android.car.scalableui, which provides Event, PanelTransaction, Panel, and the
variant/keyframe model). The second is the SystemUI wiring at
packages/apps/Car/SystemUI/src/com/android/systemui/car/wm/scalableui/.
The bridge class is PanelAutoTaskStackTransitionHandlerDelegate -- it implements the shell's
AutoTaskStackTransitionHandlerDelegate and translates WindowManager transitions into panel
events. Its companion pieces, documented in
packages/apps/Car/SystemUI/src/com/android/systemui/car/wm/scalableui/README.md, are:
| Component | Role |
|---|---|
PanelAutoTaskStackTransitionHandlerDelegate |
Bridge between Shell transitions and Scalable UI |
PanelTransitionCoordinator |
Orchestrates panel animations and state changes |
EventDispatcher |
Maps system events to PanelTransactions |
PanelConfigReader / ActionConfigReader |
Read declarative panel and action configuration |
TaskPanel / DecorPanel / SysUIPanel |
The panel container types |
The README draws the distinction that makes the framework efficient. Window state (visibility,
size, position, Z-order) is heavyweight and goes through WindowManager via an
AutoTaskStackTransaction. Surface properties (alpha, scale, translation, crop) are animated
cheaply on SurfaceFlinger through AutoSurfaceTransaction. A pure surface change (for example,
fading a panel) never round-trips through WindowManager.
sequenceDiagram
participant Src as App launch / button event
participant WM as WindowManager / Shell
participant Delegate as PanelAutoTaskStackTransitionHandlerDelegate
participant Dispatch as EventDispatcher
participant Coord as PanelTransitionCoordinator
participant Surf as SurfaceFlinger
Src->>WM: Window change initiates transition
WM->>Delegate: handleRequest(transition, request)
Delegate->>Dispatch: Event from TransitionRequestInfo
Dispatch-->>Delegate: PanelTransaction
Delegate->>Coord: Build AutoTaskStackTransaction
Coord-->>Delegate: Transaction
Delegate-->>WM: Return transaction
WM->>Delegate: startAnimation(...)
Delegate->>Coord: Reconcile state + animate
Coord->>Surf: AutoSurfaceTransaction (alpha/scale/crop)
Scalable UI is where the secondary-display work also lands. The distant-display and
driver/distant-display ("dewd") system UIs wire the framework in explicitly. For example,
packages/services/Car/car_product/distant_display/apps/CarDistantDisplaySystemUI/ constructs its
initializer with setScalableUIWMInitializer(...) and setScalableUIEventDispatcher(...). The
packages/services/Car/car_product/dewd/ product carries its own Scalable UI sample RROs. As with
the shell pieces, Scalable UI is staged behind flags, so a given product can opt in per display.
MUMD products deliberately disable it on the per-occupant path while it matures.
62.5.4 Cross-Reference: Software Defined Vehicle¶
The windowing work above is the part of the 17 automotive story that lives inside CarService and Car
SystemUI. The larger architectural shift in this release is the Software Defined Vehicle (SDV)
platform. It is a separate, vehicle-spanning stack (under trees such as
system/software_defined_vehicle/, hardware/sdv/, and device/google/sdv*). It decouples vehicle
functions from fixed ECUs and runs services across virtualized domains. It also adds a gateway and
middleware layer between Android and the rest of the car.
SDV is a subsystem in its own right, not a CarService feature. Other sections of this chapter therefore cover it. Section 62.7 walks the SDV platform architecture (the headless Core VM, the service-bundle model, and the control-plane agents that supervise it). Section 62.8 walks the SDV middleware and vehicle communication fabric (VSIDL, the three middleware agents, SOME/IP, and the gateway).
For this chapter the takeaway is the boundary. CarService, the Vehicle HAL, occupant zones, and the
new Car-WindowManager-Shell/Scalable UI windowing stack remain the Android-side automotive
platform. The SDV sections (§62.7 and §62.8) pick up where the vehicle abstraction leaves off.
62.6 Android XR¶
Automotive, TV, and Wear are the form factors AOSP ships and supports as full vertical stacks today. Android 17 adds the first platform-level groundwork for a fourth one: Android XR. It is the head-worn headset and glasses form factor built on the Khronos OpenXR standard. It is easy to overstate this groundwork, so it is worth understanding precisely. The rest of this section backs up the blunt version file by file: Android XR is not merged into AOSP.
Android 17 upstreams only the API-usage surface XR apps compile against. This surface is the feature strings, manifest properties, tracking permissions and AppOps, the vendored OpenXR headers, and a few telemetry atom definitions. It is not a working XR stack. The AOSP 17 tree has no XR runtime, no OpenXR loader, no compositor, and no scene/spatial SDK. It also has no XR HAL and no XR device or emulator target.
Those pieces live off-tree. A vendor runtime on the device implements the OpenXR ABI, and the separately-distributed Jetpack XR SDK gives apps the high-level scene API. The sections below walk what AOSP 17 actually contains. They keep insisting on the distinction, because the upstream footprint is small enough to be mistaken for the whole thing.
62.6.1 What "Android XR" Is in AOSP 17 -- and What It Isn't¶
Almost the entire in-tree XR surface is hidden behind a single aconfig flag. The flag is declared
in frameworks/base/core/java/android/content/pm/xr.aconfig:
flag {
namespace: "xr"
name: "xr_manifest_entries"
description: "Adds manifest entries used by Android XR"
bug: "364416355"
is_exported: true
}
The flag is is_exported, so the @FlaggedApi("android.xr.xr_manifest_entries") annotation can be
applied to public API in frameworks/base. Its default value is disabled. There is no release
override for xr_manifest_entries anywhere under build/release. So on a stock AOSP 17 build the
flag stays off, and the XR API surface, permissions, and manifest properties it gates are inert. A
device that actually ships XR turns the flag on through its release configuration.
What is not in the tree is everything that would make a headset boot:
- No XR HAL. There is no
hardware/interfaces/xr/and no XR HAL AIDL/HIDL package. - No XR runtime or OpenXR loader. The compositor and the loader that resolves the OpenXR ABI to a runtime live on the device. The vendor supplies them (see Section 62.6.2).
- No XR system service. There is no XR equivalent of
CarServiceorTvInputManagerServiceunderframeworks/base/services; nothing implements anandroid.xr.*runtime service. - No XR device, lunch target, or emulator. AOSP 17 has no XR reference device and no XR emulator
image. (Do not mistake
emu64xrfor one. It is a RISC-V binary-translation emulator target, unrelated to XR. LikewiseIDvris TV tuner,VrrConfigis variable refresh rate,RtcpXris VoIP statistics, anduxris an automotive UX-restrictions abbreviation. None of these are Android XR.)
The headset context for this groundwork surfaces only in telemetry. The statsd atom file
frameworks/proto_logging/stats/atoms/xr/recorder/xr_recorder_extension_atoms.proto logs XR
recorder state changes. In its logging-source comment, it references the "Moohan" headset that the
first Android XR devices are built around. The atom is a logging definition, not a runtime. It is
another instance of the platform reserving vocabulary ahead of the device.
The intended division of labor mirrors how the other form factors handle hardware-specific stacks. The platform defines the contract: the OpenXR ABI via vendored headers, the feature strings apps declare, and the tracking permissions the framework enforces. A vendor runtime implements the OpenXR ABI on the device. The off-tree Jetpack XR SDK gives apps a high-level scene API on top. Only the contract lives in AOSP 17. Put plainly: Android 17 upstreams the API-usage surface XR apps compile against, not a working XR stack.
How small that footprint really is shows up in the 16-to-17 changeset. Across that window AOSP gains
exactly one new XR project: external/openxr-sdk (the OpenXR SDK pinned to release-1.1.50,
covered in Section 62.6.2). The platform commits that mention "spatial," "head-tracking," or
"passthrough" in the same window are almost all about spatial audio, not about XR. The relevant
code is the audio Spatializer (frameworks/av/services/audiopolicy) and
frameworks/av/media/libheadtracking (Section 62.6.8). In other words, the XR platform-code
footprint added in 17 is deliberately minimal. It is a vendored header package and a flag-gated
contract, and the runtime is left entirely to the vendor.
62.6.2 The OpenXR SDK in the Tree¶
The one piece of XR code vendored into AOSP 17 is the Khronos OpenXR SDK at
external/openxr-sdk/. Its METADATA pins the version to release-1.1.50, and
external/openxr-sdk/include/openxr/openxr.h defines the matching API version:
#define XR_MAKE_VERSION(major, minor, patch) \
((((major) & 0xffffULL) << 48) | (((minor) & 0xffffULL) << 32) | ((patch) & 0xffffffffULL))
#define XR_CURRENT_API_VERSION XR_MAKE_VERSION(1, 1, 50)
What matters for AOSP is what the build exposes. external/openxr-sdk/Android.bp declares exactly
one module:
That is the entire built surface -- the OpenXR headers, nothing more. The SDK's loader source
under external/openxr-sdk/src/loader/ is present in the checkout but is not built by
Android.bp: there is no cc_library for it. And grepping the whole AOSP 17 tree for modules that
depend on openxr_headers returns zero in-tree consumers -- no platform module compiles against
the OpenXR ABI.
Headers-only is the deliberate shape. The OpenXR ABI is a contract between an application (or a higher-level SDK) and a runtime. On a real headset the vendor supplies the runtime and the loader that finds it. A GPU vendor likewise supplies the Vulkan ICD, and AOSP does not ship one. AOSP vendors only the headers, so the standard XR ABI is available to native code that opts in. This does not commit AOSP to a particular runtime implementation.
62.6.3 The Flag-Gated XR API Surface¶
On top of the headers, Android 17 adds a public Java API surface. It lets an app declare what XR
capabilities it needs and describe how it wants to be presented. All of it is gated by
@FlaggedApi("android.xr.xr_manifest_entries"), so it is inert on a stock build. It is present in
frameworks/base/core/api/current.txt, so it is part of the public SDK contract.
The feature strings live in frameworks/base/core/java/android/content/pm/PackageManager.java. An
app names them in <uses-feature> to require an XR-capable device:
FEATURE_XR_API_OPENXR("android.software.xr.api.openxr") -- the device exposes an OpenXR runtime. Its declared version is encoded asmajor << 16 | minor(major in bits 31-16, minor in bits 15-0), so OpenXR 1.1 is0x00010001. The same encoding is used by the feature XML in Section 62.6.5.FEATURE_XR_API_SPATIAL("android.software.xr.api.spatial") -- the device supports the spatial (system-composited) presentation model.FEATURE_XR_INPUT_CONTROLLER("android.hardware.xr.input.controller"),FEATURE_XR_INPUT_HAND_TRACKING("android.hardware.xr.input.hand_tracking"), andFEATURE_XR_INPUT_EYE_TRACKING("android.hardware.xr.input.eye_tracking") -- the available XR input modalities.FEATURE_XR_PERIPHERAL("android.hardware.type.xr_peripheral") -- this one is gated by a separate flag,@FlaggedApi(com.android.microxr.Flags.FLAG_XR_GLASSES_FEATURE)("com.android.microxr.xr_glasses_feature"), reflecting the lighter glasses peripheral form factor rather than a full headset.
How an XR activity is presented is described with window properties in
frameworks/base/core/java/android/view/XrWindowProperties.java. The central one is
PROPERTY_XR_ACTIVITY_START_MODE ("android.window.PROPERTY_XR_ACTIVITY_START_MODE"), whose value
selects one of (each a full XR_ACTIVITY_START_MODE_* constant, abbreviated here by suffix):
FULL_SPACE_UNMANAGED-- the activity renders its own scene graph and "controls its own scene graph." This is the mode documented for activities that use OpenXR directly to draw their world.FULL_SPACE_MANAGED-- the system renders the activity from a scene graph it composites.HOME_SPACE-- the activity sits in the shared home-space environment alongside other windows.UNDEFINED-- the reset/default value.
Two more properties tune presentation: PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED (the recommended
play-area boundary type) and PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION (the app draws
its own enter/exit animation in managed full-space mode). Separately,
DisplayManager.DISPLAY_CATEGORY_XR_PROJECTED
("android.hardware.display.category.XR_PROJECTED",
frameworks/base/core/java/android/hardware/display/DisplayManager.java) lets a caller enumerate
projected XR displays.
This is purely declarative API. There is no android.xr.* runtime service or scene SDK in AOSP 17
to act on these declarations. The off-tree vendor stack is the runtime that reads the start mode
and composites the scene. The off-tree Jetpack XR SDK is the high-level scene API that apps write
against.
The following diagram shows the flag-gated XR contract. It shows what an app declares, the flag that gates it, and the platform's in-tree declarations. It also shows the off-tree pieces (shown dashed) that actually run the experience.
graph TB
subgraph App["XR Application (manifest)"]
UF["<uses-feature><br/>FEATURE_XR_API_OPENXR / SPATIAL<br/>FEATURE_XR_INPUT_* / XR_PERIPHERAL"]
PROP["<property><br/>PROPERTY_XR_ACTIVITY_START_MODE<br/>(FULL_SPACE_UNMANAGED / MANAGED / HOME_SPACE)"]
PERM["<uses-permission><br/>EYE/FACE/HAND/HEAD_TRACKING<br/>SCENE_UNDERSTANDING_*"]
end
FLAG["aconfig flag<br/>android.xr.xr_manifest_entries<br/>(disabled by default in AOSP)"]
subgraph Platform["AOSP 17 in-tree (contract only)"]
PM["PackageManager FEATURE_XR_* strings<br/>(core/api/current.txt)"]
XWP["XrWindowProperties<br/>start mode / boundary / animation"]
MAN["AndroidManifest.xml<br/>XR tracking permissions + groups"]
HDR["external/openxr-sdk<br/>openxr_headers (headers only)"]
end
subgraph Vendor["Off-tree (NOT in AOSP)"]
RT["Vendor OpenXR runtime + loader<br/>compositor"]
SDK["Jetpack XR SDK<br/>(scene / spatial APIs)"]
end
UF --> FLAG
PROP --> FLAG
PERM --> FLAG
FLAG --> PM
FLAG --> XWP
FLAG --> MAN
HDR -.ABI.-> RT
SDK -.app-facing.-> UF
RT -.implements contract.-> Platform
62.6.4 XR Privacy: Tracking Permissions and AppOps¶
The most concrete and consequential XR surface in AOSP 17 is the privacy machinery. A headset continuously senses the wearer's body and surroundings. It senses where the eyes look, the geometry of the face, and the pose of the hands. It also senses the orientation of the head and a reconstructed mesh of the room.
Android had no permissions for that class of data. So 17 introduces a dedicated XR tracking
permission family in frameworks/base/core/res/AndroidManifest.xml. Every entry carries
android:featureFlag="android.xr.xr_manifest_entries".
The runtime (dangerous) permissions -- the ones an app must request and the user must grant -- are:
| Permission | What it exposes |
|---|---|
EYE_TRACKING_COARSE / EYE_TRACKING_FINE |
gaze direction at two fidelities |
FACE_TRACKING |
facial geometry / expression |
HAND_TRACKING |
hand and finger pose |
HEAD_TRACKING |
head pose and orientation |
SCENE_UNDERSTANDING_COARSE / SCENE_UNDERSTANDING_FINE |
reconstructed environment geometry |
Alongside these, Android 17 defines XR-specific permission-groups: XR_TRACKING, plus the more
sensitive XR_EYE_SENSITIVE and XR_TRACKING_SENSITIVE. The sensitive groups are intended for the
eye and fine-tracking data, which are especially identifying, so this data can be surfaced
separately from coarse tracking. (In the manifest the individual permissions still sit in the
UNDEFINED group. The XR grouping is applied at runtime, not via a static permissionGroup
attribute.) A second tier of signature|privileged permissions is reserved for the system and OEM
components: EYE_CALIBRATION, FACE_TRACKING_CALIBRATION, IMPORT_XR_ANCHOR, and
XR_TRACKING_IN_BACKGROUND. The last one is the right to keep tracking the wearer while not in the
foreground, which is exactly why it is privileged.
Each runtime tracking permission is paired with an AppOp in
frameworks/base/core/java/android/app/AppOpsManager.java. The framework can then audit and revoke
access at runtime, per app, the same way it does for camera and location:
| AppOp constant | Op string |
|---|---|
OP_EYE_TRACKING_COARSE / OP_EYE_TRACKING_FINE |
android:eye_tracking_coarse / android:eye_tracking_fine |
OP_FACE_TRACKING |
android:face_tracking |
OP_HAND_TRACKING |
android:hand_tracking |
OP_HEAD_TRACKING |
android:head_tracking |
OP_SCENE_UNDERSTANDING_COARSE / OP_SCENE_UNDERSTANDING_FINE |
android:scene_understanding_coarse / android:scene_understanding_fine |
XR needs an entirely new permission class because its data is qualitatively different from camera or sensor data. Eye gaze is biometric and reveals attention and intent. Face and hand tracking reconstruct the body. Scene understanding builds a persistent 3D map of the user's home. None of these map cleanly onto "take a photo" or "read the accelerometer," and they carry distinct consent and retention concerns. So the platform models each as its own permission with its own AppOp.
The permissions and AppOps ship in 17 even though no in-tree runtime produces the data yet. The privacy contract is deliberately in place before any device can sense it.
The following diagram shows the body- and environment-tracking permission-to-AppOp model and where the (off-tree) runtime sits relative to it.
graph LR
subgraph Sensing["What a headset senses"]
EYE["Gaze"]
FACE["Face geometry"]
HAND["Hand pose"]
HEAD["Head pose"]
SCENE["Room mesh"]
end
subgraph Perms["Dangerous permissions (AndroidManifest.xml)"]
PE["EYE_TRACKING_COARSE/FINE"]
PF["FACE_TRACKING"]
PH["HAND_TRACKING"]
PHd["HEAD_TRACKING"]
PS["SCENE_UNDERSTANDING_COARSE/FINE"]
end
subgraph Ops["AppOps (AppOpsManager.java)"]
OE["OP_EYE_TRACKING_*"]
OF["OP_FACE_TRACKING"]
OH["OP_HAND_TRACKING"]
OHd["OP_HEAD_TRACKING"]
OS["OP_SCENE_UNDERSTANDING_*"]
end
RT["Off-tree vendor OpenXR runtime<br/>(produces the data; NOT in AOSP)"]
EYE --> PE --> OE
FACE --> PF --> OF
HAND --> PH --> OH
HEAD --> PHd --> OHd
SCENE --> PS --> OS
RT -.guarded by.-> Ops
62.6.5 Feature Declarations and Device Opt-In¶
For the <uses-feature> strings of Section 62.6.3 to mean anything, a device has to declare the
matching system features. The declaration XML for that lives in frameworks/native/data/etc/:
| File | Feature declared | Version |
|---|---|---|
android.software.xr.api.openxr-1_0.xml |
android.software.xr.api.openxr |
65536 (= 0x00010000, OpenXR 1.0) |
android.software.xr.api.openxr-1_1.xml |
android.software.xr.api.openxr |
65537 (= 0x00010001, OpenXR 1.1) |
android.software.xr.api.openxr-1_2.xml |
android.software.xr.api.openxr |
65538 (= 0x00010002, OpenXR 1.2) |
android.software.xr.api.spatial-1.xml |
android.software.xr.api.spatial |
1 |
android.hardware.xr.input.controller.xml |
android.hardware.xr.input.controller |
(none) |
android.hardware.xr.input.hand_tracking.xml |
android.hardware.xr.input.hand_tracking |
(none) |
android.hardware.xr.input.eye_tracking.xml |
android.hardware.xr.input.eye_tracking |
(none) |
The OpenXR version attribute is the same major << 16 | minor integer encoding used by
FEATURE_XR_API_OPENXR. A device declares the highest OpenXR version its runtime supports, and
PackageManager lets an app require at least that version.
Crucially, AOSP installs none of these by default. They are not listed in the permission
makefiles that base and handheld products copy. They sit in frameworks/native/data/etc/ purely as
opt-in fragments. An XR device pulls the ones it supports into its system image with
PRODUCT_COPY_FILES in its own device makefile. Every optional hardware feature uses the same
mechanism.
There is no XR HAL, no XR input backing, and no lunch target in AOSP 17. So declaring these features only becomes meaningful on a vendor build that also supplies the runtime. On a stock build there is nothing to declare them against.
62.6.6 MicroXR: The XR Glasses Split¶
Everything in Sections 62.6.1 through 62.6.5 is the headset story, gated by the android.xr
flag xr_manifest_entries. Android 17 carves out a second, lighter form factor at the flag level:
XR glasses, which ride a separate aconfig package, com.android.microxr.
The split starts in the flag itself. The glasses flag is declared in a file of its own,
frameworks/base/core/java/android/content/pm/glasses.aconfig:
package: "com.android.microxr"
container: "system"
flag {
namespace: "xr"
name: "xr_glasses_feature"
description: "Adds features used by Android XR Glasses"
bug: "430302860"
is_exported: true
}
Like xr_manifest_entries, xr_glasses_feature is is_exported (so it can gate public API) and is
disabled by default. There is no release override for it under build/release, so on a stock
AOSP 17 build the glasses surface is inert. The two flags are independent. Full XR headsets ride
android.xr / xr_manifest_entries (Section 62.6.3), and XR glasses ride com.android.microxr /
xr_glasses_feature.
The build wiring sits in frameworks/base/AconfigFlags.bp, under a // XR - Glasses comment. It
declares com.android.microxr.flags-aconfig from core/java/android/content/pm/glasses.aconfig. It
also declares a com.android.microxr.flags-aconfig-java library whose apex_available lists both
the platform and com.android.permission. So the glasses flag is reachable from the permission
module, not just the framework.
The one public API the glasses flag gates is a device-class feature string in
frameworks/base/core/java/android/content/pm/PackageManager.java (around lines 4596-4604):
/**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: An XR peripheral is defined as a full stack Android device with
* or without a display, with or without inputs, and no user-installable apps. XR peripherals
* are worn on the user's body and likely require a companion device for user interactions.
*/
@FlaggedApi(com.android.microxr.Flags.FLAG_XR_GLASSES_FEATURE)
@SdkConstant(SdkConstantType.FEATURE)
public static final String FEATURE_XR_PERIPHERAL = "android.hardware.type.xr_peripheral";
Two things about FEATURE_XR_PERIPHERAL matter. First, it is gated by
com.android.microxr.Flags.FLAG_XR_GLASSES_FEATURE ("com.android.microxr.xr_glasses_feature"),
not by the headset flag. Second, and easy to miss, it is an android.hardware.type.* string. It is
a device-class marker, a peer of FEATURE_PC (android.hardware.type.pc), FEATURE_WATCH, and
FEATURE_TELEVISION. It is not an android.software.xr.* / android.hardware.xr.* capability
like the headset features in Section 62.6.3.
It says "this device is an XR peripheral," the way other features say "this device is a PC" or "a
watch." Unlike the headset capability features, it ships no opt-in feature XML in
frameworks/native/data/etc/. There is no android.hardware.type.xr_peripheral.xml fragment for a
device to copy in. So a glasses device declares the class through its own configuration, and does
not pull in a stock fragment.
Where in-tree code actually acts on the glasses class¶
FEATURE_XR_PERIPHERAL is almost entirely a contract, but not only a contract. Nothing in AOSP
reads the headset feature strings. But a handful of modules already branch on the XR peripheral
class. Each treats glasses as a constrained device alongside WATCH, TV, and AUTOMOTIVE:
- Wi-Fi.
WifiGlobalscaches the class at construction:mIsXrPeripheral = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_XR_PERIPHERAL)(packages/modules/Wifi/service/java/com/android/server/wifi/WifiGlobals.java:104), then consults the flag to tune Wi-Fi behavior for the peripheral. - Bluetooth.
Util.isXrDevice()(packages/modules/Bluetooth/android/app/src/com/android/bluetooth/Util.kt:266) is defined right next toisTv()andisWatch()and resolves tohasSystemFeature(FEATURE_XR_PERIPHERAL). Its consumer,media_audio/sink/MediaAudioServer.kt:605, lumps XR devices with IoT and TV devices (if (context.isIotDevice() || context.isTv() || context.isXrDevice())) when deciding media-audio sink behavior. - MediaProvider.
ProcessingUtils(packages/providers/MediaProvider/src/com/android/providers/media/localsearch/ProcessingUtils.java:112) disables on-device media processing on XR peripherals. It listsFEATURE_XR_PERIPHERALalongsideFEATURE_WATCH,FEATURE_AUTOMOTIVE,FEATURE_TELEVISION, andFEATURE_EMBEDDED. These are device types that "can possibly support UI for search services" only on phones, tablets, and PCs.
That is the whole of it: three modules that special-case glasses as a stripped-down device class. There is no MicroXR runtime, service, app, HAL, or device target behind the flag. The behavior is "do less" gating, the same shape as the existing WATCH/TV/AUTOMOTIVE checks.
MicroXR telemetry: the glasses counterpart to the headset recorder atoms¶
The headset groundwork surfaces only as the "Moohan" recorder atoms (Section 62.6.1). In contrast,
the glasses groundwork surfaces as a parallel statsd atom family under
frameworks/proto_logging/stats/atoms/microxr/microxr_extension_atoms.proto (with shared enums in
frameworks/proto_logging/stats/enums/microxr/enums.proto). These atoms read as a portrait of the
glasses hardware Google plans to ship:
- Wear state.
MicroXrDonDoffStateChangedlogs aDonDoffStateofDONNED(worn),DOFFED(not worn), orDISABLED-- the don/doff detection a body-worn device needs. - Capture.
MicroXrPhotoCaptured/MicroXrVideoCapturedlog captures triggered by a button press on the glasses or by a voice command (PhotoOrVideoTrigger=BUTTON/VOICE). They also log latency through a "Darklight" HDR capture pipeline and an HDR-vs-non-HDRCaptureMode. - A separate MCU co-processor. A cluster of atoms reports the glasses' microcontroller as a
distinct chip from the application processor. They are
MicroXrMcuCrashOccurred(crash dump or watchdog failure),MicroXrMcuPowerDeepSleepInfo,MicroXrMcuBootTimeReported, andMicroXrMcuMemorySnapshotReported. - Power and updates.
MicroXrApWakeupReported(the MCU waking the application processor) andMicroXrOtaReported(over-the-air update outcomes) round out the set.
This is Google/vendor glasses telemetry in proto_logging. It consists of statsd definitions, not
a public framework API that an app can call. Exactly as with the headset side, there is no
MicroXR module, service, app, HAL, or device target anywhere in AOSP to produce or consume it. The
atoms reserve the logging vocabulary ahead of the hardware, the same pattern the headset XR contract
follows.
62.6.7 The Legacy VR Framework Android XR Succeeds¶
Android XR is not the platform's first attempt at head-worn computing. The earlier Daydream-era
VR framework is, by Android 17, almost entirely gone. The android.hardware.vr@1.0 IVr HAL was
removed -- there is no hardware/interfaces/vr/ tree (only a stale name in a Watchdog HAL-monitor
list remains). The orchestrating VrManagerService is gone from frameworks/base/services; only
the internal VrManagerInternal interface used by ActivityManager/WindowManager survives.
What remains is a set of inert API remnants kept for source compatibility:
android.app.VrManager(frameworks/base/core/java/android/app/VrManager.java) -- still a@SystemApi, but its calls reach a system service that no longer exists.android.service.vr.VrListenerService-- the abstract service apps once extended.Vr2dDisplay(frameworks/base/services/core/java/com/android/server/vr/Vr2dDisplay.java) -- the virtual display that hosted 2D apps inside a VR scene.PackageManager.FEATURE_VR_MODE("android.software.vr.mode"), now@Deprecated, plus the still-declaredFEATURE_VR_MODE_HIGH_PERFORMANCE("android.hardware.vr.high_performance") andFEATURE_VR_HEADTRACKING("android.hardware.vr.headtracking"), which no AOSP device populates.Activity.setVrModeEnabled(...)(frameworks/base/core/java/android/app/Activity.java) still exists. It routes throughActivityClient.setVrModeand throwsPackageManager.NameNotFoundExceptionif the requestedVrListenerServiceis not installed or enabled. With the VR HAL and service removed, the path no longer activates a VR mode.
The contrast is the point. The old VR framework was an Android-side mode toggle layered on a thin HAL. Android XR is architecturally distinct. It is built on the cross-vendor OpenXR ABI and defines a body- and environment-tracking privacy model.
It also pushes the runtime and compositor entirely to the vendor side. The 17 tree reflects a clean break. The Daydream-era code is being retired to inert remnants, while the OpenXR-based contract is laid down fresh.
62.6.8 Cross-Reference: Head-Tracked Spatial Audio¶
One subsystem XR builds on does already ship in AOSP, and it predates XR: the audio Spatializer.
Chapter 15 (Audio System, Section 15.2.17) covers the SpatializerThread. This is a specialized
MixerThread in AudioFlinger. It renders spatial audio and drives HAL latency modes for low-latency
head-tracked audio.
Head-tracked spatialization is exactly the kind of platform capability an XR runtime composes with. A headset wants room-anchored sound that stays fixed as the wearer turns their head. It is not XR-exclusive: the Spatializer serves regular phones, tablets, and TVs with spatial-audio output and head-tracking headphones. It is an example of an adjacent, already-real subsystem that Android XR will lean on. It is not a piece of the XR groundwork that 17 newly added.
62.6.9 What's Next: Predicting Android XR's Path into AOSP¶
Everything above is what AOSP 17 contains. This section is the opposite: it is an informed prediction of how the rest of the stack might land upstream. The prediction comes from what 17 already stages and what is conspicuously missing. It is forward-looking, not a published roadmap. Google has not committed to any of it in the tree. So treat each item as "this is the shape the gaps suggest," hedged with likely and would, not as a statement of plans.
The reasoning is straightforward. Android 17 lays down a contract with no implementation, and a contract with no implementation usually precedes the implementation. These are the most likely next steps, each tied to something already half-present:
- The flags flip on.
xr_manifest_entries(headsets) andxr_glasses_feature(glasses) are bothis_exportedand default-off with no release override (Sections 62.6.1, 62.6.6). The cheapest possible change once devices ship is a release override that flips them on for XR products. This change activates the API surface that is already merged. It needs no new code at all. - A loader/runtime built from the vendored SDK.
external/openxr-sdkships the OpenXR headers only. The loader source is present in the checkout but not built byAndroid.bp(Section 62.6.2). The obvious next move is acc_library(or similar) that actually compiles the OpenXR loader from that already-vendored source. This gives AOSP an in-tree way to resolve the OpenXR ABI to a runtime. Today the vendor supplies both. - An XR HAL under
hardware/interfaces/. There is nohardware/interfaces/xr/today (Section 62.6.1). A device-tracking/compositor HAL contract is the natural place to specify the platform-vendor boundary, the way every other form factor has one. If XR is to be more than a vendor black box, anxr(ormicroxr) HAL package would likely appear here. - Real compositor support in WindowManager/SurfaceFlinger.
XrWindowPropertiesalready lets an app declareFULL_SPACE_UNMANAGED/FULL_SPACE_MANAGED/HOME_SPACEstart modes (Section 62.6.3), but nothing in-tree composites them. Honoring those modes would mean WindowManager and SurfaceFlinger gaining genuine full-space / home-space compositor paths behindXrWindowProperties, rather than the declarations being inert. - A scene/spatial system service. The
android.xr.*runtime and scene classes live only in the off-tree Jetpack XR SDK today. AOSP has no XR system service (Section 62.6.1). Suppose the scene model is to be a platform guarantee, not an app-bundled library. Then some of that runtime would likely move, or be mirrored, into a system service underframeworks/base/services.CarServiceandTvInputManagerServiceplay this role for their form factors. - Feature XMLs installed by default. No product copies the opt-in fragments in
frameworks/native/data/etc/today (Section 62.6.5). An XR reference product would add them to itsPRODUCT_COPY_FILES. The glasses class has no fragment at all today (Section 62.6.6). It would likely gain one if it is to be declared the stock way. - An XR device / emulator (lunch) target. There is no XR reference device or emulator image in 17, which means there is nothing to run XR CTS against (Section 62.6.1). A lunch target (a reference device or an emulator image) is what would let the contract above be tested in CI. So it is a plausible precondition for any of the rest to be exercised upstream.
None of this is promised by the 17 tree. The point of this list is that the absences are specific and the staged pieces are specific. So the missing layers can be named precisely: a built loader, a HAL, compositor paths, a system service, default-installed feature XMLs, and a test target. If and when Android XR lands in AOSP as a vertical stack, these are the slots it would fill. Automotive, TV, and Wear already ship this way.
62.7 Software Defined Vehicle: Architecture¶
Android 17's marquee device-support addition is the Software Defined Vehicle (SDV) platform. It includes an entire new top-level source tree (system/software_defined_vehicle/) and a flagship reference device (device/google/sdv). It also includes a new HAL/AIDL contract package (hardware/sdv/interfaces) and an automotive display-safety service (packages/services/display_safety). SDV is not "Android in the dashboard" the way Android Automotive OS (AAOS) is. It is a headless vehicle operating system. A "Core" VM runs the vehicle's safety- and power-relevant services with no UI at all.
One or more AAOS In-Vehicle Infotainment (IVI) VMs, plus non-Android automotive ECUs, talk to the Core VM over a service fabric. The Core VM has no display, no launcher, and no apps in the AAOS sense. It hosts service bundles, units of vehicle functionality described in a new interface language and generated to Rust. A lifecycle manager and an orchestrator supervise the bundles. They bring bundles up and down as the vehicle changes power and driving state.
This section is the architecture overview. It walks the headless Core VM, the service-bundle model, and the VSIDL-to-Rust toolchain. It then covers the four control-plane agents that supervise bundles (orchestration, lifecycle management, the service-bundles registry, and the health monitor). It also covers the update manager, the vehicle power-state manager (vpm), and the automotive display-safety runtime. Last, it shows how an AAOS IVI VM integrates through the SDV Gateway.
Section 62.8 covers the wire-level transport: the middleware comm stack, SOME/IP, the VSIDL grammar, and the gateway's network plumbing. This section cross-references that material and does not duplicate it.
62.7.1 What "Software Defined Vehicle" Means Here¶
The Headless Core VM¶
The defining architectural decision of SDV is that the vehicle's services run in a VM with no user interface. device/google/sdv/sdv_core_base/sdv_core_base.mk states it directly in its header comment: "Software-Defined Vehicle (SDV) is a headless vehicle Android OS." The Core VM is Android. It boots init, it runs Binder, and it uses APEX modules. But it ships no SystemUI, no launcher, and no AAOS app stack. Its job is to host vehicle service bundles and the agents that supervise them.
The Cuttlefish targets make the headlessness concrete. SDV is designed to run as multiple cooperating VMs on one host: a Core VM and one or more IVI VMs. device/google/sdv/cuttlefish_multi_tenancy/ carries example multi-VM launch configurations. They boot several VM instances.
Each instance gets a distinct androidboot.sdv.instance_name via bootconfig, and each runs with no GPU (gpu_mode: "none") because there is nothing to draw. The VMs reach each other over a virtual network. SDV-RPC traffic is pinned to a dedicated VLAN named by the androidboot.sdv.rpc.interface bootconfig property (default sdv_rpc). It is configured through the SDV_RPC_INTERFACE build variable (device/google/sdv/sdv_core_base/BoardConfig.mk; system/software_defined_vehicle/sdv_gateway/README.md).
The Four Trees¶
SDV is deliberately spread across four locations in the tree, each with a distinct role:
system/software_defined_vehicle/— the platform itself: 16 subrepos holding the agents, the middleware comm stack, the VSIDL toolchain, and shared libraries. This is where the running code lives.device/google/sdv— the reference device. It composes the platform code into lunch targets (sdv_core_base,sdv_ivi_base, and friends) and decides which agents and APEXes land in which VM.hardware/sdv/interfaces— the stable contract package. Every cross-process boundary that needs version stability has its@VintfStabilityAIDL frozen here underaidl_api/. These boundaries are the gateway, the registry, the lifecycle internal interface, vpm, telemetry, and the RPC agent.packages/services/display_safety— the automotive Driver-UI runtime ("HARry") and its safety monitor, which runs on the IVI side.
This section treats system/software_defined_vehicle/ and the device/HAL composition; Section 62.8 treats the middleware and SOME/IP subrepos in depth.
The Layering¶
The high-level picture is a vehicle-service fabric beneath and beside AAOS. Bundles run in the Core VM, and the comm stack carries their traffic. SOME/IP bridges across VMs and ECUs. An AAOS IVI VM reaches the fabric through the SDV Gateway.
The SDV platform stack and the seam to AAOS
graph TB
subgraph CoreVM["SDV Core VM (headless, no UI)"]
ORCH["Orchestration agent<br/>(orch_config.textproto)"]
LCM["Lifecycle Manager<br/>+ bundle runner"]
REG["Service Bundles Registry<br/>(IRegistry)"]
HM["Health Monitor<br/>(heartbeats)"]
UM["Update Manager"]
VPM["Vehicle Power Manager<br/>(sdv_vpm_agent)"]
BUNDLE["Service bundles<br/>(VSIDL-generated Rust)"]
end
subgraph Comm["SDV middleware comm stack (Section 62.8)"]
SD["Service Discovery<br/>(sd_agent)"]
DT["Data Tunnel<br/>(pub/sub)"]
RPC["RPC agent<br/>(IRpcAgent)"]
end
subgraph Wire["Cross-VM / cross-ECU (Section 62.8)"]
SOMEIP["SOME/IP stack<br/>+ broker (vsomeip)"]
ECU["External ECUs<br/>(non-Android)"]
end
subgraph IVI["AAOS IVI VM"]
GW["SDV Gateway<br/>(ISdvGateway + vhal_proxy)"]
VHAL["Vehicle HAL<br/>(sdv-emulator-service)"]
CAR["CarService<br/>(packages/services/Car)"]
DS["Display Safety / HARry<br/>(packages/services/display_safety)"]
end
ORCH --> LCM
LCM --> BUNDLE
REG --- ORCH
VPM --> ORCH
BUNDLE --> SD
BUNDLE --> DT
BUNDLE --> RPC
HM -.heartbeats.-> BUNDLE
SD --> SOMEIP
DT --> SOMEIP
RPC --> SOMEIP
SOMEIP <--> ECU
GW --> RPC
GW --> DT
VHAL --> GW
CAR --> VHAL
DS --> GW
62.7.2 The Service Bundle Model¶
What a Service Bundle Is¶
The unit of deployment in the Core VM is the service bundle, not the Android app or the standalone daemon. A service bundle is a shared library, VSIDL-generated Rust compiled to a .so. The lifecycle manager loads it into a host process and drives it through a fixed lifecycle. Bundles ship inside APEX modules. The APEX carries the bundle's native library plus a manifest entry that describes where everything lives.
The metadata contract is the SdvServiceBundleManifest proto (system/software_defined_vehicle/service_bundles_registry/proto/sdv_service_bundles_manifest.proto). Each entry carries the bundle name, a version_number and version_name, the native_library_path (relative to the APEX root), and a set of optional config paths. These are orchestration_config_path, scheduling_config_path, health_config_path, diagnostics_config_path, user_config_path, vsidl_schemas_path, external_protocol_mapping_path (the SOME/IP mapping), and authorization_policy_path (which superseded the deprecated access_control_list_policy_path). The proto reserves three field-number ranges by audience. Low numbers are for bundle execution, a middle range is for the SDV agents, and a high range is for OEM custom metadata. So a bundle's manifest never collides between layers.
VSIDL Generates Rust¶
Service interfaces are not written in AIDL. They are described in .vsidl service-bundle definitions plus .proto message schemas. The vsidlc compiler (system/software_defined_vehicle/vsidl/vsidlc) walks the catalog and emits Rust middleware bindings into generated_rs/ directories. The companion someip_translation_generator emits the SOME/IP-to-proto translation code. vsidl_rc_generator (the "Runtime Configuration generator") emits the runtime-configuration outputs that bundles consume through the VSIDL provider library (libsdv_config_provider).
All three are host tools installed by the Core target (device/google/sdv/sdv_core_base/sdv_packages_core_services.mk lists vsidlc, vsidl_rc_generator, and someip_translation_generator under SDV_CORE_SERVICES_HOST_PACKAGES). On-device, the sdv_vsidl_provider_agent (APEX com.android.sdv.vsidl_provider) serves bundle VSIDL schemas at runtime. This is the SDV equivalent of AIDL stub generation. Section 62.8 covers the full grammar and transport mapping.
A concrete .vsidl makes the shape clear. The vehicle power manager's bundle definition (system/software_defined_vehicle/vpm/stable/vsidl/vpm.vsidl) declares a service_bundle named VpmSystemServiceBundle with three interface slots. These are a server that exports the RPC service com.android.sdv.vpm.VpmSystemService, a client of com.android.sdv.vpm.client.PowerNotificationService, and a Data Tunnel publisher of com.android.sdv.vpm.vehicle.VehicleStateChange. A bundle therefore declares, in one place, what it serves over RPC, what it consumes, and what it publishes on the pub/sub fabric.
Fully Qualified Instance Names¶
Bundles can run in multiple instances, so SDV identifies a running unit by a Fully Qualified Instance Name (FQIN). In the orchestrator's common crate (system/software_defined_vehicle/orchestration/common/src/fqin.rs) an FQIN is three fields, package_name, service_bundle_name, and instance_name, formatted as package/bundle/instance. The orchestrator's FQIN converts to the lifecycle manager's ServiceFqin representation. This representation also carries a VM name (local-vm for the current VM).
The FQIN is the key the control-plane agents use everywhere. The lifecycle manager keys its process table by it. The orchestrator keys its desired-state map by it. The health monitor keys heartbeat tracking by it.
The service-bundle model, from APEX to running instance
flowchart TD
subgraph Author["Authoring (host)"]
VSIDL[".vsidl + .proto catalog"]
VSIDLC["vsidlc / someip_translation_generator"]
RS["generated_rs/<br/>(Rust bindings)"]
VSIDL --> VSIDLC --> RS
end
subgraph Pkg["Packaging"]
SO["bundle .so<br/>(native_library_path)"]
MAN["SdvServiceBundleManifest<br/>(name, version, config paths)"]
APEX["APEX module"]
RS --> SO
SO --> APEX
MAN --> APEX
end
subgraph Run["Core VM runtime"]
REG2["Service Bundles Registry<br/>scans APEXes -> caches metadata"]
ORCH2["Orchestrator<br/>desired state per FQIN"]
LCM2["Lifecycle Manager<br/>launch / start / stop / shutdown"]
RUNNER["lifecycle_service_bundle_runner<br/>(one process per instance)"]
APEX --> REG2
REG2 --> ORCH2
REG2 --> LCM2
ORCH2 --> LCM2
LCM2 --> RUNNER
RUNNER --> INST["Loaded bundle instance<br/>(FQIN package/bundle/instance)"]
end
62.7.3 The Service Bundles Registry¶
The first control-plane agent in the boot order is the Service Bundles Registry. Its README (system/software_defined_vehicle/service_bundles_registry/README.md) gives it three jobs. First, it scans, detects, and caches the metadata of locally available SDV service bundles. Second, it verifies their security restrictions. Third, it provides that cached metadata to a limited set of SDV agents and automotive services.
The registry is the catalog the rest of the control plane reads from. The orchestrator and the lifecycle manager both ask the registry "what bundles exist and where are their config files" before they can do anything.
The registry's public interface is the stable, @VintfStability IRegistry.aidl (hardware/sdv/interfaces/service_bundles_registry/google/sdv/service_bundles_registry/IRegistry.aidl). The frozen contract is small: a single method getAvailableServiceBundlesMetadata() that returns a List<ServiceBundleMetadata>. The interface is currently at frozen API version 3 (the versioned snapshots live under hardware/sdv/interfaces/service_bundles_registry/aidl_api/google.sdv.service_bundles_registry/, and 3/ is the current frozen version). The ServiceBundleMetadata parcelable mirrors the manifest proto. It carries name, versionNumber, versionName, packageName, nativeLibraryPath, and the nullable config-path fields (orchestrationConfigPath, healthConfigPath, authorizationPolicyPath, vsidlSchemasPath, and the rest), plus a customMetadata array of KeyValuePair.
The agent binary is sdv_service_bundles_registry_agent. It is installed by the Core target and registered as a system service. It is not shipped in its own APEX. When the registry has finished its scan and is ready to serve, it registers the binder service descriptor google.sdv.service_bundles_registry.IRegistry/default. It then sets the system property ro.sdv.sbr.state.ready to true. This is a readiness signal the other agents wait on (system/software_defined_vehicle/service_bundles_registry/src/registry/binder.rs).
62.7.4 Lifecycle Management¶
The Lifecycle Manager's Job¶
The Lifecycle Manager (system/software_defined_vehicle/lifecycle_management/) is the agent that actually launches, starts, stops, and kills bundle processes. Its README describes it as the central dispatcher through which SDV agents control service-bundle lifecycles. Other agents — chiefly the orchestrator — drive it through the ILifecycleManager AIDL (system/software_defined_vehicle/lifecycle_management/aidl/google/sdv/lifecycle/ILifecycleManager.aidl):
launchService(ServiceFqin)— bring a bundle to the CREATED state (loaded, constructed, but not yet running).startService(ServiceFqin)— transition CREATED to STARTED (running).stopService(ServiceFqin)— transition STARTED back to CREATED (stopped but still loaded).shutdownService(ServiceFqin)— gracefully bring a bundle to DESTROYED.killService(ServiceFqin)— forcefully stop a bundle.getServiceBundleState(ServiceFqin)— return the currentIServiceBundleState.
The two persistent states a loaded bundle can hold are defined in IServiceBundleState.aidl as an int-backed enum. CREATED = 1 is reached after onCreate or after onStop. STARTED = 2 is reached after onStart. Error returns use the ResponseCode enum (SERVICE_NOT_FOUND, PERMISSION_DENIED, OPERATION_FAILED, VALUE_CORRUPTED, INVALID_ARGUMENT, INTERNAL_ERROR).
The Bundle Lifecycle and the Runner¶
A bundle's own code sees the lifecycle through the IService interface, which is frozen in the stable HAL package (hardware/sdv/interfaces/lifecycle_management/aidl/google/sdv/lifecycle/internal/IService.aidl). It is four callbacks that mirror a native constructor/destructor pair around a start/stop pair: onCreate(), onStart(), onStop(), onDestroy(). onCreate and onDestroy are guaranteed to be called exactly once; onStart/onStop can cycle. The matching IServiceManager (same package) is how a bundle process registers itself back with the manager: registerService(ServiceFqin, IService), unregisterService(ServiceFqin), and getPid().
The mechanism that turns a bundle library into a running process is the lifecycle_service_bundle_runner. The lifecycle agent does not load bundle code into its own address space. Instead, for each instance it spawns a fresh lifecycle_service_bundle_runner process. It passes the bundle's native-library path and the FQIN as arguments (system/software_defined_vehicle/lifecycle_management/service_bundle_runner/src/main.rs).
The runner dynamically loads the bundle .so from its APEX and starts a binder thread pool. It registers an IService back with the manager via IServiceManager. Then it runs the bundle's executor on its main thread. When the agent launches an instance, it allocates a per-bundle user ID, creates the bundle's data directory, applies the SELinux domain, and spawns the runner. It then waits (with a registration timeout) for the runner to register before it considers the launch successful (system/software_defined_vehicle/lifecycle_management/src/lifecycle_manager/agent.rs).
Isolation is therefore per instance: one process, one UID, one SELinux context per running bundle. The agent binary sdv_lifecycle_agent and the lifecycle_service_bundle_runner are both system binaries installed by the Core target.
The service-bundle lifecycle state machine
stateDiagram-v2
[*] --> Destroyed
Destroyed --> Created : launchService -> onCreate
Created --> Started : startService -> onStart
Started --> Created : stopService -> onStop
Created --> Destroyed : shutdownService -> onDestroy
Started --> Destroyed : shutdownService -> onStop then onDestroy
Created --> Destroyed : killService forced
Started --> Destroyed : killService forced
Destroyed --> [*]
62.7.5 Orchestration¶
Desired State, Not Imperative Control¶
The Orchestrator is the brain of the Core VM control plane. The lifecycle manager is imperative ("launch this, start that"), but the orchestrator is declarative. It holds a desired lifecycle state for every bundle instance. It recomputes that state whenever the vehicle changes mode. Then it drives the lifecycle manager until reality matches.
Its README (system/software_defined_vehicle/orchestration/README.md) calls it "an SDV agent responsible for managing the lifecycle of service bundles based on orchestrator configurations." The binary is sdv_orchestration_agent, shipped in the APEX com.android.sdv.orchestrator.
At startup the orchestrator's Agent::new() (system/software_defined_vehicle/orchestration/engine/src/agent.rs) reads a VM-level config path from the system property persist.sdv.orchestrator_config_path. It fetches per-bundle orchestration configs from the registry and builds an Evaluator from the combined configuration. It then restores the previously persisted modes from backup and constructs the OrchestratorEngine. The engine sets ro.sdv.orchestrator.state.ready once it is up.
Modes, Conditions, and the Evaluator¶
Orchestration configuration is textproto. The schema lives in system/software_defined_vehicle/orchestration/distributed_config/src/protos/ as vm_config.proto, service_bundle_config.proto, and the shared common.proto. A ServiceBundleConfig lists the bundle's instances and a set of InstancesStateConfiguration entries. Each entry pairs an optional condition with the instance states it wants (created, started, destroyed).
The condition is a boolean expression over the system's current modes. common.proto defines Condition as a oneof of power_state, vehicle_state, custom_state, and the logical operators not/and/or. The Rust side mirrors this with a Condition enum (distributed_config/src/condition.rs) that carries State { mode, state }, And, Or, Not, and Empty (always true). Conditions are evaluated with three-valued logic, so partially-undefined conditions behave sanely.
A mode is what the orchestrator tracks (orchestration/common/src/mode.rs): Power, Vehicle, and OEM-defined Custom(String). When a mode changes, the Evaluator updates its orchestration state (with timestamp validation so stale updates are dropped). Then it evaluates every bundle's conditions to produce a fresh HashMap<Fqin, InstanceState> of what each instance should be. Where several conditions apply to one instance, the states merge by precedence: Destroyed beats Started beats Created (orchestration/common/src/instance_state.rs). The "config can live VM-wide or per-bundle" design is why this subtree is named distributed config (orchestration/distributed_config/README.md). A bundle ships its own slice of orchestration policy in its APEX, and the orchestrator stitches the slices together at startup.
Enforcement, Crashes, and Retry¶
The engine's enforce_instances_states() (orchestration/engine/src/engine.rs) reconciles desired state against reality. It calls the lifecycle manager concurrently across instances up to a thread cap (default 12). Each instance is managed by an InstanceManager (orchestration/engine/src/instance_manager.rs). It tracks the last known state and classifies failures as transient (retry), persistent (kill, then retry), or permanent (give up). Retries are bounded by a RetryConfiguration (orchestration/common/src/retry_configuration.rs) that carries max_retries.
The orchestrator publishes instance state outward through IOrchestrationAgent.aidl. A subscriber registers an IServiceBundleInstanceStateChangeListener, and the orchestrator calls back onServiceBundleInstanceStateChanged(fqin, newRecoveryState, expectedLifecycleState). Two enums carry that state. ServiceBundleInstanceLifecycleState is the intent: STARTED, CREATED, or DESTROYED. ServiceBundleInstanceRecoveryState is the health: OPERATIONAL (reached its required state), RETRYING (recovering from a crash or a failed transition), or RETRY_FAILED (the orchestrator has given up).
The lifecycle manager can itself crash. So the orchestrator holds its connection through a PersistentBinderConnection (orchestration/persistent_binder/lib.rs) that relinks death notifications and reconnects transparently. After a lifecycle-manager crash the engine processes a Recovery event (orchestration/common/src/mode.rs) to re-enforce every instance.
How the orchestrator reconciles desired state on a mode change
sequenceDiagram
participant VPM as Vehicle Power Manager
participant ORCH as Orchestrator engine
participant EVAL as Evaluator
participant LCM as Lifecycle Manager
participant RUN as Bundle instance
VPM->>ORCH: power / vehicle mode update
ORCH->>EVAL: update_orchestration_state(mode)
EVAL-->>ORCH: HashMap FQIN to desired InstanceState
ORCH->>ORCH: enforce_instances_states (concurrent)
ORCH->>LCM: launchService / startService / stopService / shutdownService
LCM->>RUN: onCreate / onStart / onStop / onDestroy
RUN-->>LCM: state reached
LCM-->>ORCH: ServiceBundleState
ORCH-->>ORCH: notify listeners (lifecycle + recovery state)
62.7.6 Health Monitoring¶
Per its README, the Health Monitor is "a VM-internal service which is responsible for monitoring heartbeats from critical services and generating VM health report." The monitor lives in system/software_defined_vehicle/health_monitor/. It is the watchdog tier beneath the orchestrator. The orchestrator decides what should run. The health monitor notices when something that is running has gone silent. The binary is sdv_health_monitor, shipped in the APEX com.android.sdv.health.
Bundles opt into monitoring by registering a heartbeat configuration. The monitor does not watch them implicitly. The monitor's registration path (system/software_defined_vehicle/health_monitor/src/hb_explicit_registration.rs) takes a RegisterConfiguration keyed by FQIN. The configuration itself (hb_config.rs) is four numbers:
initial_delay_ms— the grace period between start and the first expected heartbeat.period_ms— how often the bundle should beat.num_periods— how many beats may be missed before the bundle is considered unhealthy.task_duration_ms— the expected length of the bundle's work.
A bundle is marked unhealthy when no heartbeat has arrived within period_ms * num_periods + task_duration_ms. The monitor tracks each bundle through a small state machine (sb_recovery_monitor.rs). The states are Normal while healthy, Recovering once recovery has been triggered, and FailedRecovery if recovery did not restore it. The health verdict feeds back into the orchestrator's recovery/retry handling. So a bundle that stops beating is restarted by the same machinery that restarts one that crashed.
62.7.7 Vehicle Power-State Manager (vpm)¶
Power and Vehicle States¶
The Vehicle Power-state Manager (vpm) is the agent that owns the VM's relationship to the vehicle's power and driving state. It is the upstream that drives the orchestrator's Power and Vehicle modes. The agent binary is sdv_vpm_agent (system/software_defined_vehicle/vpm/android/sdv/vpm/Android.bp), packaged in the APEX com.android.sdv.vpm.
vpm's state vocabulary is defined as VSIDL protos. The power side (system/software_defined_vehicle/vpm/stable/vsidl/power.proto) defines PowerStateReport with a full suspend/resume lifecycle. Its values are:
POWER_OFF_EXIT(cold boot).SUSPEND_TO_RAM_EXIT/SUSPEND_TO_DISK_EXIT(resume).ON(running normally).- The
_ENTERstates that begin a shutdown or suspend. WAIT_FOR_FINISH(the VM has done initial cleanup and is waiting for the OEM's go/cancel signal).SHUTDOWN_CANCELLED.- The
_POST_FINISHstates where SDV agents do their final cleanup before the platform powers off or suspends.
The comments are precise about who may rely on whom in each phase. For instance, during POWER_OFF_ENTER agents must stay up because OEM applications may still need them. During POWER_OFF_POST_FINISH everyone cleans up.
The vehicle side (system/software_defined_vehicle/vpm/stable/vsidl/vehicle.proto) defines VpmVehicleState as a ladder of vehicle activity:
LOW_POWER(car off from the user's view but the power-control unit still sees it).SOFTWARE_UPDATE.PARK(a few ECUs powered for a specific activity).LIFE_ON_BOARD(comfort ECUs, customer present).VEHICLE_ON(engine ECUs powered, driving not yet possible).TRACTION_ON(driving possible).
These are the values an orchestration condition matches against when it gates a bundle by vehicle_state.
The OEM-Facing and Client-Facing Interfaces¶
vpm exposes two faces. The OEM-facing RPC service VpmSystemService lets the OEM's platform integration set the vehicle state and request power transitions. The requests are TURN_ON, PREPARE_SHUTDOWN, CANCEL_SHUTDOWN, FINISH_SHUTDOWN, with a ShutdownType of POWER_OFF, SUSPEND_TO_RAM, or SUSPEND_TO_DISK. After a successful vehicle-state change, vpm publishes the new state on the Data Tunnel topic com.android.sdv.vpm.vehicle.VehicleStateChange so any bundle can react.
The bundle-facing client side is the stable HAL AIDL: IPowerStateClientApi.aidl (hardware/sdv/interfaces/vehicle_power_manager/aidl/google/sdv/vpm/IPowerStateClientApi.aidl). It lets a client subscribeToPowerStateReport(IPowerStateReportListener) and receive PowerStateReport callbacks. This is the path by which the orchestrator (and any power-aware bundle) learns of power transitions and recomputes desired state.
62.7.8 Update Manager¶
The Update Manager (system/software_defined_vehicle/update_manager/) handles both system (partition) updates and service-bundle (APEX) updates for the VM. The agent binary is sdv_update_manager_agent, shipped in the APEX com.android.sdv.update_manager. Its interfaces are VSIDL service definitions (update_manager/catalog/update_manager_agent.vsidl and update_manager_client.vsidl) exposing an UpdateManagerService and a client-side UpdateManagerListenerService for status callbacks.
The update model is a small state machine over a payload. The payload proto (update_manager/catalog/payload.proto) distinguishes two kinds of payload. A SystemUpdatePayload is a path to an OTA image, with optional offset/size. A ServiceBundleUpdatePayload is one or more APEX paths plus a boot_attempts count for retry. The service proto (update_manager/catalog/update_manager_service.proto) drives them through Prepare, Activate, Commit, and Rollback. Suspend/Resume are available for system updates, and UninstallApex removes a bundle.
Crucially, the update path is power-aware. The service proto documents this case: the Update Manager is in the PREPARE state, and vpm signals a VM suspend or power-off. Then the update is suspended. The same power modes that gate bundle lifecycles also gate the update flow.
62.7.9 The Platform Layer and Shared Common Code¶
platform¶
The system/software_defined_vehicle/platform/ tree is the native foundation the agents and bundles build on. Its README describes it as "native libraries and wrappers ... Log & Trace, Time Sync and others." The subtree carries those wrappers in C, C++, and Rust flavors.
The most load-bearing is platform/status/, which defines the SDV error/status API. It contains libsdv_status (a C ABI-stable core), libsdv_status_cpp (the C++ SdvStatus/SdvStatusOr wrappers), and libsdv_status_rs (the Rust SdvStatus/SdvResult types). This is the result type every agent returns. Alongside it the platform tree carries logging and tracing libraries, a power library, open-DICE initialization, and adbd_auth glue. These give every SDV component the same observability, error-handling, and attestation primitives, regardless of which language it is written in.
common¶
The system/software_defined_vehicle/common/ tree holds shared infrastructure. The piece worth naming is common/lib_dump/, a thin wrapper around libbinder_rust. It exposes the ISdvAgent.aidl interface (common/lib_dump/aidl/google/sdv/agent/ISdvAgent.aidl), so every agent gets uniform dumpsys support. The registry, orchestrator, lifecycle manager, and the rest implement this single, simple interface, so an operator can dump any agent the same way. The tree also carries shared protos, vendored third-party code, and common/performance_image/. Its generator/ subdirectory produces the SDV "performance" image variants such as sdv_core_perf_cf.
62.7.10 Display Safety: the HARry Driver-UI Runtime¶
What Display Safety Is¶
packages/services/display_safety implements the automotive cluster runtime and its safety enforcement. It is split across both VMs. The High Availability Renderer (harry) and the safety monitor run on the fast-booting SDV Media VM, which owns the cluster display. The Android DriverUI app that supplies the non-regulated cluster content runs on the AAOS IVI VM. Section 62.9 covers this subsystem in depth, and what follows is the overview.
It is a large Rust workspace (the root packages/services/display_safety/Cargo.toml enumerates dozens of crates), split into three tiers. The framework/ tier holds reusable rendering, audio, layout, and monitoring crates. The reference/ tier holds the reference implementation (harry-app, the safety-monitor, and ADAS visualization). The service/ tier bridges the UI to the SDV fabric.
The motivation is regulatory. A driver-facing display must not show distracting or non-compliant content while the vehicle is in motion. The cluster/Driver-UI must also render deterministically. The graphics path of the framework wraps the Impeller engine (framework/graphics/impeller). It drives layout through a Taffy-based engine (framework/har-layout) and instruments itself with a performance-monitoring crate (framework/har-monitoring).
The Safety Monitor¶
The distraction-and-compliance enforcement lives in packages/services/display_safety/reference/safety-monitor, which builds the har_safety_monitor binary. It captures the rendered screen, takes vehicle data over gRPC, and runs a set of pluggable algorithms over the result. These are a static-pixel check, a TFLite inference path for ML-based classification, and correlation/computer-vision filters. They decide whether what is on screen is safe for the current vehicle state. The monitor issues verdicts back over a gRPC control interface. It is, in effect, an independent referee that watches the Driver-UI's output.
The SDV Service Bundle Bridge¶
The seam between this IVI-side Rust runtime and the SDV fabric is packages/services/display_safety/service/har-sdv-service, which builds libhar_sdv_service_bundle, an SDV service bundle. Its Android.bp depends on two sets of libraries. On one side are the bundle's generated SDV middleware/comms bindings (libsdv_mw_rs_com_sdv_google_display_safety_har_sdv_service_bundle, plus the core SDV libraries libsdv_rs and libsdv_log_rust). On the other side are the gRPC services (libhar_grpc_services, generated from vehicledata.proto and driverui.proto). So it publishes vehicle data and serves the Driver-UI over gRPC through the SDV middleware. Vehicle data flows in from a publisher service bundle (service/vehicledata/), through the SDV fabric, into the HARry app and the safety monitor.
The whole runtime ships as APEXes built only for SDV/display-safety products. com.google.display_safety.har carries the harry_app, the har_safety_monitor, and the rendering assets. com.sdv.google.display_safety.services_bundle.apex carries the service-bundle .sos and their orchestration/ACL configs. The product wiring lives in device/google/sdv_display_safety. Its makefiles (sdv_harry_common.mk, sdv_ivi_cf_ds.mk, sdv_ivi_arm64_ds.mk, sdv_media_har_cf.mk) layer the display-safety stack onto the IVI and media products. They also pull in the AAOS DriverUI app from packages/services/Car.
62.7.11 Integrating AAOS Through the SDV Gateway¶
The Gateway as the IVI's Door to the Fabric¶
The AAOS IVI VM is a full Android Automotive image. It is not built from SDV-aware code top to bottom. So how does CarService, or a Vehicle HAL service, reach vehicle data that physically lives in another VM? Through the SDV Gateway. The gateway (system/software_defined_vehicle/sdv_gateway/, contract in hardware/sdv/interfaces/sdv_gateway/) is a @VintfStability AIDL service that runs on the IVI VM. It gives non-SDV-aware native and Java clients a controlled entry point into the comm stack.
The entry interface ISdvGateway.aidl is intentionally tiny: getVersion() and createSession(). All the work happens on the returned ISdvGatewaySession. A session is per-process and isolated. Through it a client calls initComms(InitCommsParams) to bring up bidirectional communication with remote SDV services. It then calls registerRpcServer(...) / findRpcServerByName(...) to expose or locate RPC servers in Service Discovery. It also calls createPublication(...) / subscribeToPublicationByName(...) to use the Data Tunnel pub/sub.
The session also exposes the calling app's ServiceIdentity, an authorization service, and handles to the underlying Service Discovery and Data Tunnel agents. The gateway is the IVI-side adaptor that turns "I am an ordinary Android service" into "I am a participant in the SDV fabric." The IVI client does not link the full SDV middleware.
Gating: the Gateway Config¶
Because the gateway hands ordinary Android processes the keys to the vehicle fabric, access is allowlisted. The gateway requires a config file installed at /vendor/etc/sdv_gateway_config.json. For each process UID, the file declares which SDV package names that UID's native service is permitted to use when it calls initComms (system/software_defined_vehicle/sdv_gateway/README.md). A package name is the second element of the FQIN. The format maps a UID to an array of allowed package names.
For example, a UID 2942 is allowed to use com.oemspecific.vhal. A UID of -1 grants a package name to all UIDs. The README recommends unique AIDs for the gateway's native clients, each restricted to only the package names it needs. An empty config blocks every native application from using the gateway. The reference config (device/google/sdv/sdv_ivi_base/sdv_gateway_config.json) ships with only the propagation flags (propagate_rpc_network_changes_to_data_tunnel, propagate_rpc_network_changes_to_service_discovery, both false). This means the reference image's separate VLANs for RPC, Service Discovery, and Data Tunnel are kept independent.
VHAL Proxy: Vehicle Properties Across VMs¶
The concrete CarService integration is the Vehicle HAL. On the IVI VM the SDV products wire an SDV-specific VHAL. device/google/sdv/sdv_ivi_cf/sdv_ivi_cf.mk sets LOCAL_VHAL_PRODUCT_PACKAGE := android.hardware.automotive.vehicle@V1-sdv-emulator-service. That VHAL uses the gateway's vhal_proxy library (system/software_defined_vehicle/sdv_gateway/vhal_proxy/libvhal_proxy).
The VhalProxy class reads and writes Android VehiclePropValues. It translates them to and from SDV proto messages and routes them over the gateway. ReadMessages/WriteMessages move properties, and Subscribe/Unsubscribe register for incoming updates. The proxy's config is a JSON of protobuf descriptors and property-to-service-unit mappings. It decides which property maps to which SDV publication, and whether each is an ACTION_SUBSCRIBE or ACTION_PUBLISH.
CarService sits above the VHAL exactly as it does on a normal automotive build. It is therefore unaware that the vehicle property it reads originated in a service bundle in the Core VM. The gateway and vhal_proxy make the cross-VM hop invisible.
The IVI's SDV-facing services are installed by device/google/sdv/sdv_ivi_base/sdv_packages_ivi_services.mk (the gateway, libvhal_proxy, the gateway networking service, and the SDV IVI runtime). They are started by device/google/sdv/sdv_ivi_base/sdv.agents.rc. This file starts the gateway on boot, and starts the Service Discovery and RPC agents once ro.sdv.ethernet.ready=true. Start order between them is explicitly unimportant, because the gateway waits for the SD and RPC agent services before it fully starts. The transport beneath them is the subject of Section 62.8. It covers RPC, Data Tunnel, and SOME/IP across VMs and to external ECUs.
How a CarService VHAL read reaches a Core VM service bundle
flowchart LR
CAR["CarService<br/>(packages/services/Car)"] --> VHAL["sdv-emulator-service<br/>(Vehicle HAL)"]
VHAL --> PROXY["libvhal_proxy<br/>(VehiclePropValue to proto)"]
PROXY --> SESS["ISdvGatewaySession<br/>(initComms / subscribe)"]
SESS --> GWCFG["sdv_gateway_config.json<br/>(UID to package allowlist)"]
SESS --> COMM["RPC / Data Tunnel agents"]
COMM --> XVM["SOME/IP across VMs<br/>(Section 62.8)"]
XVM --> BUNDLE["Vehicle service bundle<br/>in SDV Core VM"]
62.7.12 Composing It All: the Reference Device¶
device/google/sdv ties the platform into buildable products. OEM products are meant to inherit one SDV "base" target plus a vendor target (device/google/sdv/README.md). The bases are:
sdv_base— comm stack only.sdv_core_base— the full set of Core services.sdv_media_base— Core plus media APIs.sdv_ivi_base— an AAOS IVI that can talk to SDV services on other VMs.
The canonical "what runs in the Core VM" list is device/google/sdv/sdv_core_base/sdv_packages_core_services.mk. It holds these items:
- The lifecycle client libraries and
orch_config.textproto. - The agents and binaries
sdv_lifecycle_agent,sdv_orchestration_agent,sdv_service_bundles_registry_agent,lifecycle_service_bundle_runner,sdv_someip_broker_agent_comms,sdv_update_manager_agent,sdv_health_monitor, andsdv_vsidl_provider_agent. - The comm-stack agents (
dt_agent,rpcagent,sdv_sd_agent). - The matching APEXes (
com.android.sdv.health,com.android.sdv.orchestrator,com.android.sdv.update_manager,com.android.sdv.vsidl_provider,com.android.sdv.dt).
The same file demands a SOME/IP broker config and warns if no SOME/IP agent is installed. A Core VM with no transport agent cannot talk to anything.
The sample lunch targets (device/google/sdv/AndroidProducts.mk) are the Cuttlefish and ARM64 instances of these bases. They are sdv_core_cf, sdv_core_perf_cf, sdv_core_tiny_cf, sdv_ivi_cf, sdv_media_cf, sdv_media_har_arm64, and their *_arm64/*_cf peers. (The display-safety media-HAR Cuttlefish target sdv_media_har_cf lives in a separate device tree, device/google/sdv_display_safety/, not in device/google/sdv/.) The smallest end-to-end SDV system boots a Core VM and an IVI VM together, as device/google/sdv/cuttlefish_multi_tenancy/ configures. It has a headless Core that hosts bundles, an AAOS IVI that reaches them through the gateway, and the comm fabric between them.
62.8 SDV Middleware and Vehicle Communication¶
Section 62.7 introduced the Software Defined Vehicle (SDV) platform that arrives in Android 17. It is a headless vehicle Android OS. A Core VM runs vehicle services with no UI. It runs alongside one or more Android Automotive OS (AAOS) In-Vehicle Infotainment (IVI) VMs and non-Android automotive ECUs.
That section covered the architecture overview, the Core VM, and the orchestration that drives bundle lifecycle. This section goes one layer down, into the communication fabric that ties all of those pieces together. It covers these topics:
- The VSIDL interface-definition language and its Rust code generator.
- The three-agent middleware (Service Discovery, Data Tunnel, RPC) with its secure mesh.
- The SOME/IP stack that carries cross-VM and cross-ECU traffic.
- The SDV Gateway that lets ordinary AAOS apps and the VHAL reach the fabric.
- The automotive-domain service catalog (diagnostics, configuration, calibration, vehicle mode, user profile) layered on top.
The source lives almost entirely under system/software_defined_vehicle/, with the stable contracts in hardware/sdv/interfaces/.
62.8.1 The Shape of the Fabric¶
Binder is how processes talk inside one Android VM (Chapter 9). In the same way, the SDV middleware is how Service Bundles talk across VMs and out to physically separate ECUs. The design borrows Android's idioms: AIDL contracts, a registry, and identity-aware calls. It stretches them over a network of mutually distrusting compute nodes inside a single vehicle.
Three concepts recur throughout this section:
- Service Bundle — the SDV unit of deployment and the analog of an Android service. A bundle publishes topics, subscribes to topics, and offers or consumes RPC services. Its interface is described in VSIDL (§62.8.2).
- FQIN (Fully Qualified Instance Name) — the vehicle-wide identity of a bundle instance.
ServiceFqin(hardware/sdv/interfaces/middleware/service_discovery/google/sdv/identity/ServiceFqin.aidl) is four strings:sdvVmName,sdvPackageName,serviceBundleName, andserviceInstanceName(the last assigned by the Orchestrator at load time). The FQIN is what gets baked into TLS certificates so the mesh can authenticate peers. - SID (Service ID) — a 64-bit numeric identity that fast lookups use at runtime.
ServiceIdentity(.../identity/ServiceIdentity.aidl) pairs thelong sidwith an EC public key and the human-readable FQIN.
The three middleware agents¶
Inside any one VM, a Service Bundle reaches the fabric through three agents, each a Binder service defined in hardware/sdv/interfaces/middleware/:
- Service Discovery (
service_discovery/) registers, finds, and watches service units and topics. - Data Tunnel (
data_tunnel/) carries named-topic publish/subscribe traffic over Android FastMessageQueues (FMQ). - RPC (
rpc/) carries request/response calls over sockets.
A bundle does not connect to those agents one by one. The Lifecycle Manager (§62.7.4) hands each bundle a single use-once ContextInitializationToken (hardware/sdv/interfaces/middleware/ctx/aidl/google/sdv/comms/ContextInitializationToken.aidl) that bundles all four Binder connections it needs:
// Source: hardware/sdv/interfaces/middleware/ctx/aidl/google/sdv/comms/ContextInitializationToken.aidl:31
parcelable ContextInitializationToken {
ServiceIdentity identity;
IServiceRegistrationAgent sr_agent;
IServiceDiscoveryAgent sd_agent;
IAgentService dt_agent; // Data Tunnel
IRpcAgent rpc_agent;
}
The comment on the token states its lifecycle plainly. The comment calls it "the use-once Context initialisation value issued by LifecycleManager to Service Bundles that enables them to create the SDV SDK Context object." From that token the bundle builds its SDK Context, and everything else flows from there.
The overall layering, from the bundle down to the wire, looks like this.
How a Service Bundle reaches the fabric, and how the fabric reaches other VMs and ECUs
graph TB
subgraph App["Service Bundle (Rust, VSIDL generated_rs)"]
SDK["SDV SDK Context<br/>(from ContextInitializationToken)"]
end
subgraph Agents["Per-VM middleware agents (Binder)"]
SD["Service Discovery<br/>(sd_agent, Rust)"]
DT["Data Tunnel<br/>(dt_agent, C++, FMQ)"]
RPC["RPC agent<br/>(IRpcAgent, sockets)"]
end
subgraph Sec["Identity and security"]
ID["Identity Agent<br/>(SID + FQIN)"]
CA["Certificate Authority<br/>(per-VM, mTLS)"]
AZ["Authz service<br/>(ACL / permission)"]
end
subgraph Wire["Cross-VM and cross-ECU"]
BROK["SOME/IP broker<br/>(Rust)"]
STACK["SOME/IP stack agent<br/>(C++, vsomeip)"]
ECU["External ECUs<br/>(non-Android)"]
end
SDK --> SD
SDK --> DT
SDK --> RPC
SD --> ID
SD --> CA
SD --> AZ
SD --> BROK
DT --> BROK
RPC --> BROK
BROK --> STACK
STACK <--> ECU
62.8.2 VSIDL: Describing Services and Generating Rust¶
SDV does not hand-write the marshalling code that moves messages between bundles. It describes services in a Vehicle Service Interface Definition Language (VSIDL) and generates the middleware bindings, the same way AIDL generates Binder stubs. The toolchain lives under system/software_defined_vehicle/vsidl/ and is written in Rust.
The .vsidl and .proto catalog¶
A catalog is a directory holding two kinds of files: .proto files that define message types, and .vsidl files that define service bundles. The compiler README spells out the split: "From .proto files it reads message names, rpc interfaces, and type definitions. From .vsidl files it reads service bundle definitions" (system/software_defined_vehicle/vsidl/vsidlc/README.md).
A .vsidl file is itself textproto, conforming to the grammar in system/software_defined_vehicle/vsidl/language/src/protos/sdv/vsidl/v1/syntax.proto. A bundle declares the topics it publishes and subscribes to and the RPC services it serves or calls:
// Source: system/software_defined_vehicle/samples/vsidl/complex/catalog/complex_message_publisher.vsidl:18
package: "com.android.sdv.sample.complex"
service_bundle {
name: "ComplexMessagePublisher"
publisher {
message: "ComplexMessage"
topic: "complex-message"
capacity: 50
}
server {
service: "ComplexMessageRPC"
channel: "complex-message-rpc"
}
}
A subscriber/client bundle is the mirror image: a subscriber block naming the same topic, and a client block naming the same RPC channel. Topics are named — complex-message here — and that name is the rendezvous point the two halves use without ever knowing each other's FQIN at authoring time.
vsidlc and generated_rs¶
The compiler vsidlc (system/software_defined_vehicle/vsidl/vsidlc/) walks the catalog recursively and emits Rust middleware bindings into an output/generated_rs directory (per its README). Internally it runs a small pipeline of generation steps under system/software_defined_vehicle/vsidl/vsidlc/src/rust/steps/. The steps are service-bundle bindings, RPC bindings, diagnostics bindings, and a generated Android.bp. The output is the SDV equivalent of an AIDL stub. It is a set of typed publisher/subscriber/server/client handles that the bundle code links against. So application logic never touches the wire format directly.
A companion tool, vsidl_rc_generator (system/software_defined_vehicle/vsidl/vsidl_rc_generator/), produces the runtime configuration the agents load, rather than the code the bundle links. Its README lists the outputs: "Schemas of Protobuf messages used in the catalog, SOME/IP mapping files, [and] Diagnostic declarations." The outputs are serialized as vsidl-config.binpb, someip-config.binpb, and diagnostics-config.binpb.
SOME/IP translation modes¶
Because a topic may have to cross onto a SOME/IP bus to reach a non-Android ECU, message types carry a translation mode. This mode decides how the SOME/IP layer treats their bytes. The parser recognizes three modes (system/software_defined_vehicle/vsidl/language/src/parser/converter.rs): INTERPRET_AS_BYTES, DYNAMIC_LIBRARY, and a default REFLECTION.
The someip_translation_generator (system/software_defined_vehicle/some_ip/someip_translation_generator/) reads these tags and emits translation code. Its README documents a static-lib mode that handles messages tagged INTERPRET_AS_BYTES (the bytes go on the wire as-is). It also documents a dyn-lib mode for messages tagged DYNAMIC_LIBRARY (translation code is compiled into a shared library). This is the seam where SDV's protobuf-shaped messages meet SOME/IP's fixed wire layout.
The VSIDL provider agent¶
Catalog metadata also has to be queryable at runtime, sometimes from a different VM. sdv_vsidl_provider_agent (system/software_defined_vehicle/vsidl/provider/agent/sdv/) is an RPC service that answers descriptor queries: publication descriptors, RPC method descriptors, message descriptors, and diagnostics declarations. Its client library (system/software_defined_vehicle/vsidl/provider/clientlib/) can source that metadata in three ways. The ways are local config files, on-device APEXes, or delegation to another VM's provider agent. So a tool or bundle can introspect a service bundle that lives on a peer VM. There is an ivi/ variant of the agent for the IVI side as well.
62.8.3 The Middleware: Discovery, Data Tunnel, RPC, and the Secure Mesh¶
With VSIDL covering the contract, the runtime fabric is the three agents plus the identity and security layer beneath them. The implementations live under system/software_defined_vehicle/middleware/; the contracts under hardware/sdv/interfaces/middleware/.
Service Discovery¶
IServiceRegistrationAgent registers a service unit and returns a one-use RegistrationToken; IServiceDiscoveryAgent finds and watches units. The registration call carries the unit name, its UnitType, an ACL, and application metadata (.../service_discovery/discovery/IServiceRegistrationAgent.aidl). Discovery offers both type-based and name-based lookups plus topic enumeration:
// Source: hardware/sdv/interfaces/middleware/service_discovery/google/sdv/service_discovery/discovery/IServiceDiscoveryAgent.aidl
ServiceUnitDefinition getServiceUnit(in ServiceFqin fqin, in String unitName);
// ... plus listServiceUnitsByType/ByName, fetchPublishersByTopicName, listTopics
A third interface, ITransportSupportAgent, lets a transport (such as the SOME/IP broker) redeem a RegistrationToken for the full ServiceUnitDefinition. The transport can also attach transport-specific metadata to it. That indirection is how the wire layer learns where to actually send bytes for a logically-registered service. The Service Discovery agent itself, sdv_sd_agent, is Rust (system/software_defined_vehicle/middleware/service_discovery/sdv_sd_agent/srcs/main.rs).
Data Tunnel¶
Data Tunnel is named-topic pub/sub. IAgentService (hardware/sdv/interfaces/middleware/data_tunnel/aidl/google/sdv/data_tunnel/IAgentService.aidl) has a publisher register a publication. The publisher hands over an MQDescriptor for the FastMessageQueue it will write into. Subscribers attach by unit identifier or by topic name:
Connect(long sid, out ClientDescriptor)establishes the per-client channel.RegisterPublication(RegistrationToken, MQDescriptor<byte,...>, out PublicationDescriptor)registers a topic backed by an FMQ the publisher allocates.SubscribeToPublicationExtended(SubscriptionParams, out SubscriptionResult)subscribes with a readiness listener.GetLastMessageByTopic(UnitType, String topicName, out byte[])reads the most recent value of a topic.
Using FMQ means same-VM pub/sub is effectively zero-copy through shared memory; cross-VM topics ride the SOME/IP broker instead. The Data Tunnel agent is C++, and it ships with a companion APEX com.android.sdv.dt that carries the ACLs governing inter-VM Data Tunnel communication (system/software_defined_vehicle/middleware/data_tunnel/apex/Android.bp).
RPC¶
RPC is socket-based request/response. IRpcAgent (hardware/sdv/interfaces/middleware/rpc/google/sdv/rpc/IRpcAgent.aidl) is small and pointed. A server redeems a RegistrationToken to get a socket to listen on. A client asks for a connection to a named server:
// Source: hardware/sdv/interfaces/middleware/rpc/google/sdv/rpc/IRpcAgent.aidl:26
interface IRpcAgent {
ParcelFileDescriptor registerServer(in RegistrationToken token);
void registerServerPort(in RegistrationToken token, int port);
ParcelFileDescriptor getFdConnection(in long sid, @utf8InCpp String unitName);
@utf8InCpp String getAddressConnection(in long sid, @utf8InCpp String unitName);
@utf8InCpp String getNetworkInterfaceName();
}
getNetworkInterfaceName() returns the network interface the agent binds to — by default the dedicated SDV-RPC VLAN discussed in §62.8.4.
Identity, the certificate mesh, and authorization¶
The agents above are only safe because of a security layer that runs beneath them. It has three parts, all under hardware/sdv/interfaces/middleware/service_discovery/google/sdv/:
- Identity (
identity/IIdentityAgent.aidl) mints and verifiesServiceIdentityrecords:createIdentity(EcPublicKey, ServiceFqin, ...),verifyIdentity(...), and lookups by SID, by FQIN, or by OS process identifier. - Certificate Authority (
ca/ICertificateAuthority.aidl) issues X.509 certificates for FQINs.requestCertification(String request)takes a PEM PKCS#10 request whose subject-alternative DNS name encodes the FQIN;addAuthoritiesListener(...)lets a peer learn as VMs join or leave. The CA is gated on boot state —isEnabled()returns false in the UNLOCKED boot mode and true when LOCKED. - Authorization (
authz/IAuthzService.aidl) answersisAuthorized(subject_fqin, object_fqin, object_service_unit_name)so the middleware can deny calls between bundles that policy does not permit.
The certificate material is EC (P-256). The helper that builds the self-signed per-VM root encodes the BASE32 FQIN into the certificate subject and subject-alternative name (system/software_defined_vehicle/middleware/crypto_rpc/src/cert.rs). The crypto_rpc library README states the coupling directly. The README says the library "enables TLS for RPC." It also says "Service Discovery and SDV RPC library depend on each other in terms of X509 certificate signing and usage."
The result is the secure mesh. Each SDV VM runs its own CA. The set of CAs is shared across VMs, so any node can validate any peer's certificate.
IMeshStatus (.../mesh/IMeshStatus.aidl) reports whether this VM is connected to every other VM declared in the vehicle's vvmconfig (isComplete()) and the per-peer PeerConnectionStatus. Mesh provisioning writes a truststore file /vvmtruststore/uds_pubs via IUdsPubsProvisioner (.../mesh/provisioning/IUdsPubsProvisioner.aidl). That step is only available in the UNLOCKED boot mode. The device is provisioned, then locked.
The security and identity layer beneath the three agents
graph LR
BUNDLE["Service Bundle"] -->|register| SR["IServiceRegistrationAgent"]
SR -->|"createIdentity()"| ID["IIdentityAgent<br/>(SID + FQIN)"]
SR -->|"requestCertification()"| CA["ICertificateAuthority<br/>(per-VM root, P-256)"]
BUNDLE -->|"call peer"| AZ["IAuthzService<br/>(isAuthorized)"]
CA -->|"share roots"| MESH["Secure mesh<br/>(IMeshStatus, uds_pubs)"]
MESH -->|"mTLS peers"| PEER["Other SDV VMs"]
AZ -->|"allow / deny"| RPC["RPC / Data Tunnel<br/>over mTLS"]
62.8.4 SOME/IP: Crossing VM and ECU Boundaries¶
Same-VM traffic stays in Binder and FMQ. The moment a topic or RPC has to reach another VM or a non-Android ECU, it goes onto SOME/IP, the AUTOSAR automotive service protocol. It does this through two cooperating processes under system/software_defined_vehicle/some_ip/.
The stack agent and vsomeip¶
sdv_someip_stack_agent is C++. It wraps the open-source vsomeip library: StackImpl constructs vsomeip::runtime::get() and creates a vsomeip application (system/software_defined_vehicle/some_ip/vsomeip_stack/src/stack.cpp), then exposes a Binder interface, ISomeIpStack. The wire configuration lives in system/software_defined_vehicle/some_ip/vsomeip_stack/vsomeip_config.json, the standard vsomeip configuration format. It defines which SOME/IP service IDs and instance IDs this node offers, their TCP/UDP ports, and the service-discovery multicast group.
ISomeIpStack (hardware/sdv/interfaces/some_ip/stack_agent/aidl/google/sdv/someip/ISomeIpStack.aidl) is the boundary between SDV's world and the SOME/IP wire, and it speaks in raw byte[] payloads on both sides:
// Source: hardware/sdv/interfaces/some_ip/stack_agent/aidl/google/sdv/someip/ISomeIpStack.aidl
byte[] rpc_transact(in SomeIpService service, char method_id, in byte[] payload); // sync RPC
void rpc_oneway (in SomeIpService service, char method_id, in byte[] payload); // fire-and-forget
oneway void monitor_service(in SomeIpService service); // track availability
oneway void publish(in SomeIpService service, char event_id, in byte[] payload); // emit event
void subscribe_eventgroup(in SomeIpService service, char eventgroup, in char[] event_ids);
A SomeIpService is the SOME/IP triple: a 16-bit service_id, a 16-bit instance_id, and a SomeIpServiceVersion (byte major, int minor). Both types are defined in SomeIpService.aidl and SomeIpServiceVersion.aidl in the same directory.
The broker¶
The stack agent only knows SOME/IP. Mapping SDV's topics, RPC channels, and protobuf messages onto SOME/IP services, events, and method IDs is the job of sdv_someip_broker_agent_comms, which is Rust. Its module header states its purpose: "This agent is responsible for enabling communication between SOME/IP communication and SDV" (system/software_defined_vehicle/some_ip/broker_agent_comms/src/main.rs). The broker has sub-modules for service discovery, pub/sub, and RPC, plus a translator. This sub-module converts between SOME/IP bytes and SDV types with the mappings generated by vsidl_rc_generator (§62.8.2).
The broker connects to the stack agent over Binder (google.sdv.someip.ISomeIpStack/default). It also registers callbacks, so SOME/IP events, availability changes, and inbound RPC requests are routed back into the SDV agents.
Callbacks: how traffic flows in both directions¶
ISomeIpStack is paired with three callback interfaces the broker registers so the flow is bidirectional:
ISomeIpServiceAvailabilityCallback— the stack tells the broker when a SOME/IP service appears or disappears (SOME/IP service discovery).IEventNotificationCallback— the stack delivers a subscribed SOME/IP event up to the broker, which fans it out to Data Tunnel subscribers.IRpcRequestCallback—byte[] onRpcRequest(SomeIpService, char method_id, byte[] payload): the stack hands an inbound SOME/IP RPC request to the broker and sends the returned bytes back as the response.
A separate, tiny interface, ISomeIpLoadIndicators (hardware/sdv/interfaces/some_ip/load_indicators/aidl/.../ISomeIpLoadIndicators.aidl), reports a single int pendingSomeIpEventCounter. Zero means idle. A positive value is the depth of the unprocessed SOME/IP event queue. A negative value signals an error. The counter is a cheap real-time backpressure signal that the stack agent samples periodically.
The round trip for an outbound RPC and an inbound event
sequenceDiagram
participant B as Service Bundle
participant RA as RPC agent / Data Tunnel
participant BR as SOME/IP broker (Rust)
participant ST as SOME/IP stack agent (C++)
participant E as External ECU
Note over B,E: Outbound RPC (SDV bundle calls an ECU)
B->>RA: call remote service
RA->>BR: route by FQIN
BR->>ST: rpc_transact(service, method_id, bytes)
ST->>E: SOME/IP request over vsomeip
E-->>ST: SOME/IP response
ST-->>BR: response bytes
BR-->>RA: translated reply
RA-->>B: result
Note over B,E: Inbound event (ECU notifies the vehicle)
E->>ST: SOME/IP event
ST->>BR: onEvent(service, event_id, bytes)
BR->>RA: publish to Data Tunnel topic
RA->>B: deliver to subscriber
The SDV-RPC VLAN¶
SDV-RPC traffic rides a dedicated VLAN so it can be isolated and policed separately from ordinary networking. The interface name is set either as a bootconfig variable, androidboot.sdv.rpc.interface=sdv_rpc, or via the SDV_RPC_INTERFACE build variable. The reference Cuttlefish targets (sdv_core_cf, sdv_ivi_cf) default it to sdv_rpc (system/software_defined_vehicle/sdv_gateway/README.md). At runtime the gateway and networking services read it from the ro.boot.sdv.rpc.interface system property (system/software_defined_vehicle/sdv_gateway/service/cpp/SdvGatewayService.cpp).
62.8.5 The SDV Gateway: Bringing the IVI and the VHAL onto the Fabric¶
The middleware so far assumes SDV-aware Rust bundles built from VSIDL. But an AAOS IVI VM is full of ordinary Java apps and a Vehicle HAL. They know nothing about FQINs, registration tokens, or the secure mesh. The SDV Gateway (system/software_defined_vehicle/sdv_gateway/) is the adapter that lets those non-SDV-aware clients reach the fabric. It is implemented mainly in C++ (the service/, libsdvgateway, and vhal_proxy pieces) with Java for the networking service and client SDK. Section 62.7.11 introduced the gateway from the AAOS side; this section details its interfaces.
The session model¶
A client first obtains the gateway, then opens a session. ISdvGateway (hardware/sdv/interfaces/sdv_gateway/google/sdv/gateway/ISdvGateway.aidl) is deliberately minimal — getVersion() and createSession() — and a process may hold only one session at a time. ISdvGatewaySession (.../ISdvGatewaySession.aidl) is the substantial interface; it mirrors the three middleware agents but for an untrusted caller:
initComms(InitCommsParams)— establishes the session's comms.InitCommsParams(.../InitCommsParams.aidl) carries the caller'sPublicKeyplus the three FQIN strings it is asking to use:sdvPackageName,serviceBundleName,serviceInstanceName.registerRpcServer(RegisterRpcServerParams)/findRpcServerByName(FindRpcServerByNameParams)— the gateway side of RPC; the find result returns aSocketAddress, a subject-alternative name, and the peer VM name.createPublication(CreatePublicationParams)returningIDataTunnelPublication, andsubscribeToPublicationByName(...)— the gateway side of Data Tunnel.rpcCredentialsConfig()returningRpcCredentialsConfigResult(auseInsecureRpcflag plus the subject-alternative name),requestCertificateChain(...), andsetAuthoritiesListener(...)— the gateway side of the certificate mesh.
Status comes back as SdvGatewayStatusCode (.../SdvGatewayStatusCode.aidl), a gRPC-style enum (OK, CANCELLED, … UNAUTHENTICATED).
The privileged interfaces¶
Behind the public session, the gateway also exposes a set of privileged interfaces under hardware/sdv/interfaces/sdv_gateway/google/sdv/privileged/, reserved for trusted system services rather than arbitrary apps:
IPrivilegedGatewayNetworking(privileged/gatewaynetworking/) — used only by the Javasdv_gatewaynetworking_service. It suppliesgetRpcNetworkInterfaceName(),setRpcNetworkHandle(long),onCarPowerStateChanged(CarPowerState), and listener registration.CarPowerStateenumerates the AAOS power states (WAIT_FOR_VHAL,ON,SHUTDOWN_PREPARE,SUSPEND_ENTER,HIBERNATION_ENTER, …) so the fabric can react to vehicle power transitions.IPrivilegedIdentityAgent,IPrivilegedServiceRegistrationAgent,IPrivilegedServiceDiscoveryAgent— the same identity/registration/discovery operations as §62.8.3. The difference is that they take an explicit process identifier argument. This lets the gateway act on behalf of a specific client process rather than its own.
The gateway AIDL is versioned and frozen under hardware/sdv/interfaces/sdv_gateway/aidl_api/. The public google.sdv.gateway package is at API v3 (with v1/v2 snapshots retained). The privileged packages are at mixed versions: gatewaynetworking at v3, identity and service_discovery at v2. This is the same backward-compatibility discipline as any stable AIDL HAL (Chapter 10).
The VHAL proxy¶
The flagship gateway client is the VHAL proxy (system/software_defined_vehicle/sdv_gateway/vhal_proxy/libvhal_proxy/). It lets a Vehicle HAL service surface vehicle properties as Data Tunnel topics and vice versa. Per its README, VhalProxy "handles parsing a VHAL proxy configuration file, subscribing to the services defined via Data Tunnel Publishers, and reading protobuf messages from those services. For every message, this library handles converting the protobuf value for each property ID and area ID pair, and creating a corresponding VehiclePropValue." Its API surface is four calls: ReadMessages, WriteMessages, Subscribe, and Unsubscribe. The config maps protobuf message types to VHAL property/area pairs and a Data Tunnel action (subscribe or publish) — a sample lives at system/software_defined_vehicle/samples/vhal_proxy/sdv_emulated_vhal/VhalProxySampleConfig.json.
This is what makes a stock AAOS CarService (§62.1) work over SDV. CarService reads vehicle properties from the VHAL exactly as on a one-VM device. The VHAL proxy quietly sources those properties from vehicle services that run in the Core VM. It does this through the gateway, over the fabric.
The UID allowlist¶
Because the gateway lets an untrusted process claim an SDV package name (the second FQIN element), it must police which process may claim which name. That is the job of sdv_gateway_config.json, installed at /vendor/etc/sdv_gateway_config.json. The README is explicit. The file contains "the SDV package names (the second element of the FQIN) that native service (e.g. VHAL) are allowed to use when calling initComms." The README also says "an empty config would prevent all native applications from using the SDV Gateway."
The allowlist is keyed by process UID (the same key §62.7.11 uses):
// Source: system/software_defined_vehicle/sdv_gateway/README.md (config example)
{
"allowed_native_packagename": {
"2942": [ "com.oemspecific.vhal" ]
},
"propagate_rpc_network_changes_to_data_tunnel": false,
"propagate_rpc_network_changes_to_service_discovery": false
}
UID 2942 here may register only the com.oemspecific.vhal SDV package name; a UID of -1 grants a name to all UIDs. The README recommends a unique AID for each native gateway client, with only the names it truly needs on the allowlist. The aim is "to add another layer of security against the compromise or misuse of service discovery identities." The two propagate_rpc_network_changes_* flags control whether an SDV-RPC VLAN change is pushed to the Data Tunnel and Service Discovery agents. Both are false in the reference device/google/sdv/sdv_ivi_base/sdv_gateway_config.json. The flags are relevant only when those agents share the RPC interface rather than running on separate VLANs.
62.8.6 Automotive Services: the Domain Catalog¶
The fabric so far is domain-agnostic plumbing. On top of it, system/software_defined_vehicle/automotive_services/ ships the automotive-specific service catalog. The repo README names the five: "Diagnostics, Configuration, Calibration, Vehicle Mode, User Profile." These are defined in VSIDL/proto and implemented in Rust as ordinary service bundles riding the Data Tunnel and RPC agents.
Diagnostics (ISO 14229-1 / AUTOSAR)¶
Diagnostics is the most developed of the five and the one that most clearly shows SDV adopting an automotive standard rather than inventing one. Its README states the lineage outright: "APIs are based on ISO 14229-1:2020 and AUTOSAR Diagnostic Event Manager standards" (system/software_defined_vehicle/automotive_services/diagnostics/README.md). It also links the ISO 14229-1:2020 (UDS) and AUTOSAR DEM specifications as external references.
The interface set under system/software_defined_vehicle/automotive_services/diagnostics/vsidl/v1/ maps directly onto UDS:
diagnostics_manager_service.proto— the RPC API a service uses to query connection parameters from the Diagnostics Manager.connection.proto—SessionType(default, programming, extended-diagnostic, safety-system-diagnostic sessions per UDS) andConnectionParameters(source/target address, session type).event.proto— fault events. TheirStatusenum (PASS, FAIL, PRE_PASS, PRE_FAIL) follows the AUTOSAR Diagnostic Event Manager. The file also has anOperationCycle(START/STOP/RESTART) and anEnableConditionpublished over Data Tunnel.response_code.proto— the ISO 14229-1 negative-response codes.- Per-service interfaces for the standard UDS routines:
routine_control_service.proto,io_control_service.proto,ecu_reset_service.proto,security_access_service.proto,authentication_service.proto,file_transfer_service.proto, andfault_listener_service.proto.
A reference sdv_diagnostics_agent (system/software_defined_vehicle/automotive_services/diagnostics/tests/agent/src/main.rs) wires these together and listens for DoIP (Diagnostics over IP, ISO 13400) traffic. This is the path an external diagnostic tester uses to reach the vehicle.
Configuration, Calibration, Vehicle Mode, User Profile¶
The remaining four follow the same pattern — proto-defined VSIDL services, Rust implementations:
- Configuration / Calibration (ConCal) (
automotive_services/concal/) lets services be reconfigured and calibrated at runtime.ConCalCalibrationService(concal/catalog/concal_calibration_service.proto) exposesGetCalibrationConfigIds,StartCalibration,UpdateConfigForCalibration, andFinishCalibration, with companion registration, update, and notification services. - User Profile (
automotive_services/user_preferences/) manages per-user settings.UserPreferencesManagementService(user_preferences/vsidl/v1/user_preferences_management_service.proto) offersRequestSettingsChange,SubscribeToSettingsChangeAndGetSettings, andUnsubscribeFromSettingsChange, alongside admin, registry, and change-notifier services. - Vehicle Mode is named in the catalog README as one of the five automotive services. Vehicle power-state handling itself lives in the Vehicle Power Manager (
system/software_defined_vehicle/vpm/). Section 62.7.7 covers it together with orchestration.
62.8.7 Samples and Tools¶
SDV ships an unusually large body of runnable examples and host tooling, because a distributed multi-VM fabric is hard to learn from contracts alone.
Samples¶
system/software_defined_vehicle/samples/ holds reference bundles, each with its own README:
- quickstart (
samples/quickstart/) packages two bundles (aManagerand aMonitor) into an APEX, withvsidlc-generated Rust middleware. It demonstrates the end-to-end flow from.vsidlto a deployable, lifecycle-launched bundle. - qos_scheduling (
samples/qos_scheduling/) shows scheduling profiles affecting CPU-bound bundles. - tracing instruments both C++ (Perfetto SDK) and Rust (the
tracingcrate pluslibatrace_rust) bundles and shows AIDL calls auto-emitting ATrace events. - sdv_gateway (
samples/sdv_gateway/) is an IVI app reaching SDV services through the gateway client library, exercising Service Discovery, Data Tunnel, and RPC together with TLS. - oem_partition_update_client demonstrates the OEM A/B partition update flow (prepare, activate, commit, rollback) via an
OemUpdatercrate. - cujs (
samples/cujs/) is a large set of Critical User Journeys: pub/sub, RPC, pub/sub-with-power-suspend, multi-publisher, and robustness scenarios. They double as integration tests across VMs.
Host tools¶
system/software_defined_vehicle/tools/ holds the host-side, mostly-Rust tooling that supports the build and provisioning flows:
- regenerator (
tools/regenerator/) re-runsvsidlcto refresh generated middleware catalogs, driven byCATALOG_UPDATEtextproto files that record the output path, dependency catalogs, and generator flags. - vvmconfig_generator (
tools/vvmconfig_generator/) converts a human-readable JSON vehicle-VM topology into the CBORvvmconfigblob the Service Discovery agent consumes. The JSON stays in git, and the binary CBOR appears at build time. - sdv_provisioning_tool (
tools/sdv_provisioning_tool/) drives secure-mesh provisioning: it waits until the mesh is complete, writes/vvmtruststore/uds_pubs, and returns its SHA-256 as the factory trust anchor. - vhal_json_generator (
tools/vhal_json_generator/) is a Rust host binary that reads VSIDL VHAL mappings and emits the JSON config the VHAL proxy (§62.8.5) loads. - test_uds_certs_generator (
tools/test_uds_certs_generator/) generatesuds_certsfiles for development and testing of the mesh.
62.9 The High Availability Renderer¶
An instrument cluster is not a screen like the others. When a brake-system fault appears, the red telltale for it has to be visible. Visible does not mean "as soon as the UI process finishes starting". It does not mean "unless the compositor is wedged". It means visible, because a regulator says so and because the driver is traveling at speed.
The rest of Android's display stack is built on a different premise. An app asks a window manager for a surface. SurfaceFlinger composes what it gets. If a process dies, it restarts and redraws a moment later. A moment later is a perfectly good answer for a launcher. It is not an answer for a seatbelt warning.
Android 17 resolves that tension. It does not try to make the ordinary stack safety-critical. It ships a second, much smaller renderer that owns the cluster display outright: the High Availability Renderer (HAR). HAR is a Rust application that runs in the Software Defined Vehicle's fast-booting Media VM. It talks to the display through DRM/KMS with SurfaceFlinger stopped. It keeps drawing regulated content whether or not the Android that renders the pretty parts of the cluster is alive.
Beside it runs an independent safety monitor. The monitor reads the finished framebuffer back. It checks, pixel against golden image, that the telltales the vehicle asked for are actually on the glass.
The code lives in packages/services/display_safety/ — 571 files across 52 Cargo
workspace members, plus a prebuilt graphics engine and a Figma-driven design
toolchain. This section walks it top to bottom:
- Why the subsystem exists at all.
- How its platform abstraction layer lets the same renderer run on Android, Linux and QNX.
- The path a Figma design takes to become pixels.
- How HAR takes exclusive ownership of the display.
- The heartbeat contract that decides when Android's contribution is trustworthy.
- The design compiler and the safety monitor, which together make a verifiable claim about what is on screen.
- How the whole thing is packaged and started.
Sections 62.7 and 62.8 covered the SDV platform that hosts this code — the headless Core VM, the service-bundle model, and the middleware fabric. This section goes down into the renderer itself.
62.9.1 Why the Cluster Needs Its Own Renderer¶
Telltales are a legal obligation¶
A telltale is the automotive term for an indicator lamp. Examples are anti-lock brakes, airbag, low tire pressure, parking brake, seatbelt, low beam, high beam, fog lamps, and traction control. They are not decoration and they are not application UI. Their color, their meaning, and the conditions under which they must illuminate are written into vehicle regulations. A cluster that fails to show one when the vehicle bus says it should is a defective vehicle, not a buggy app.
That single requirement is what separates HAR from every other renderer in Android. It is not enough to draw the telltale correctly on a good day. The system has to be able to state, with evidence, that the pixels currently being scanned out contain the telltales the vehicle asked for. Sections 62.9.8 and 62.9.9 describe the machinery built to make exactly that statement.
The set is concrete rather than abstract. The design compiler's configuration maps vehicle bus signal names onto nodes in the cluster design. The names are abs, airbag, low_tire_pressure, brake,
traction, lowbeam, hibeam, park_lights, fog_lights, seatbelts. The monitor watches those specific regions of
the screen.
The startup gap¶
The second problem is time. A driver turns the key and expects the cluster to be alive almost immediately; regulation is similarly impatient about some elements. A full Android Automotive image — boot, Zygote, system server, SystemUI, a cluster app — does not reach first frame in that window. Nothing about Android's architecture makes it cheap to try.
HAR sidesteps the problem instead of optimizing it. It is a single native Rust binary with precompiled shaders and its own display path, started from an APEX as soon as APEXes are mounted. It does not wait for the Android framework because it does not use it. The ordinary Android cluster experience arrives later and is composited in when it does. The heartbeat contract in Section 62.9.7 arbitrates precisely that.
Where HAR runs and what it owns¶
In an SDV deployment the work is split across two virtual machines:
- SDV Media VM — a fast-booting VM that runs
harry(the HAR binary) andhar_safety_monitor. This VM owns the cluster display hardware. - SDV IVI VM — a full Android Automotive OS image. It runs DriverUI, a privileged Android app that renders the non-regulated parts of the cluster: media, navigation, telephony.
HAR draws the safety-relevant layer and decides how much of the screen Android is allowed to contribute. DriverUI streams its content across to HAR over gRPC and must keep proving it is alive. The division is deliberate. The component with the regulatory obligation is small, native, and independent. The component with the rich feature set is ordinary Android. It is treated as untrusted for availability purposes.
The two-VM split¶
graph TB
subgraph MEDIA["SDV Media VM (fast boot)"]
HARRY["harry<br/>High Availability Renderer"]
MON["har_safety_monitor<br/>golden-image checks"]
VD["vehicledata publisher<br/>service bundle"]
end
subgraph IVI["SDV IVI VM (Android Automotive)"]
DUI["DriverUI<br/>media, maps, telephony"]
CAR["CarService"]
end
subgraph HW["Cluster hardware"]
DRM["DRM/KMS<br/>no SurfaceFlinger"]
DISP["Cluster display"]
end
DUI -->|"DriverUIService gRPC<br/>heartbeat + document updates"| HARRY
CAR --- DUI
VD -->|"VehicleDataService gRPC"| HARRY
VD --> MON
HARRY --> DRM
DRM --> DISP
HARRY -->|"screen buffer"| MON
The arrow worth noticing is the one that is missing: nothing draws to the cluster display except HAR. Android's contribution reaches the glass only by passing through HAR, which is what makes the availability guarantee enforceable.
62.9.2 The Workspace¶
The crate map¶
packages/services/display_safety/ is a single Cargo workspace whose root
Cargo.toml lists 52 members, organized into four tiers plus prebuilts. The
table below is a useful guide to where the weight sits:
| Area | Path | What it is |
|---|---|---|
| Graphics | framework/graphics/ |
Impeller binding, GL context, DRM/KMS presentation, external images |
| Utilities | utils/ |
Design tooling, test apps, report generators, parity checking |
| View IR | framework/squoosh/ |
Intermediate representation of view parameters |
| Core runtime | framework/harry/ |
The HAR framework: pre-renderer, display-list generation, frame loop |
| Audio | framework/audio/har-audio/ |
Chime playback, streams, mixing |
| Platform API | framework/api/har-platform-api/ |
The platform abstraction layer (traits) |
| Platform impls | reference/platforms/ |
har-platform-android, har-platform-linux, headless looper, logging |
| App core | reference/harry-app-core/ |
Reducer, state, presenter, heartbeat watchdog |
| SDV services | service/ |
Service bundle, gRPC services, vehicle data publisher |
| Reference app | reference/harry-app/ |
The harry binary itself |
| Safety monitor | reference/safety-monitor/ |
Telltale monitoring and vehicle-data server |
| Customizations | framework/customizations/ |
Design customization API and derive macros |
| Monitoring | framework/har-monitoring/ |
Performance and latency instrumentation |
| Layout | framework/har-layout/ |
Taffy-based layout wrapper |
| Screen capture | reference/screencap/ |
Framebuffer read-back for the monitor |
| Display list | framework/display_list/ |
The backend-independent scene description |
The shape of that table is itself informative. framework/display_list/ is tiny
because the display list is deliberately a narrow, dumb data structure. It is the handoff between "what to draw", computed on one thread, and "how to draw it",
executed on another. The graphics tier is large because owning a display without
a compositor means implementing buffer management, fencing and mode setting
yourself.
A Cargo workspace inside Soong¶
The tree is a genuine Cargo workspace: rust-toolchain.toml pins channel
1.90 and the targets aarch64-linux-android, x86_64-linux-android and
x86_64-unknown-linux-gnu. It also builds under Soong, with 80 Android.bp files that carry the platform build. The dual arrangement lets the same code build for a Linux desktop (where a developer can iterate in a window) and
for the device.
The graphics engine is not built from source in AOSP. prebuilts/ carries
Impeller as prebuilt libraries per OS and architecture (prebuilts/impeller/android,
prebuilts/impeller/linux). It also carries impeller-rs-bindgen, the generated Rust FFI bindings, and a hand-written impeller-rs-bindgen-wrapper. The wrapper gives the rest of the tree a safer API than raw bindgen output.
62.9.3 The Platform Abstraction Layer¶
The trait surface¶
HAR is written to be portable across operating systems that have nothing in common with Android. The documentation names Linux and QNX alongside Android. QNX is a common choice for safety-certified automotive systems. Portability
is achieved the ordinary Rust way: framework/api/har-platform-api/ defines
traits, and a platform crate implements them.
The module list in framework/api/har-platform-api/src/lib.rs is the shape of
the abstraction: audio, camera, graphics, i18n, looper, tracing,
user_input, vehicle_data, resource_manager, test_support, commlib.
The load-bearing traits:
| Trait | Defined at | Responsibility |
|---|---|---|
GlContextFactory |
framework/api/har-platform-api/src/graphics.rs:179 |
Create the GL context HAR renders through |
Renderer |
framework/api/har-platform-api/src/graphics.rs:227 |
Draw a frame |
TakeScreenshot |
framework/api/har-platform-api/src/graphics.rs:315 |
Read the framebuffer back |
AudioApiFactory |
framework/api/har-platform-api/src/audio/v2/audio_api_factory.rs:39 |
Open the platform's audio output |
AudioApi |
framework/api/har-platform-api/src/audio/v2/audio_api.rs:79 |
Play and mix chimes |
ICameraManager |
framework/api/har-platform-api/src/camera/api.rs:34 |
Enumerate and open cameras |
PlatformVehicleData |
framework/api/har-platform-api/src/vehicle_data/api.rs:281 |
Stream vehicle state in |
VehicleDataListener |
framework/api/har-platform-api/src/vehicle_data/api.rs:262 |
Receive vehicle-state updates |
LayoutHelper |
framework/api/har-platform-api/src/looper/api.rs:105 |
Text metrics, wrapping and shaping |
RenderObserver |
framework/api/har-platform-api/src/looper/observer.rs:27 |
Observe frames as they complete |
PlatformTracing |
framework/api/har-platform-api/src/tracing.rs:20 |
Route spans to the platform tracer |
PlatformTestSupport |
framework/api/har-platform-api/src/test_support/api.rs:47 |
Test setup, synthetic events, artifacts |
ResourceManager (framework/api/har-platform-api/src/resource_manager.rs:204)
is a struct rather than a trait. It is an in-memory cache of startup resources (images and design documents), so that first frame does not wait on storage.
The framework cannot be built alone. The platform documentation is explicit that a platform implementation must be supplied. It also says that this is intentional. The framework is a framework, and a half-implemented platform should fail to compile rather than fail at 70 km/h.
The platform implementations¶
reference/platforms/ carries the implementations that ship:
har-platform-android— the device path, used in the SDV Media VM.har-platform-linux— the development path. Windowed mode useswinitandglutin. Headless mode uses a pbuffer context fromhar-gl-contextand needs no display hardware. This is what makes the design compiler able to render cluster designs on a build machine.har-looper-headless— a looper with no display attached.har-log— platform logging.harry-app-platform-specific— the glue that binds the reference app to whichever platform it was built for, and where Cargo features such astracing-androidare selected.
Input is a platform concern too. reference/har-user-input-evdev/ is the Linux reference implementation. It reads touch, keyboard and mouse from evdev. The PAL exposes KeyEvent, TouchEvent, RawUserInputEvent and UserInputEvent
in framework/api/har-platform-api/src/user_input/.
One error type¶
Every platform call returns a Result whose error is the framework's own enum,
defined in framework/api/har-platform-api/src/error.rs:34. A platform
implementation cannot leak its own error type into application code. This is an important property when the application is supposed to behave identically on
three operating systems.
62.9.4 From Figma to Display List¶
HAR does not have a widget toolkit in the Android sense. Its UI is a compiled Figma document. The renderer's first job each frame is to turn design plus state into a flat list of drawing commands.
The design document¶
Cluster designs are authored in Figma and serialized — the same Automotive Design for Compose lineage that DriverUI uses on the Android side. The serialized document carries the node tree, images, components and metadata.
What the Rust code supplies is not a widget tree but a set of overrides onto
named nodes in that document. An application declares a struct whose fields are
annotated with the node each one drives, and the derive_customizations
procedural macro in framework/customizations/ generates the plumbing:
#[derive(DesignDocument, Debug)]
#[DesignDocument(root)]
pub struct UiModel {
#[Design(node = "BackgroundLayer")]
pub background_layer: HeartbeatVariant,
#[Design(node = "ViewModeHar")]
pub view_mode: ViewModeVariant,
// ...
}
(reference/harry-app-core/src/ui/model.rs.) A field's type decides how it is
applied: a bool toggles visibility, a String sets text, and an enum swaps a Figma component variant. Each application returns an UpdatePolicy that says whether anything needs redrawing. The retained document is therefore loaded once
and re-skinned every tick, which is why a frame costs so little when nothing has
changed.
This is why the toolchain in Section 62.9.8 can make strong claims about what is on screen. The design is data. The same data feeds the renderer and the compiler. The compiler can therefore produce golden images that correspond exactly to what the renderer will draw.
The reducer and the presenter¶
reference/harry-app-core/ holds the application's state machine. Actions arrive
— vehicle data, input events, messages from Android — and a reducer folds them
into state; a presenter maps state onto the UI model. The important structural
choice is that this work happens on its own thread, off the display loop. This way, a slow state update cannot miss a frame. Work only proceeds to rendering when the
update policy says something actually changed.
Layout¶
framework/har-layout/ wraps Taffy, a Rust flexbox/grid layout engine.
Text is not something a layout engine can resolve alone. For this reason, the PAL supplies LayoutHelper (looper/api.rs:105) for metrics, wrapping and shaping. It also supplies LayoutHelperManager (looper/api.rs:94) to hand them out. Layout results
are positions and sizes for the nodes the display list will then describe.
Display-list generation¶
The output of the pre-render phase is a Vec<DisplayListEntry>. The entry type
is defined at framework/display_list/src/display_list.rs:256. What an entry looks like is DisplayListAppearance
(framework/display_list/src/display_list.rs:106). Its variants are the complete drawing vocabulary. They are Rect, RoundedRect (with per-corner radii and a
corner-smoothing factor), Text, and StyledText (runs with individual styles). They also include Path (separate fill and stroke vector paths), VectorRect, Arc, and the
state-stack operations including PushClipRegion.
Generation itself lives in the harry crate rather than in display_list. generate_dl is defined at
framework/harry/src/pre_renderer/generation.rs:68. It recurses through the view tree via generate_dl_recursive
(framework/harry/src/pre_renderer/generation.rs:85), driven from
framework/harry/src/pre_renderer/mod.rs:261. The split is deliberate:
display_list is a vocabulary that knows nothing about views, and the
pre-renderer is the thing that speaks it.
Arc deserves a note, because it is the reason gauges work. A tachometer sweep
is an arc whose angle is a function of vehicle data. The meter values computed
during pre-render adjust arc sweep, rotation and scaling directly. As a result, a needle moves without any per-frame re-layout of the design.
The four phases of a frame¶
graph TB
subgraph REDUCER["Reducer thread"]
A["Action<br/>vehicle data, input, Android"] --> B["State update"]
B --> C["Presenter<br/>state to UI model"]
C --> D["Layout<br/>Taffy + text shaping"]
D --> E["generate_dl<br/>DisplayListEntry list"]
end
subgraph LOOP["Looper thread"]
F["ImpellerRenderer<br/>display list to GL commands"]
G["swap_buffers"]
end
subgraph HWPATH["Display"]
H["DRM atomic commit<br/>with GPU fence"]
I["Scanout"]
end
E -->|"ViewDescriptor over MPSC channel"| F
F --> G
G --> H
H --> I
The channel in the middle is the architectural seam. Everything to its left is "decide what the frame contains" and can take as long as it takes. Everything to its right is "put it on the glass" and must not block.
62.9.5 Rendering with Impeller¶
Why Impeller and not Skia¶
Android's own UI renders through Skia (Chapter 13). HAR uses Impeller, the engine developed for Flutter, and the reason is shader compilation. A renderer that compiles shaders lazily at first use produces a visible hitch the first time a given effect appears. That is acceptable in an app, but not acceptable when the effect in question is a warning lamp. Impeller ships a precompiled shader set, so the rendering cost of a frame does not depend on whether it is the first of its kind. Fast startup and a compact binary matter for the same reasons the rest of HAR is small.
The render pass¶
Impeller is not rebuilt from source here. prebuilts/ carries the engine as a
prebuilt SDK pinned to a specific Flutter engine commit. It also carries bindgen-generated
FFI bindings over Impeller's public C interface and a hand-written wrapper crate
above them. The Rust binding exposes three backends, but only one is real for this product. create_open_gles is the production path. The Metal and Vulkan constructors log warnings that call themselves experimental and untested.
The renderer creates an impeller::Context with the OpenGL ES backend. It supplies a callback that resolves GL function pointers. It renders into a platform-provided framebuffer through a wrapped FBO, and it does not create its own window. Resources are registered up front. They are images in standard formats and KTX2 compressed textures, and fonts as TrueType/OpenType registered with the typography context. For camera and externally rendered content, EGLImage handles are bound as textures, so no pixels are copied.
Drawing a frame walks the display list. It translates each DisplayListEntry into Impeller calls. It uses save/restore for the transform, clip and mask stack that PushClipRegion and its siblings describe. Then it hands the result to the surface and swaps.
62.9.6 Owning the Display¶
Stopping SurfaceFlinger¶
The most direct evidence of what HAR is comes from its init script. In
service/product/harry_apex/init.display_safety.har.rc, the block that runs when
APEXes are ready does this before starting the renderer:
on property:apex.all.ready=true
# ... camera emulation and data folder setup ...
stop surfaceflinger
# start the HAR application
start harry
On a Media VM there is no Android compositor. HAR performs its own mode setting
and talks to DRM/KMS directly, which is what "direct rendering" means throughout
this code. framework/graphics/har-gl-context/ implements it. DrmCard::open_as_gbm_device opens the DRM device as a GBM device
(framework/graphics/har-gl-context/src/har_gl_context_factory.rs:61), and devices are cached per path in a RwLock<HashMap<String, Arc<Mutex<GbmDevice>>>>. HarDirectRenderingContext::init_boxed builds the context on top of it
(.../har_gl_context_factory.rs:79). Mode setting proper lives in
framework/graphics/har-gl-context/src/direct_rendering/kms.rs.
The swap chain and its fences¶
Without a compositor, HAR maintains its own swap chain. Impeller renders into an offscreen draw buffer; an optional resolve buffer exists for MSAA; the render buffer is GBM-backed and becomes the next front buffer.
A swap blits draw into render. It creates an EGL_SYNC_NATIVE_FENCE_ANDROID fence that tracks GPU completion. It builds a DRM atomic request that carries that fence as an in-fence, so the display controller cannot scan out an unfinished frame. It requests an out-fence that signals when the previous front buffer has left the display. It commits non-blocking. The out-fence is cached and consulted on the next swap so the
renderer never draws into a buffer that is still being displayed.
This is the same class of problem SurfaceFlinger solves for ordinary Android. HAR solves it in a few thousand lines, because it only has to solve it for one producer and one display.
The wait on that fence is bounded, which is the point most worth noticing. The
swap path waits at most 500 ms
(framework/graphics/har-gl-context/src/direct_rendering/har_direct_rendering_context.rs:319). On expiry it logs that the display hardware may be hung. It also increments a fence_timeouts counter alongside the buffer-swap and FPS statistics the context
keeps, destroys the sync object and returns. It does not block the render thread forever on wedged hardware.
The same defensive shape appears throughout the render path. Drawing a frame with no surface is a no-op rather than a panic. An external image that has not arrived yet is skipped with a warning instead of failing the frame.
External images¶
Not everything HAR shows is drawn by HAR. Camera preview and externally rendered
content arrive as buffers. framework/graphics/external-image/ binds them as
EGLImage textures so they can be composited into the scene without a copy. A
dedicated offscreen context serves external renderers. They draw into pooled
surfaces and pass a shared handle back over a channel for the main renderer to
bind.
62.9.7 Availability: the Heartbeat Contract¶
This section is the heart of the chapter: the mechanism that earns the name "high availability".
The problem stated precisely¶
DriverUI runs in the IVI VM. It renders media, maps and telephony into regions of the cluster that HAR keeps transparent for it. If DriverUI (or the whole Android guest) hangs, crashes, or reboots for an update, those regions would otherwise display stale content. Examples are a navigation instruction from two minutes ago, or a frozen album cover. Either would be presented with the authority of an instrument cluster. Stale is worse than absent.
So HAR does not trust Android to report its own health. It requires continuous proof of life and treats silence as failure.
The DriverUIService protocol¶
The contract is a gRPC service defined in
service/har-grpc-services/src/proto/driverui.proto:177:
service DriverUIService {
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse) {}
rpc DocumentSwitched(DocumentSwitchedRequest) returns (DocumentSwitchedResponse) {}
rpc DocumentUpdated(DocumentUpdatedRequest) returns (DocumentUpdatedResponse) {}
rpc DesignTokenUpdate(DesignTokenUpdateRequest) returns (DesignTokenUpdateResponse) {}
rpc LocaleUpdate(LocaleUpdateRequest) returns (LocaleUpdateResponse) {}
rpc ChangeVariant(ChangeVariantRequest) returns (ChangeVariantResponse) {}
rpc ChangeContainerConfiguration(ChangeContainerConfigurationRequest) returns (ChangeContainerConfigurationResponse) {}
}
A heartbeat carries the sender's uptime and, importantly, which sender it is
(driverui.proto:24):
message HeartbeatRequest {
// system uptime in millis
uint64 uptime = 1;
// Heartbeat source
enum Source {
SOURCE_UNKNOWN = 0;
SOURCE_ANDROID = 1;
SOURCE_INSTRUMENT_CLUSTER = 2;
SOURCE_CAMERA_SERVICE = 3;
}
Three real sources are tracked independently, mirrored on the Rust side as
HeartbeatSource at reference/harry-control-api/src/driver_ui.rs:27. The camera service has its own heartbeat. This matters for the rear-visibility path in
Section 62.9.10, where a frozen camera feed is a safety problem in its own right.
The watchdog¶
reference/harry-app-core/src/heartbeat_watchdog.rs is small and does one
thing well. HeartbeatWatchdog decorates the callback that receives DriverUI RPCs. It spawns a single worker thread that tracks every source:
That constant, at heartbeat_watchdog.rs:34, is the whole availability policy:
two seconds of silence from a source and it is considered disconnected. The
worker keeps last_seen: HashMap<HeartbeatSource, Instant> and sleeps only until
the nearest deadline, so a timeout is detected promptly without polling:
let loop_timeout =
last_seen.values().map(|timestamp| *timestamp + timeout_duration).min().map(
|next_deadline| next_deadline.saturating_duration_since(Instant::now()),
);
When a deadline passes, the source is dropped from the map and a disconnection is
delivered (heartbeat_watchdog.rs:92):
last_seen.retain(|source, timestamp| {
if timestamp.elapsed() > timeout_duration {
if let Err(err) = delegate_clone.send_guest_disconnected(*source) {
log::warn!("Error delivering heart beat timeout for {:?}: {:?}", source, err);
}
false // Remove
A heartbeat is correspondingly cheap to receive. The RPC handler forwards the source to the worker thread to reset that source's deadline. Then it delegates the call onward.
What a disconnection does¶
send_guest_disconnected (reference/harry-app-core/src/data_channel.rs:63)
turns the timeout into an ordinary action on the same channel the rest of the
application state travels on:
self.channel.send(Actions::CustomAction(CustomActions::GuestStateUpdateAction(
CommunicationData {
connected: false,
data: Some(AndroidData::Heartbeat(HeartbeatData {
uptime: 0,
source: heartbeat_source as _,
})),
},
)))
From there it is just state. The reducer records that the guest is disconnected. The presenter reflects it in the UI model. HAR no longer keeps that region transparent: the area becomes opaque and shows a placeholder instead of whatever Android last put there. When heartbeats resume, the region becomes transparent again and Android's content reappears.
The design is worth appreciating. There is no special emergency path, no separate failure renderer, no attempt to restart Android from the cluster. Loss of the guest is modeled as a state change like any other. This means the same code that runs constantly exercises it, rather than a rarely-taken branch that might have rotted.
Guest availability as seen by HAR¶
stateDiagram-v2
[*] --> Disconnected
Disconnected --> Connected : heartbeat
Connected --> Disconnected : 2s silence
While a source is Connected, its region stays transparent and Android's content
is shown. Every heartbeat that arrives inside the window simply resets that
source's deadline. While it is Disconnected, the region is opaque and carries a
placeholder. Each source (Android, instrument cluster, camera service) moves through this machine independently. As a result, a wedged camera service does not blank the
navigation region and vice versa.
62.9.8 The Safety Design Toolchain¶
Knowing that HAR drew a telltale is not the same as knowing the telltale is visible. The toolchain exists to make the second claim checkable, and it starts before the vehicle ever runs.
The design compiler¶
har_design_compiler takes the serialized Figma document plus a configuration
file and produces the artifacts the runtime monitor will use:
The configuration supplies what the design cannot. It gives the root node of the safety-relevant display and the target display ID and resolution. It also gives the dictionary that maps vehicle bus signal names to Figma node IDs. Telltales are addressed by design path, in the style #cluster/telltale/abs.
The compiler renders headless with Impeller. This is why the Linux headless platform from Section 62.9.3.2 exists at all. The same renderer that will draw the real thing produces the golden images.
What it emits¶
The output archive contains:
data.json— the manifest. It contains astatic_ui_elementsarray that gives each safety element's name, position and size. It also contains ascreenobject with the display resolution, and abuildobject that records the Figma document ID and the compiler version.- Per-element golden images — each safety-relevant element rendered in isolation in its active state, in a directory structure mirroring the design hierarchy.
- Full-UI verification images — complete screenshots with each telltale both active and inactive, for test and for human review.
- An updated design document — the original with
RenderOptions::PixelPerfectset on the telltale nodes. The runtime renderer then draws them without the filtering that would defeat a pixel comparison.
That last item closes the loop. The compiler does not merely describe what the telltales should look like. It adjusts the design so that what the renderer produces is comparable.
Human review and approval¶
Compiled artifacts are reviewed by a person, and the review is itself tooled.
utils/human-readable-report-generator renders the artifacts into an HTML report
showing each node in its active state; a second binary records an approval:
cargo run --bin approve-hrr -- -f /path/to/compiler_inspection_output.html \
-n "Name" -e email@domain.com -o output/path
The resulting approval_file.json records the approver and a SHA-256 hash of the
report, binding the approval to the exact artifacts reviewed.
The documentation credits the separation of design compilation from code generation for letting the code generator reach an ISO 26262 Tool Confidence Level of TCL-3. This is a claim about the toolchain's qualification, not about any particular vehicle's certification.
62.9.9 The Safety Monitor¶
The loop¶
har_safety_monitor is a separate process, built from
reference/safety-monitor/. It is started with the compiler's output:
Those arguments are parsed into Args
(reference/safety-monitor/src/main.rs:47), and load_data_json
(reference/safety-monitor/src/telltale_monitoring.rs:90) deserializes the manifest into DataJson, StaticUiElement, Screen and Build
(telltale_monitoring.rs:83, :48, :61, :73).
start_telltale_monitoring_thread then spawns the comparison loop. Its own
comment states the job plainly (telltale_monitoring.rs:130):
// Spawn a thread that will continuously compare the telltale golden image
// With the contents of the screen buffer.
The screen buffer comes from reference/screencap/, and how it is obtained matters. It does not ask Android for a screenshot. Instead, it opens /dev/dri/card0 directly, imports the KMS framebuffer as a GBM buffer object, and mmaps it. It synchronizes with DMA_BUF_IOCTL_SYNC. The crate's own README compares this technique to ffmpeg's kmsgrab. There is no SurfaceFlinger to ask, so the monitor reads the scanout buffer itself.
Regions of interest can be overridden with
rois_override: Option<Vec<har_screencap::Rect>> for testing, and results flow
out as TelltaleState over a channel.
The loop runs on a 100 ms cadence (MONITOR_LOOP_SLEEP_MS,
reference/safety-monitor/src/shared.rs:25) with the per-telltale checks spread
across cores by rayon. A 200 ms hold time (MONITOR_HOLD_TIME_MS,
shared.rs:28) debounces the result, so a single frame caught mid-transition
does not register as a mismatch.
The algorithms¶
Checks are pluggable behind one trait, TelltaleAlgorithm
(reference/safety-monitor/algorithms/interface/src/lib.rs:22), whose whole
contract is fn check(&mut self, buffer: &DisplayBuffer) -> bool. Four implementations ship, in reference/safety-monitor/algorithms/. The monitor picks one per telltale rather than applying one globally
(telltale_monitoring.rs:216-331).
The choice follows this order. It is a computer-vision task if data.json specifies one. Otherwise it is a TFLite model, if one exists for that element. Otherwise it is a correlation filter, when a search margin is configured. Otherwise it is a plain pixel check.
The simplest is static_pixel_check: slide the golden image over the region and
compare opaque pixels. It allows a budget of 20 mismatched pixels
(PIXEL_TOLERANCE, algorithms/static_pixel_check/src/lib.rs:22).
correlation_filter does Zero-mean Normalized Cross-Correlation. It uses integral images, so each candidate window's mean and variance are O(1).
tflite_inference runs a per-telltale TFLite model with a selectable
CPU/GPU/NNAPI delegate and treats a score above 0.5 as present. har-cv is a
small but real vision library — average-color checks, Sobel edge detection and
Harris corner detection, with unit tests against synthetic shapes.
The computer-vision implementation (telltale_monitoring.rs:111) wraps a
processor from that har_cv library:
whose check reduces to matching the watcher for this telltale against the processed buffer:
fn check(&mut self, buffer: &har_screencap::DisplayBuffer) -> bool {
if let Ok(results) = self.processor.process(buffer) {
for res in results {
if res.watcher_id == self.name {
return res.is_matched;
}
}
}
false
}
A TFLite path exists behind the tflite feature, with a TfliteDelegate passed
through to the monitoring thread and a wrapper crate at utils/tflite_wrapper/. The non-TFLite build compiles the same entry point with the delegate typed away
as () (telltale_monitoring.rs:171-176). Note that the false returned when
the processor errors is a not matched answer, not a silent pass. An unreadable screen is treated as a telltale that is not visible.
What a verdict actually does¶
Here it is worth being exact, because the obvious assumption is wrong. The
monitor compares what vehicle data says a telltale should be doing against what
the pixels say. It classifies each element as Match, FalsePositive
(lit when it should not be), Missing (dark when it should be lit), or Normal
(reference/safety-monitor/src/main.rs:248-256). A persistent mismatch produces
two things: an error in the log, and a SetDebugOutlinesRequest sent over gRPC
to the renderer (main.rs:286). The renderer draws a colored box around the offending region: green for match, yellow for false positive, red for missing
(reference/harry-vehicle-data-grpc/proto/debug.proto).
That is all it does. The monitor does not blank the display, tear down the
document, alert the driver, or fail over to anything. A search of this code for enforcement machinery turns up none. It is an independent observer that reports,
and the RPC it reports through is named DebugService.
The safety argument it supports is evidentiary. A component that did not draw the frame can state whether the regulated content is on it. The response to a mismatch belongs to whatever integrates the system, not to this binary.
Vehicle data on the monitor side¶
reference/safety-monitor/src/vehicle_data_server.rs gives the
monitor its own view of vehicle state. This way, the component that checks the screen does not ask the component that draws the screen what should be there. The
publisher fans the same stream out to both consumers: the renderer on
127.0.0.1:50051 and, when persist.product.harplatform.safetymonitor names
one, the monitor on port 50052. The two sides therefore receive the data independently. Neither side relays it to the other. The monitor keeps only what it needs.
It filters the stream down to the telltale signals. These are ABS, airbag, brake, fog lights, high and low beam, low tire pressure, park lights, traction, and the two seatbelt signals. It ORs the two seatbelt signals into a single no-seatbelt state. It ignores speed, gear and RPM entirely.
Independent delivery, compared at the glass¶
graph TB
BUS["Vehicle bus / VHAL"]
VD["vehicledata publisher<br/>service bundle"]
BUS --> VD
VD -->|"VehicleDataService"| HARRY["harry<br/>renders telltales"]
VD -->|"vehicle_data_server"| MON["har_safety_monitor"]
HARRY --> FB["Framebuffer"]
FB -->|"screencap"| MON
GOLD["Golden images<br/>+ data.json"] --> MON
MON --> V{"Expected state<br/>matches pixels?"}
V -->|"Yes"| OK["Telltale state recorded"]
V -->|"No"| ERR["Mismatch logged"]
62.9.10 Camera, Audio, and Vehicle Data¶
The camera view runs against a regulatory clock¶
Rear visibility is not a convenience feature. The documentation cites the requirements that the camera path is built to meet. The image must be displayed within 2.0 seconds of selecting reverse (CFR 571.111 S5.5.3). The camera path must sustain at least 30 Hz nominal and 15 Hz in low light. It must form an image in under 55 ms at 22°C ± 5°C. Total system latency must stay under 200 ms (UNECE R46 6.2.2.3.4 and its subsections).
The abstraction is deliberately EVS-like. ICameraManager
(framework/api/har-platform-api/src/camera/api.rs:34) enumerates and opens
devices; ICameraDevice starts and stops streams and returns consumed buffers;
ICameraStreamListener receives events. The descriptor and configuration types —
CameraDescriptor, CameraStreamConfiguration, CameraParameterDescriptor,
CameraLocation, FrameRate — carry resolution, frame rate, pixel format and
mounting information.
Two details matter for safety. The camera input block owns the frame buffers, and the application cannot write to them. A consumer therefore cannot corrupt a frame that another consumer is using. The event vocabulary includes an explicit stream-hang event. This is how a camera that has stopped delivering frames becomes visible to the system rather than simply appearing frozen. That pairs
with SOURCE_CAMERA_SERVICE in the heartbeat enum: a stalled camera is detected
both at the stream and at the availability layer.
Chimes¶
Cluster audio is warning chimes, not media, and framework/audio/har-audio/
implements it against the PAL's AudioApiFactory and AudioApi.
The manager plays a chime on a specified device with given behaviors
(framework/audio/har-audio/src/audio_manager.rs:264). WAV assets load lazily on
first play (src/assets/impls/wav/wav_asset.rs:44). A stream controller can block
until a chime completes (src/stream/stream_controller.rs:146). Streams carry a state machine with an interruption path that fades a chime out and does not cut it (src/stream/stream.rs:273). The PAL exposes the vocabulary the
automotive context needs: AudioBus, VolumeMillibel, PlaybackMode,
Spatialization.
Vehicle data plumbing¶
Vehicle state reaches HAR as a stream. The service is defined at
service/har-grpc-services/src/proto/vehicledata.proto:18:
service VehicleDataService {
rpc ReceiveVehicleData(stream VehicleData) returns (stream VehicleDataStreamResponse) {}
}
Bidirectional streaming rather than polling, which suits a signal source that
updates continuously. On the framework side the PAL models this as
PlatformVehicleData and VehicleDataListener
(framework/api/har-platform-api/src/vehicle_data/api.rs:281 and :262) over a
generic VehicleData<T> with a VehicleDataType discriminant
(vehicle_data/api.rs:23, :46). The renderer therefore consumes vehicle state. It does not need to know whether the state came from VHAL, a CAN bus, or a test script from reference/vehicle-data-scripts/.
62.9.11 Packaging and Startup¶
The APEX¶
HAR ships as an APEX, com.google.display_safety.har, defined at
service/product/harry_apex/Android.bp:65, with companion modules for updates,
signing key, certificate and init script (:96, :132, :142, :147). The
APEX carries the harry binary, the har_safety_monitor binary, and the
rendering assets. A second APEX,
com.sdv.google.display_safety.services_bundle, carries the SDV service-bundle
shared objects and their orchestration and ACL configuration.
The APEX packaging makes the renderer updatable independently of the platform
image. The harry service is declared updatable in the init script. At the same
time, the renderer starts early enough in boot to beat the framework.
The init sequence¶
service/product/harry_apex/init.display_safety.har.rc declares both processes:
service harry /apex/com.google.display_safety.har/bin/harry
class main
user root
group audio camera graphics
oneshot
disabled
updatable
service har_safety_monitor /apex/com.google.display_safety.har/bin/har_safety_monitor
class main
user root
group graphics
oneshot
disabled
Both are disabled, meaning init does not start them with their class; they are
started explicitly by property triggers. Both are also oneshot, which is the
more interesting flag: init will not respawn them when they exit. That is deliberate rather than an oversight. Restart policy for SDV workloads
belongs to the platform's orchestrator. The orchestrator supervises
service-bundle processes through a binder death recipient and a per-bundle
max_retries retry configuration. The default value comes from the
ro.boot.sdv.orchestrator.recovery.max_retries property.
Worth stating plainly, since "high availability" invites the assumption: there is
no in-process failover here, no hot standby renderer, and no self-restart logic
inside harry. Availability is achieved by starting early, owning the display,
and degrading visibly when a contributor fails — not by making the renderer
itself redundant. The renderer starts as soon as APEXes
are mounted, with the on property:apex.all.ready=true trigger. That trigger also
stops SurfaceFlinger, as shown in Section 62.9.6.1. The monitor waits for the
renderer's gRPC layer to come up:
on property:vendor.harplatform.grpc.started=true
# start the safety monitor
start har_safety_monitor
The ordering is the availability story in miniature. The thing with the
regulatory obligation starts at the earliest moment the system can start
anything. The thing that checks it starts as soon as there is something to
check. The same trigger block also starts an emulated camera. It loops an
mp4 into /dev/video10 with ffmpeg. This is how a Cuttlefish instance gets
a camera feed without camera hardware.
Products¶
device/google/sdv_display_safety/ layers the stack onto SDV products through
makefiles including sdv_harry_common.mk, sdv_ivi_cf_ds.mk,
sdv_ivi_arm64_ds.mk and sdv_media_har_cf.mk, and pulls the AAOS DriverUI app
in from packages/apps/Car/. The documented build and run path for the Media VM
is:
and for the IVI VM carrying DriverUI:
Two Cuttlefish instances are then launched with SDV boot parameters. The
parameters give each VM its instance name and virtio address. Alternatively,
both can be launched at once through ds_toolkit launch. This command deploys
both VMs with the display overlay configured.
62.9.12 Tracing and Performance¶
HAR instruments itself with the Rust tracing crate, bridged to Android's
ATrace through tracing_android_trace and enabled by the
harry-app-platform-specific/tracing-android Cargo feature. Spans appear as
ordinary trace sections:
let custom_trace = tracing::info_span!("custom_trace_span").entered()
// code here will be traced
drop(custom_trace)
The monitoring tier has its own instrumentation crate,
framework/har-monitoring/, with a monitoring.proto for
reporting, and the PAL's HarPerformanceMonitor lets a platform supply its own
implementation.
Traces are collected with Torq, a CLI for AAOS and SDV systems. Torq can
collect from both Display Safety VMs at once with Perfetto's traced_relay. It
produces a single timeline that shows harry and com.android.car.driverui
side by side. This is the only practical way to answer questions about cross-VM
latency, such as how long a vehicle signal takes to reach the glass.
utils/har-rendering-parity/ addresses a different question: whether HAR draws a
given Figma design the same way the other renderers of that design do. It
gathers three screenshots of the same component: one from HAR/Impeller, one
from DesignCompose, which runs on Jetpack Compose, and one from Figma itself.
It then emits an HTML report that shows them side by side. There is no automated pixel diff
or similarity score in the tool; its README is explicit that it exists for
manual comparison. Parity here is a human judgement about visual fidelity
against Figma as the source of truth, not an automated determinism proof.
62.9.13 Why HAR Looks the Way It Does¶
Pulling the pieces together: the High Availability Renderer is Android's admission that a safety-critical display should not be built out of general-purpose UI machinery. AOSP 17 does not try to harden the framework path until it could carry a regulatory obligation. Instead, it adds a small, independent renderer that runs beside it and outranks it.
The decisions that follow from that premise are consistent throughout the code. HAR is Rust and native, so it starts without waiting for a runtime. It uses Impeller with precompiled shaders, so a frame's cost does not depend on which effects appear for the first time. It stops SurfaceFlinger and drives DRM/KMS itself, so nothing can reach the cluster display without passing through it. Its UI is a compiled Figma document, so the same design data can drive both the renderer and a compiler that emits golden images.
HAR treats Android as an untrusted contributor. Android content is shown only while heartbeats keep arriving. Two seconds of silence are enough to replace that content with a placeholder. A separate process watches HAR. That process reads the framebuffer back and compares the telltale regions against those golden images. It uses vehicle data that it gets independently.
The last point is the one that generalizes beyond automotive. HAR does not claim correctness by construction. It produces evidence at runtime that what should be on the screen is on the screen. The evidence comes from a component that did not draw it. That is a markedly different engineering posture from the rest of the platform. It is the reason for the shape of the subsystem.
It is worth being precise about what the name does not mean, since "high
availability" carries baggage from server systems. There is no redundant
renderer, no hot standby, no failover, and no in-process recovery. If harry
dies, something outside it has to start it again. The safety monitor reports
and does not intervene. Availability here means something narrower and more
achievable:
- Be running before the framework is.
- Hold the display against everything else.
- Continue to draw the regulated layer from data you receive independently.
- Make a failed contributor visibly absent instead of silently stale.
Chapter 13 covers the HWUI and SurfaceFlinger path that HAR deliberately steps around, and Chapter 24 the display stack it takes over.
Further Reading¶
- Display Safety overview: https://source.android.com/docs/automotive/sdv/display-safety -- The authoritative documentation set for this subsystem covers HAR, DriverUI and the safety monitor. It has sub-pages for the code structure, the graphics pipeline, and the platform abstraction layer. Other sub-pages cover the camera view, audio chimes, performance tracing, and the safety design toolchain.
- HAR platform abstraction layer: https://source.android.com/docs/automotive/sdv/display-safety/har-pal -- The per-subsystem trait contract an integrator implements to bring HAR up on a new operating system.
- Safety design toolchain: https://source.android.com/docs/automotive/sdv/display-safety/toolchain
--
har_design_compiler, the generated artifacts, and the human approval step described in Section 62.9.8. - "I ran Doom on Android SDV's High Availability Renderer (HAR)": https://medium.com/@passenger6/i-ran-doom-on-android-sdvs-high-availability-renderer-har-and-learned-how-the-software-defined-e8ccf962311e -- A developer write-up of a third-party application that renders on HAR. It approaches the platform abstraction layer of Section 62.9.3 from the outside in. It is a useful counterpoint to reading the framework top-down. The fastest way to learn where the PAL's real boundaries are is this: try to put something through it that its authors never intended.
62.10 Try It¶
Exercise 62.1: Explore CarService Services¶
List all services registered by ICarImpl:
# On an automotive emulator or device:
adb shell dumpsys car_service --services
# List all VHAL properties:
adb shell dumpsys car_service --hal
# Check occupant zone configuration:
adb shell dumpsys car_service --services CarOccupantZoneService
Examine the CarService initialization timing:
# CarService boot timing is recorded in a system property:
adb shell getprop boot.car_service_created
# The full dump also includes per-service state:
adb shell dumpsys car_service -a
Exercise 62.2: Inspect Vehicle HAL Properties¶
Query vehicle properties using the car service shell:
# Get vehicle speed:
adb shell cmd car_service get-property-value PERF_VEHICLE_SPEED
# Get gear selection:
adb shell cmd car_service get-property-value GEAR_SELECTION
# List all property configs (or one config by name/id):
adb shell cmd car_service get-carpropertyconfig
# Set HVAC temperature (on emulator):
# set-property-value <PROPERTY> <areaId> <data>
adb shell cmd car_service set-property-value HVAC_TEMPERATURE_SET 49 22.5
Exercise 62.3: Trace a VHAL Property Event¶
Use atrace to follow a property change through the stack:
# Start tracing; CarService spans are emitted under existing atrace
# categories (there is no dedicated car_service tag):
adb shell atrace --async_start -c -b 8192 aidl wm am
# Trigger a property change (e.g., change gear on emulator)
# ... interact with the emulator's vehicle controls ...
# Stop tracing:
adb shell atrace --async_stop > car_trace.html
# Look for spans:
# VehicleHal.onPropertyEvent
# PropertyHalService.onHalEvents
# CarPropertyService.onPropertyChange
Exercise 62.4: Examine TV Input Framework¶
On a TV emulator or device:
# List registered TV inputs:
adb shell dumpsys tv_input
# List tuner resources:
adb shell dumpsys tv_tuner_resource_mgr
# Check HDMI-CEC state:
adb shell dumpsys hdmi_control
# Send a CEC message (requires root):
adb shell cmd hdmi_control cec_setting set hdmi_cec_enabled 1
Exercise 62.5: Investigate TV PIP Behavior¶
On a TV emulator:
# Check PIP state:
adb shell dumpsys activity service SystemUIService | grep -A 20 "PipController"
# Enter PIP mode for current activity:
adb shell input keyevent KEYCODE_WINDOW
# D-pad navigation while in PIP:
adb shell input keyevent KEYCODE_DPAD_LEFT
adb shell input keyevent KEYCODE_DPAD_RIGHT
Exercise 62.6: Check Device Form Factor¶
Determine the form factor programmatically:
# Check hardware type (set only on automotive builds):
adb shell getprop ro.hardware.type
# Returns: "automotive" on AAOS; empty on other form factors
# Check UI mode:
adb shell dumpsys uimode | grep "mCurUiMode"
# Check features:
adb shell pm list features | grep -E "automotive|leanback|watch"
# android.hardware.type.automotive
# android.software.leanback
# android.hardware.type.watch
Exercise 62.7: Inspect Automotive Power States¶
On an automotive emulator:
# Check current power state:
adb shell dumpsys car_service --services CarPowerManagementService
# Or query the active power policy:
adb shell cmd car_service get-current-power-policy
# Simulate garage mode:
adb shell cmd car_service garage-mode on
# Check garage mode status:
adb shell cmd car_service garage-mode query
# Simulate shutdown prepare:
adb shell cmd car_service power-off --skip-garagemode
Exercise 62.8: Explore Car SystemUI Components¶
# List Car SystemUI services:
adb shell dumpsys activity services com.android.systemui | grep car
# Check activity/task state tracked by CarService:
adb shell dumpsys car_service --services CarActivityService
# Inspect HVAC properties used by SystemUI:
adb shell cmd car_service get-property-value HVAC_TEMPERATURE_SET
adb shell cmd car_service get-property-value HVAC_FAN_SPEED
Exercise 62.9: Examine Occupant Zones¶
# Dump occupant zone configuration:
adb shell dumpsys car_service --services CarOccupantZoneService
# Output shows:
# - Zone definitions (driver, passenger, rear)
# - Display assignments per zone
# - User assignments per zone
# - Audio zone mappings
# - Input type support per display
Exercise 62.10: Build Automotive Emulator Image¶
# Set up the build environment:
source build/envsetup.sh
# Choose an automotive target:
lunch sdk_car_x86_64-userdebug
# Build the image:
m -j$(nproc)
# Launch the automotive emulator:
emulator -no-snapshot
Exercise 62.11: Trace CEC Message Handling¶
# Enable CEC debug logging:
adb shell setprop log.tag.HdmiCecLocalDeviceTv DEBUG
adb shell setprop log.tag.HdmiControlService DEBUG
# Watch CEC messages:
adb logcat -s HdmiControlService:D HdmiCecLocalDeviceTv:D
# Simulate a CEC device connection (if supported):
adb shell cmd hdmi_control onetouchplay
Exercise 62.12: Investigate WMShell Module Selection¶
Examine which WMShell module is active:
# On phone:
adb shell dumpsys activity service SystemUIService | grep "WMShell"
# On TV:
adb shell dumpsys activity service SystemUIService | grep "TvPip"
# Inspect the PIP implementation class:
adb shell dumpsys activity service SystemUIService | grep -A 5 "Pip"
Compare the TvWMShellModule dependencies with the default WMShellModule by examining:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
Exercise 62.13: Measure Wear Battery Impact of AOD¶
On a Wear emulator or device:
# Check display state:
adb shell dumpsys display | grep -A 5 "mScreenState"
# Monitor battery drain:
adb shell dumpsys battery
# Toggle ambient mode:
adb shell input keyevent KEYCODE_SLEEP
adb shell input keyevent KEYCODE_WAKEUP
# Check power stats:
adb shell dumpsys batterystats --checkin
Exercise 62.14: Compare RRO Layering¶
Examine how RROs stack across an automotive product:
# List all overlays:
adb shell cmd overlay list
# Check overlay state for SystemUI:
adb shell cmd overlay list com.android.systemui
# Dump overlay priority ordering:
adb shell dumpsys overlay | grep -A 3 "car"
Exercise 62.15: Source Code Exploration Tasks¶
Study the following files to understand form-factor abstractions:
-
CarService bootstrap chain:
packages/services/Car/service/src/com/android/car/CarServiceImpl.javapackages/services/Car/service/src/com/android/car/ICarImpl.javapackages/services/Car/service/src/com/android/car/VehicleStub.java
-
Vehicle HAL interface:
-
hardware/interfaces/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl -
TV Input Framework:
frameworks/base/media/java/android/media/tv/TvInputService.javaframeworks/base/services/core/java/com/android/server/tv/TvInputManagerService.javaframeworks/base/services/core/java/com/android/server/tv/TvInputHardwareManager.java
-
HDMI-CEC:
hardware/interfaces/tv/cec/1.0/IHdmiCec.halframeworks/base/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
-
WMShell TV variant:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.javaframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.javaframeworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
-
Car SystemUI:
packages/apps/Car/SystemUI/src/com/android/systemui/car/CarServiceProvider.javapackages/apps/Car/SystemUI/pods/systembar/base/controller/src/com/android/systemui/car/systembar/base/CarSystemBar.java
-
Occupant Zones:
packages/services/Car/service/src/com/android/car/CarOccupantZoneService.java
-
Fixed Activity:
packages/services/Car/service/src/com/android/car/am/FixedActivityService.java
-
Automotive Power:
packages/services/Car/service/src/com/android/car/power/CarPowerManagementService.javapackages/services/Car/service/src/com/android/car/garagemode/GarageModeService.java
-
Wearable Sensing: -
frameworks/base/services/core/java/com/android/server/wearable/WearableSensingManagerService.java-frameworks/base/core/java/android/app/wearable/WearableSensingManager.java
Exercise 62.16: Explore the SDV Platform (Architecture)¶
These exercises use the AOSP 17 source tree; none require building a vehicle. Run them from the root of a synced android17-release checkout.
-
List the Core VM's agents. Open
device/google/sdv/sdv_core_base/sdv_packages_core_services.mkand map eachSDV_CORE_SERVICES_PACKAGESentry to a subsection of §62.7. Which entries are agents, which are APEXes, and which are host build tools? -
Read a real service-bundle definition. Open
system/software_defined_vehicle/vpm/stable/vsidl/vpm.vsidl. Identify the bundle'sserver,client, andpublisherslots. Then opensystem/software_defined_vehicle/vpm/stable/vsidl/power.protoandvehicle.protoand list thePowerStateReportandVpmVehicleStateenum values. Which of these would an orchestrationconditionmatch against? -
Trace the lifecycle contract. Read
system/software_defined_vehicle/lifecycle_management/aidl/google/sdv/lifecycle/ILifecycleManager.aidlandhardware/sdv/interfaces/lifecycle_management/aidl/google/sdv/lifecycle/internal/IService.aidl. Match eachILifecycleManagermethod (launchService,startService,stopService,shutdownService) to theIServicecallback it triggers (onCreate,onStart,onStop,onDestroy). -
Inspect the orchestration config schema. Open
system/software_defined_vehicle/orchestration/distributed_config/src/protos/service_bundle_config.protoandcommon.proto. How does anInstancesStateConfigurationpair aconditionwith desired instance states? What are the three logical operators aConditioncan use? -
Find the frozen registry API. List
hardware/sdv/interfaces/service_bundles_registry/aidl_api/google.sdv.service_bundles_registry/and confirm the current frozen version. Open the version-3IRegistry.aidland confirm it exposes onlygetAvailableServiceBundlesMetadata(). -
Read the gateway allowlist. Open
system/software_defined_vehicle/sdv_gateway/README.mdanddevice/google/sdv/sdv_ivi_base/sdv_gateway_config.json. Explain whatallowed_native_packagenamegates, and why a UID of-1is a security risk worth avoiding. -
Find the display-safety bundle. In
packages/services/display_safety/service/har-sdv-service/Android.bp, find thelibhar_sdv_service_bundlemodule and list its dependencies. Which dependency is the SDV middleware, and which is the gRPC service layer?
Exercise 62.17: Explore the SDV Communication Fabric (Middleware)¶
These commands assume an SDV Core VM or a Cuttlefish SDV target (sdv_core_cf, sdv_ivi_cf) and a checked-out tree at system/software_defined_vehicle/.
-
Read a service bundle's contract. Open
system/software_defined_vehicle/samples/vsidl/complex/catalog/complex_message_publisher.vsidland its subscriber peer. Identify the sharedtopicname and the shared RPCchannel. That is the only thing the two halves agree on at authoring time. -
See what
vsidlcwould generate. Readsystem/software_defined_vehicle/vsidl/vsidlc/README.md, then look at the generation steps undersystem/software_defined_vehicle/vsidl/vsidlc/src/rust/steps/. Note that the output goes intooutput/generated_rs, the SDV analog of AIDL stubs. -
Trace the SOME/IP boundary. Read
hardware/sdv/interfaces/some_ip/stack_agent/aidl/google/sdv/someip/ISomeIpStack.aidl. Findrpc_transact,publish, andsubscribe_eventgroup. Confirm that every payload that crosses this interface is a rawbyte[]. Translation happens in the broker, not the stack. -
Inspect the gateway allowlist. Read
device/google/sdv/sdv_ivi_base/sdv_gateway_config.jsonand the "Service config" section ofsystem/software_defined_vehicle/sdv_gateway/README.md. Work out which UID is allowed to claim which SDV package name, and what an empty config would do. -
Map a UDS routine to a proto. Open
system/software_defined_vehicle/automotive_services/diagnostics/vsidl/v1/connection.protoand find theSessionTypeenum. Match each value against ISO 14229-1's diagnostic session types (default, programming, extended, safety-system). -
Check the mesh on a running target. On a Core VM, run
adb shell. List the SDV agent services withservice list | grep sdv(ordumpsys). Then look for the Data Tunnel APEXcom.android.sdv.dt. Inspecting/vvmtruststore/shows the mesh truststore the provisioning tool wrote.
Exercise 62.18: The High Availability Renderer¶
These use the AOSP 17 source tree; only the last needs a build.
-
Find the availability policy. Open
packages/services/display_safety/reference/harry-app-core/src/heartbeat_watchdog.rsand locateTIMEOUT_MS. Trace what happens when it expires, throughsend_guest_disconnectedindata_channel.rsto the action the reducer receives. Ask yourself what a longer or shorter value would trade away. -
Read the drawing vocabulary. In
packages/services/display_safety/framework/display_list/src/display_list.rs, read theDisplayListAppearancevariants. Which ones exist specifically to support instrument-cluster UI rather than general-purpose graphics? Compare the list to what Chapter 13 describes for HWUI. -
Follow a telltale end to end. Pick
abs. Find three places. First, find where a bus signal name is mapped to a design node in the compiler configuration. Second, find where the golden image for it would be emitted. Third, find wherereference/safety-monitor/src/telltale_monitoring.rscompares it against the screen buffer. -
Confirm who owns the display. Read
packages/services/display_safety/service/product/harry_apex/init.display_safety.har.rc. Note thestop surfaceflingerline and the two property triggers. Then findDrmCard::open_as_gbm_deviceinframework/graphics/har-gl-context/src/har_gl_context_factory.rsand follow it intodirect_rendering/kms.rs. -
Map the PAL. List the modules in
framework/api/har-platform-api/src/lib.rs, then openreference/platforms/har-platform-linuxand check which of them it implements. What would you have to write to bring up HAR on a new operating system? -
Build it. With an SDV-capable checkout,
lunch sdv_media_har_cf-aosp_current-userdebugand build. Then launch withds_toolkit launchand observe which process draws the cluster.
Summary¶
Form Factor Comparison Matrix¶
| Aspect | Phone | Automotive | TV | Wear |
|---|---|---|---|---|
| Primary input | Touch | Touch + Rotary + Voice | D-pad remote | Touch + Crown button |
| Display count | 1-2 | 2-6+ | 1 | 1 |
| Display shape | Rectangle | Rectangle | Rectangle | Round or square |
| SystemUI | Default SystemUI | Car SystemUI | TV SystemUI | Wear SystemUI |
| Launcher | Launcher3 | Car Launcher | TV Launcher | Clock/Tiles |
| WMShell module | WMShellModule | N/A (own SystemUI) | TvWMShellModule | Simplified |
| PIP support | Full (touch, drag) | None (safety) | TV PIP (D-pad) | None |
| Split screen | Yes | Per-display | TV Split | No |
| Navigation | Gesture/3-button | System bar buttons | D-pad focus | Swipe + button |
| Power model | Battery + charger | Vehicle ignition | Wall power | Small battery |
| Special power state | Doze | Garage Mode | Screen saver | Ambient/AOD |
| Multi-user | Single active | Per-zone users | Single | Single |
| Safety restrictions | None | UX restrictions while driving | None | None |
| Key HAL interfaces | Standard | Vehicle, EVS, AudioControl | TvInput, CEC, Tuner | Sensors |
| Feature flag | (default) | android.hardware.type.automotive | android.software.leanback | android.hardware.type.watch |
| ro.hardware.type | (unset) | automotive | (unset) | (unset) |
Key Source Trees by Form Factor¶
| Form Factor | Service Code | Apps | HAL Interfaces | Product Config |
|---|---|---|---|---|
| Automotive | packages/services/Car/service/ |
packages/apps/Car/ |
hardware/interfaces/automotive/ |
packages/services/Car/car_product/ |
| TV | frameworks/base/services/core/.../tv/ |
(vendor-specific) | hardware/interfaces/tv/ |
(vendor-specific) |
| Wear | frameworks/base/services/core/.../wearable/ |
(vendor-specific) | Standard + sensors | (vendor-specific) |
Android's form-factor extensibility is one of its most impressive architectural features. Rather than maintaining separate codebases, the platform uses a layered customization approach:
-
HAL abstraction separates hardware-specific details (Vehicle HAL, TV tuner HAL, CEC HAL) from framework logic.
-
System services extend the base framework with form-factor-specific functionality (CarService with ~40 subsystems, TvInputManagerService, TunerResourceManagerService).
-
Dagger module substitution (WMShellModule vs. TvWMShellModule) swaps entire subsystem implementations at build time without forking the base code.
-
SystemUI replacement (Car SystemUI, TV SystemUI) provides completely different user shells while sharing the underlying window management infrastructure.
-
Runtime Resource Overlays enable visual and behavioral customization without source changes, organized in product-specific overlay trees.
-
Product configuration (makefiles, properties, feature flags) wires everything together, selecting which packages, overlays, and policies constitute a complete form-factor product.
The automotive stack is by far the most complex, with multi-zone displays, vehicle power management, driver distraction enforcement, and a complete Vehicle HAL abstraction. TV adds the TIF, CEC, and specialized PIP. Wear strips away complexity, simplifying windowing and adding round-display and battery-optimization support. All three prove that Android's architecture -- despite its complexity -- is genuinely modular enough to serve radically different device categories from a single codebase.
Software Defined Vehicle¶
The SDV platform (§62.7 and §62.8) is the next step beyond AAOS. It does not run the whole vehicle stack inside one Android image. Instead, it splits the vehicle into a headless Core VM of service bundles and one or more AAOS IVI VMs. The IVI VMs reach the service bundles over a service fabric.
- SDV is a headless vehicle OS. The Core VM runs Android with no UI
(
device/google/sdv/sdv_core_base/sdv_core_base.mk). It hosts service bundles and the agents that supervise them. AAOS IVI VMs and external ECUs talk to it over a service fabric. - The unit of deployment is the service bundle, a VSIDL-generated Rust
.soshipped in an APEX with anSdvServiceBundleManifestentry. A Fully Qualified Instance Name (package/bundle/instance) identifies the bundle at runtime. - VSIDL replaces AIDL for bundle interfaces.
vsidlcand its companions generate Rust middleware bindings (generated_rs) and SOME/IP translation from.vsidl+.protocatalogs. Per-message translation modes (INTERPRET_AS_BYTES,DYNAMIC_LIBRARY) decide how a type crosses onto SOME/IP. - Four control-plane agents supervise bundles. The Service Bundles Registry (
IRegistry, frozen at v3) catalogs them. The Lifecycle Manager (ILifecycleManager+ theIServicecallbacks, vialifecycle_service_bundle_runnerprocesses) launches/starts/stops them. The Orchestrator holds a desired state per FQIN and reconciles it on every mode change with retry/recovery semantics. The Health Monitor watches per-bundle heartbeats and feeds failures back into recovery. - vpm drives the modes. The vehicle power-state manager (
sdv_vpm_agent) defines thePowerStateReportandVpmVehicleStateladders and publishes transitions, which are exactly what orchestration conditions match against. The Update Manager is power-aware in the same way. - The communication fabric is a network-spanning version of Android's IPC idioms. Three
Binder agents — Service Discovery (Rust), Data Tunnel (C++, named-topic pub/sub over FMQ), and
RPC (socket-based) — are handed to a bundle as one
ContextInitializationToken. Beneath them, a per-VM certificate authority, identity agent, and authorization service form a mutually-authenticated secure mesh over mTLS with P-256 certificates that encode the FQIN. - SOME/IP carries cross-VM and cross-ECU traffic. A C++
vsomeipstack agent presentsISomeIpStack(rawbyte[]payloads). A Rust broker maps SDV topics/RPC/messages onto SOME/IP services, events, and methods. - AAOS integrates through the SDV Gateway.
ISdvGateway/ISdvGatewaySession, plus privileged interfaces, give ordinary IVI native/Java clients and the VHAL proxy a UID-allowlisted door into the fabric (/vendor/etc/sdv_gateway_config.json).libvhal_proxymakes a Core-VM vehicle property look like an ordinary VHAL property to CarService, with SDV-RPC traffic on a dedicated VLAN. - The domain catalog and tooling round it out.
automotive_services/layers Diagnostics (ISO 14229-1 / AUTOSAR DEM, DoIP), Configuration, Calibration, Vehicle Mode, and User Profile on top.samples/andtools/provide runnable references and the host-side codegen/provisioning toolchain. Display safety (HARry) is an IVI-side Rust runtime whoselibhar_sdv_service_bundlejoins the SDV fabric, with ahar_safety_monitorenforcing distraction/compliance constraints, shipped as SDV-only APEXes.
Key Source Files Reference¶
SDV platform (§62.7) and middleware (§62.8):
| File | Purpose |
|---|---|
device/google/sdv/sdv_core_base/sdv_packages_core_services.mk |
Canonical list of agents/APEXes installed in the Core VM |
device/google/sdv/AndroidProducts.mk |
SDV lunch targets (Core/IVI/Media, cf and arm64) |
device/google/sdv/sdv_core_base/sdv_core_base.mk |
Declares SDV a headless vehicle Android OS |
system/software_defined_vehicle/service_bundles_registry/proto/sdv_service_bundles_manifest.proto |
Service-bundle manifest schema |
hardware/sdv/interfaces/service_bundles_registry/google/sdv/service_bundles_registry/IRegistry.aidl |
Registry stable AIDL (frozen v3) |
system/software_defined_vehicle/lifecycle_management/aidl/google/sdv/lifecycle/ILifecycleManager.aidl |
Lifecycle control interface |
hardware/sdv/interfaces/lifecycle_management/aidl/google/sdv/lifecycle/internal/IService.aidl |
Bundle lifecycle callbacks (onCreate/onStart/onStop/onDestroy) |
system/software_defined_vehicle/lifecycle_management/service_bundle_runner/src/main.rs |
The per-instance bundle runner process |
system/software_defined_vehicle/orchestration/engine/src/engine.rs |
Orchestrator reconciliation engine |
system/software_defined_vehicle/health_monitor/src/hb_config.rs |
Heartbeat monitoring configuration |
system/software_defined_vehicle/vpm/stable/vsidl/power.proto |
PowerStateReport enum |
system/software_defined_vehicle/vpm/stable/vsidl/vehicle.proto |
VpmVehicleState enum |
hardware/sdv/interfaces/vehicle_power_manager/aidl/google/sdv/vpm/IPowerStateClientApi.aidl |
Client power-state subscription AIDL |
system/software_defined_vehicle/update_manager/catalog/update_manager_service.proto |
Update Manager state machine |
hardware/sdv/interfaces/middleware/ctx/aidl/google/sdv/comms/ContextInitializationToken.aidl |
The use-once token bundling a Service Bundle's four agent connections |
hardware/sdv/interfaces/middleware/service_discovery/google/sdv/identity/ServiceFqin.aidl |
Vehicle-wide bundle identity (the FQIN) |
hardware/sdv/interfaces/middleware/rpc/google/sdv/rpc/IRpcAgent.aidl |
Socket-based RPC agent contract |
hardware/sdv/interfaces/middleware/data_tunnel/aidl/google/sdv/data_tunnel/IAgentService.aidl |
Named-topic pub/sub over FMQ |
hardware/sdv/interfaces/middleware/service_discovery/google/sdv/ca/ICertificateAuthority.aidl |
Per-VM CA for the secure mesh |
system/software_defined_vehicle/vsidl/vsidlc/README.md |
VSIDL compiler: catalog to generated_rs |
system/software_defined_vehicle/vsidl/language/src/protos/sdv/vsidl/v1/syntax.proto |
The VSIDL textproto grammar |
hardware/sdv/interfaces/some_ip/stack_agent/aidl/google/sdv/someip/ISomeIpStack.aidl |
SOME/IP stack boundary (raw bytes) |
system/software_defined_vehicle/some_ip/broker_agent_comms/src/main.rs |
SOME/IP-to-SDV broker (Rust) |
hardware/sdv/interfaces/sdv_gateway/google/sdv/gateway/ISdvGatewaySession.aidl |
Gateway session for non-SDV-aware clients |
system/software_defined_vehicle/sdv_gateway/README.md |
Gateway config, UID allowlist, SDV-RPC VLAN |
system/software_defined_vehicle/sdv_gateway/vhal_proxy/libvhal_proxy/README.md |
VHAL proxy: properties to Data Tunnel topics |
system/software_defined_vehicle/automotive_services/diagnostics/README.md |
Diagnostics: ISO 14229-1 / AUTOSAR DEM |
packages/services/display_safety/service/har-sdv-service/Android.bp |
The display-safety SDV service bundle |
packages/services/display_safety/Cargo.toml |
Workspace root; 52 members across framework, reference, service, utils |
packages/services/display_safety/rust-toolchain.toml |
Rust 1.90; Android arm64/x86_64 and Linux x86_64 targets |
packages/services/display_safety/framework/api/har-platform-api/src/lib.rs |
The PAL module list: graphics, audio, camera, looper, vehicle_data, user_input, tracing, test_support |
.../har-platform-api/src/graphics.rs |
GlContextFactory, Renderer, TakeScreenshot, DisplayRotation |
.../har-platform-api/src/vehicle_data/api.rs |
PlatformVehicleData, VehicleDataListener, VehicleData<T> |
.../har-platform-api/src/camera/api.rs |
ICameraManager, ICameraDevice, CameraDescriptor |
.../har-platform-api/src/audio/v2/audio_api_factory.rs |
AudioApiFactory; audio_api.rs defines AudioApi |
.../har-platform-api/src/looper/api.rs |
LooperOptions, LayoutHelper, RendererHelper, UiEventListener |
.../har-platform-api/src/error.rs |
The single framework error enum platform impls return |
packages/services/display_safety/framework/display_list/src/display_list.rs |
DisplayListAppearance variants and DisplayListEntry |
packages/services/display_safety/framework/harry/src/pre_renderer/generation.rs |
generate_dl / generate_dl_recursive: view tree to display list |
packages/services/display_safety/framework/graphics/har-gl-context/src/har_gl_context_factory.rs |
DrmCard::open_as_gbm_device, GBM device cache, HarDirectRenderingContext |
.../har-gl-context/src/direct_rendering/kms.rs |
DRM/KMS mode setting and atomic commit |
packages/services/display_safety/framework/har-layout/ |
Taffy-based layout wrapper |
packages/services/display_safety/framework/audio/har-audio/src/audio_manager.rs |
Chime playback on a named device |
packages/services/display_safety/reference/harry-app-core/src/heartbeat_watchdog.rs |
HeartbeatWatchdog, TIMEOUT_MS = 2000, per-source deadlines |
packages/services/display_safety/reference/harry-app-core/src/data_channel.rs |
send_guest_disconnected turns a timeout into a state action |
packages/services/display_safety/reference/harry-control-api/src/driver_ui.rs |
HeartbeatSource enum mirroring the proto |
packages/services/display_safety/reference/safety-monitor/src/main.rs |
har_safety_monitor entry point and arguments |
packages/services/display_safety/reference/safety-monitor/src/telltale_monitoring.rs |
DataJson/StaticUiElement, CvAlgorithm, the comparison thread |
packages/services/display_safety/reference/safety-monitor/src/vehicle_data_server.rs |
The monitor's independent vehicle-data view |
packages/services/display_safety/reference/screencap/ |
Framebuffer read-back for monitoring |
packages/services/display_safety/reference/platforms/ |
har-platform-android, har-platform-linux, headless looper, logging |
packages/services/display_safety/service/har-grpc-services/src/proto/driverui.proto |
DriverUIService, HeartbeatRequest and its Source enum |
packages/services/display_safety/service/har-grpc-services/src/proto/vehicledata.proto |
VehicleDataService.ReceiveVehicleData bidirectional stream |
packages/services/display_safety/service/product/harry_apex/Android.bp |
APEX com.google.display_safety.har and companion modules |
packages/services/display_safety/service/product/harry_apex/init.display_safety.har.rc |
harry and har_safety_monitor services; stop surfaceflinger; property triggers |
packages/services/display_safety/prebuilts/impeller/ |
Prebuilt Impeller per OS/arch, plus generated Rust bindings |
packages/services/display_safety/utils/human-readable-report-generator/ |
Safety artifact report and approve-hrr approval tool |
device/google/sdv_display_safety/ |
Product makefiles layering HAR onto SDV media and IVI targets |