Skip to content

Chapter 60: Automotive, TV, and Wear

Android is not a single-device operating system. The same platform that powers phones also drives car dashboards, living-room televisions, and wrist-worn wearables. Each form factor imposes radically different constraints -- an instrument cluster must never crash, a TV must respond to a D-pad, a watch must survive on a tiny battery for days -- yet all three share the core Android framework. This chapter dissects how AOSP adapts itself to three of its most divergent form factors: Android Automotive OS (AAOS), Android TV, and Wear OS. We will trace each vertical from the HAL layer through system services, window management, and specialized UI shells, quoting real source code and referencing actual file paths throughout.


60.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

60.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 constructs ICarImpl, which is the actual ICar.Stub binder service that clients connect to. The car_service name is published to ServiceManager, making 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 ensures 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
    ICS --> CHal
    CHS --> CHal
    CEVS --> EHal

    AVS --> VHAL
    HVS --> VHAL
    CEVS --> EVS_HAL
    CAS --> AC_HAL

60.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 with 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:

  1. Asynchronous operations: getValues() and setValues() use callbacks, not blocking returns. This prevents the car service from stalling on slow ECU communication.

  2. Shared memory for large payloads: The returnSharedMemory mechanism allows efficient transfer of bulk sensor data without repeated binder parceling.

  3. Property change modes: Properties are either STATIC (never change, like VIN number), ON_CHANGE (event-driven, like door state), or CONTINUOUS (polled, like vehicle speed).

  4. 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 VehicleAreaSeat bit 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 is responsible for subscribing to the VHAL properties it cares about, converting raw HalPropValue events into higher-level data, and routing 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, 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 (SimulationVehicleStub) exists for testing without real hardware.

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

60.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)

60.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.

60.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 where the cluster display runs a full Android activity (the "Cluster Home" app), and content is rendered via ClusterHalService communicating 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 information
  • NavStateController -- Handles navigation state display
  • ClusterViewModel -- 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 Renderer as Cluster Renderer (Vendor App)
    participant ClusterDisplay as Instrument Cluster Display

    NavApp->>CNS: sendNavigationState(bundle)
    CNS->>ICS: onNavigationStateChanged()
    ICS->>Renderer: IInstrumentCluster.setNavigationState()
    Renderer->>ClusterDisplay: Render navigation turn card

    Note over CHS,Renderer: Cluster state changes via VHAL
    CHS-->>ICS: onClusterStateChanged()
    ICS->>Renderer: Update cluster mode

60.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, re-launching it 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

60.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:

  1. Display-specific activity launching: When an app targets a specific occupant zone, the service routes the activity to the correct display using ActivityOptions:
// Usage pattern for launching on a specific display:
ActivityOptions options = ActivityOptions.makeBasic();
options.setLaunchDisplayId(targetDisplayId);
context.startActivity(intent, options.toBundle());
  1. SystemUI proxy registration: Car SystemUI registers itself as a proxy through ICarSystemUIProxy, allowing the service to control task presentation:
// packages/services/Car/service/src/com/android/car/am/CarActivityService.java
// (field declarations showing the proxy mechanism)
// Uses ICarSystemUIProxy and ICarSystemUIProxyCallback
  1. Blocking activity management: When a non-distraction-optimized activity attempts to display while driving, the service intercepts and replaces it with a blocking activity that shows a safety message. The display ID is passed via:
// From the import:
// import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_DISPLAY_ID;
  1. 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

60.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

60.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(). When garage mode completes (either all jobs finish or the timeout expires), 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 system property android.car.garagemodeduration. The minimum enforced duration is 15 minutes, ensuring enough time for critical updates.

Garage mode also handles edge cases:

  • STATE_SHUTDOWN_CANCELLED: If the driver turns the ignition back on during shutdown preparation, garage mode is immediately cancelled.

  • STATE_SUSPEND_ENTER / STATE_HIBERNATION_ENTER: Different paths for deep sleep vs. hibernate, both requiring garage mode cleanup before proceeding.

  • The completion callback pattern ensures 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 GMS as GarageModeService
    participant GMC as GarageModeController
    participant JS as JobScheduler

    Ignition->>VHAL: Ignition OFF
    VHAL->>CPMS: AP_POWER_STATE_REQ = SHUTDOWN_PREPARE
    CPMS->>GMS: enterGarageMode()
    GMS->>GMC: enterGarageMode()
    GMC->>JS: Schedule deferred jobs

    Note over GMC,JS: Jobs run: app updates, data sync, optimization

    GMC-->>GMS: All jobs complete / timeout
    GMS-->>CPMS: Garage mode finished
    CPMS->>VHAL: AP_POWER_STATE_REPORT = DEEP_SLEEP_ENTRY
    Note over VHAL: System enters deep sleep

60.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.

60.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_TEXT_INPUT<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.

60.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, adds HVAC controls, volume controls tailored for multi-zone audio, and a user picker for multi-user vehicles.

// packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/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/src/com/android/systemui/car/hvac/
  HvacButtonController.java       -- Handles HVAC button interactions
  HvacPanelOverlayViewMediator.java -- Manages HVAC panel visibility
  HvacView.java                    -- Base HVAC view
  HvacPanelView.java               -- Full HVAC panel layout
  TemperatureControlView.java      -- Temperature adjustment widget
  referenceui/
    FanSpeedBar.java               -- Fan speed control
    FanDirectionButtons.java       -- Air direction buttons

60.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:

  1. No home screen widgets: The automotive home screen emphasizes quick app access and essential information (maps, media) rather than customizable widget grids.

  2. Dock-based navigation: The dock at the bottom provides persistent access to navigation, phone, media, and app grid.

  3. Task stack awareness: The DockTaskStackChangeListener monitors the task stack to keep the dock state synchronized with what is actually running.

  4. Package filtering: AppLauncherUtils filters the app list to show only distraction-optimized applications when driving restrictions are active.

  5. 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

60.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.

60.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
  sv/            -- Surround View
  ivn_android_device/  -- In-Vehicle Networking
  occupant_awareness/  -- Occupant detection (presence, attention)
  remoteaccess/        -- Remote wake and task execution

60.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
  DriveModeSportRRO/      -- Sport driving mode theme
  DriveModeEcoRRO/        -- Eco driving mode theme
  overlay-config/
    androidRRO/           -- Framework resource overrides
    SettingsProviderRRO/  -- Default settings values
    TelecommRRO/          -- Telecom UI adjustments
  oem-design-tokens/
    OEMDesignTokenRRO/    -- OEM visual design tokens

60.2 Android TV

Android TV transforms Android into a 10-foot UI experience. The framework additions focus on three areas: a TV Input Framework (TIF) for managing broadcast and HDMI sources, HDMI-CEC control for device coordination, and a specialized windowing system for D-pad navigation and picture-in-picture.

60.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

60.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>

60.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 cancelled if the TV switches inputs or goes back to sleep, preventing unnecessary CEC traffic.

60.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.

60.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

60.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

60.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

60.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 over 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 TvPipModule provides Dagger dependency injection:

// frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java

@Module(includes = {
        WMShellBaseModule.class,
        Pip1SharedModule.class})
public abstract class TvPipModule {
    @WMSingleton
    @Provides
    static Optional<Pip> providePip(
            Context context,
            ShellInit shellInit,
            ShellController shellController,
            TvPipBoundsState tvPipBoundsState,
            // ... many dependencies
    ) { /* ... */ }
}

60.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:

  1. Focus search algorithm: View.focusSearch() uses FocusFinder to determine which view should receive focus when the user presses Up/Down/Left/Right.

  2. Touch mode: TV devices are always in "non-touch" mode. Views must handle focus state drawing (focused rings, highlights) explicitly.

  3. BrowseFragment / Leanback library: The androidx.leanback library provides pre-built UI components optimized for D-pad navigation: BrowseFragment, DetailsFragment, SearchFragment, PlaybackFragment.

  4. 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

60.2.10 TvSettings

Android TV uses a specialized settings application rather than the standard phone Settings app. The TV Settings app (packages/apps/TvSettings/ -- typically vendor-specific) 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

60.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.

60.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

60.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.

60.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.

60.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:

  1. Reduced refresh rate: The display drops to 1 Hz or lower update rate.
  2. Limited color palette: The screen switches to a grayscale or limited-color mode to reduce OLED pixel power consumption.

  3. 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

60.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:

  1. 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.

  2. Outline-only rendering: Watch faces in ambient mode use outlined digits rather than filled shapes, reducing the number of lit pixels.

  3. 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

60.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

60.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)

60.3.6 Reduced Windowing

Wear OS significantly simplifies the windowing system compared to phone:

  1. Single-task model: Only one activity is visible at a time. There is no split-screen, freeform, or PIP support.

  2. No navigation bar: The system back gesture is handled by the physical button or a swipe gesture, not an on-screen button.

  3. Simplified recent apps: The recent apps list is either absent or a simple vertical scroll, not the full phone-style overview.

  4. 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

60.3.7 Battery Optimization for Wearables

Wear OS employs aggressive battery optimization beyond standard Android:

  1. 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.

  2. Network efficiency: Wearable devices preferentially route network requests through a connected phone (Bluetooth proxy) rather than using their own Wi-Fi or cellular radio, saving significant power.

  3. Sensor batching: Sensors batch readings and deliver them in bursts rather than continuously, allowing the processor to sleep between batches.

  4. Reduced background activity: JobScheduler constraints are tighter on Wear. Fewer concurrent background services are allowed.

  5. 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

60.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.

60.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

60.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.

60.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
(vendor-specific)/TvSystemUI/            -- TV SystemUI (vendor-provided)

The phone SystemUI is the default and most feature-rich. Car SystemUI replaces it entirely with automotive-specific UI components. TV SystemUI is typically 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

60.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

60.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
  DriveModeSportRRO/          -- Sport mode visuals
  DriveModeEcoRRO/            -- Eco mode visuals
  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:isStatic="true"
             android:priority="10" />
</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.

60.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_PROPERTY_OVERRIDES += \
    ro.hardware.type=tv

PRODUCT_PACKAGES += \
    TvSettings \
    TvSystemUI \
    TvProvider \
    TvLauncher

Wear product configuration typically includes:

# (vendor-specific)
PRODUCT_PROPERTY_OVERRIDES += \
    config.override_forced_orient=true \
    config.override_forced_orient_value=0

# Watch-specific features
PRODUCT_PACKAGES += \
    WearSettings \
    ClockworkHome \
    WearSystemUI

60.4.5 Feature Flags and Configuration

Beyond properties and overlays, form-factor behavior is controlled through:

  1. 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" />
  1. Config resources: frameworks/base/core/res/res/values/config.xml contains hundreds of configurable values. Form-factor overlays change these:
<!-- Example: config_supportsPictureInPicture -->
<!-- Phone: true, Watch: false -->
<!-- config_hasAutomotiveDock: true for automotive -->
  1. SELinux policies: Each form factor has specific SELinux policies:
packages/services/Car/car_product/sepolicy/  -- Automotive SEPolicy

60.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 IVehicle HAL 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 TvInputService implementations 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

60.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.

60.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]
        TVWS[TvWatchdogService]
    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 android.software.leanback feature present
if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
    mSystemServiceManager.startService(TvInputManagerService.class);
}

// HDMI-CEC: started on TV devices
if (pm.hasSystemFeature(PackageManager.FEATURE_HDMI_CEC)) {
    // HdmiControlService starts
}

60.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:

// Referenced in ICarImpl field declarations:
private final CarInputService mCarInputService;

60.5 Try It

Exercise 60.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 --occ-zone

Examine the CarService initialization timing:

# View initialization trace:
adb shell dumpsys car_service --print-timing

# Or look at the system property:
adb shell getprop boot.car_service_created

Exercise 60.2: Inspect Vehicle HAL Properties

Query vehicle properties using the car service shell:

# Get vehicle speed:
adb shell cmd car_service get-property PERF_VEHICLE_SPEED

# Get gear selection:
adb shell cmd car_service get-property GEAR_SELECTION

# List all available properties:
adb shell cmd car_service list-properties

# Set HVAC temperature (on emulator):
adb shell cmd car_service set-property HVAC_TEMPERATURE_SET \
    --area 49 --type 1 -- 22.5

Exercise 60.3: Trace a VHAL Property Event

Use atrace to follow a property change through the stack:

# Start tracing with car_service tag:
adb shell atrace --async_start -c -b 8192 car_service

# 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 60.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 60.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 60.6: Check Device Form Factor

Determine the form factor programmatically:

# Check hardware type:
adb shell getprop ro.hardware.type
# Returns: "automotive", "tv", "watch", or empty (phone)

# 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 60.7: Inspect Automotive Power States

On an automotive emulator:

# Check current power state:
adb shell dumpsys car_service --power

# 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 60.8: Explore Car SystemUI Components

# List Car SystemUI services:
adb shell dumpsys activity services com.android.systemui | grep car

# Check system bar state:
adb shell dumpsys car_service --act

# Inspect HVAC properties used by SystemUI:
adb shell cmd car_service get-property HVAC_TEMPERATURE_SET
adb shell cmd car_service get-property HVAC_FAN_SPEED

Exercise 60.9: Examine Occupant Zones

# Dump occupant zone configuration:
adb shell dumpsys car_service --occ-zone

# Output shows:
# - Zone definitions (driver, passenger, rear)
# - Display assignments per zone
# - User assignments per zone
# - Audio zone mappings
# - Input type support per display

Exercise 60.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 60.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 60.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 60.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 60.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 60.15: Source Code Exploration Tasks

Study the following files to understand form-factor abstractions:

  1. CarService bootstrap chain:
  2. packages/services/Car/service/src/com/android/car/CarServiceImpl.java
  3. packages/services/Car/service/src/com/android/car/ICarImpl.java
  4. packages/services/Car/service/src/com/android/car/VehicleStub.java

  5. Vehicle HAL interface:

  6. hardware/interfaces/automotive/vehicle/aidl/android/hardware/automotive/vehicle/IVehicle.aidl

  7. TV Input Framework:

  8. frameworks/base/media/java/android/media/tv/TvInputService.java
  9. frameworks/base/services/core/java/com/android/server/tv/TvInputManagerService.java
  10. frameworks/base/services/core/java/com/android/server/tv/TvInputHardwareManager.java

  11. HDMI-CEC:

  12. hardware/interfaces/tv/cec/1.0/IHdmiCec.hal
  13. frameworks/base/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java

  14. WMShell TV variant:

  15. frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
  16. frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
  17. frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java

  18. Car SystemUI:

  19. packages/apps/Car/SystemUI/src/com/android/systemui/car/CarServiceProvider.java
  20. packages/apps/Car/SystemUI/src/com/android/systemui/car/systembar/CarSystemBar.java

  21. Occupant Zones:

  22. packages/services/Car/service/src/com/android/car/CarOccupantZoneService.java

  23. Fixed Activity:

  24. packages/services/Car/service/src/com/android/car/am/FixedActivityService.java

  25. Automotive Power:

  26. packages/services/Car/service/src/com/android/car/power/CarPowerManagementService.java
  27. packages/services/Car/service/src/com/android/car/garagemode/GarageModeService.java

  28. Wearable Sensing:

    • frameworks/base/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
    • frameworks/base/core/java/android/app/wearable/WearableSensingManager.java

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
hardware.type (none) automotive tv watch

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:

  1. HAL abstraction separates hardware-specific details (Vehicle HAL, TV tuner HAL, CEC HAL) from framework logic.

  2. System services extend the base framework with form-factor-specific functionality (CarService with ~40 subsystems, TvInputManagerService, TunerResourceManagerService).

  3. Dagger module substitution (WMShellModule vs. TvWMShellModule) swaps entire subsystem implementations at build time without forking the base code.

  4. SystemUI replacement (Car SystemUI, TV SystemUI) provides completely different user shells while sharing the underlying window management infrastructure.

  5. Runtime Resource Overlays enable visual and behavioral customization without source changes, organized in product-specific overlay trees.

  6. 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.