Every OEM wants a head unit that looks like theirs and nobody else's. Every OEM also wants to take Android platform upgrades for the next fifteen years. Those two goals fight, and the Car UI Library plus RROs is how AOSP resolves it.
The problem being solved#
The naive approach is to fork: copy the system apps, restyle them, ship. It demos beautifully and then costs you every upgrade cycle forever, because each platform release must be merged into a diverged codebase by people who have forgotten why the changes were made.
The alternative: build the UI from components whose appearance is driven entirely by resources, then replace the resources at runtime without touching code.
Car UI Library#
car-ui-lib provides the automotive component vocabulary — lists, toolbars,
preferences, dialogs — already sized for reach and glance, already
distraction-aware.
<com.android.car.ui.recyclerview.CarUiRecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>val toolbar = CarUi.requireToolbar(this)
toolbar.setTitle(R.string.media_browse)
toolbar.setState(ToolbarController.State.SUBPAGE)
toolbar.setMenuItems(listOf(
MenuItem.builder(this)
.setIcon(R.drawable.ic_search)
.setOnClickListener { openSearch() }
.build()
))The point is not that these look better out of the box. It is that an OEM overlay can restyle every one of them at once, because they all resolve the same resource names.
Runtime Resource Overlays#
An RRO is an APK containing only resources, declaring that it overrides another package's.
<manifest package="com.oem.rro.caruilib">
<application android:hasCode="false"/>
<overlay
android:targetPackage="com.android.car.ui.paintbooth"
android:targetName="car-ui-lib"
android:resourcesMap="@xml/overlays"
android:isStatic="true"
android:priority="10"/>
</manifest><resources>
<color name="car_ui_toolbar_background">#0B0F1A</color>
<color name="car_ui_text_color_primary">#E8EFFA</color>
<color name="car_ui_list_item_icon_color">#22D3EE</color>
</resources><resources>
<dimen name="car_ui_toolbar_first_row_height">96dp</dimen>
<dimen name="car_ui_list_item_height">116dp</dimen>
<dimen name="car_ui_touch_target_size">76dp</dimen>
</resources>Overlays can replace colours, dimensions, drawables, strings and even layouts. The target application's code is untouched and unaware.
Static vs dynamic#
- Static (
isStatic="true") — applied at boot, cannot be turned off. Used for the OEM's own brand identity. Must be in the system image. - Dynamic — enabled and disabled at runtime with
cmd overlay. Used for themes a user or the vehicle can switch: day/night, sport mode, trim packages.
adb shell cmd overlay list
adb shell cmd overlay enable com.oem.rro.night
adb shell cmd overlay disable com.oem.rro.night
adb shell cmd overlay dump com.oem.rro.nightDynamic overlays are the fastest theming loop
Push the RRO, cmd overlay enable, restart the target app, look. No platform
rebuild, no reflash. Teams that discover this iterate on styling ten times faster
than teams that keep rebuilding the image.
Making a component themable#
If you write an OEM component, its appearance must come from resources or it cannot be overlaid. This is a code-review rule, not a preference.
<TextView
android:textColor="#FFFFFF"
android:textSize="18sp"
android:padding="12dp"/><TextView
android:textColor="@color/oem_text_primary"
android:textSize="@dimen/oem_text_size_body"
android:padding="@dimen/oem_spacing_medium"/>The second version can be re-skinned for a new trim by shipping one small APK. The first requires a code change, a rebuild and a re-release.
Day/night is not optional#
Vehicles drive at night, and a bright UI at night is a genuine safety problem. Android's resource qualifiers handle it:
res/values/colors.xml ← day
res/values-night/colors.xml ← nightMode is driven by the vehicle, typically from a VHAL property such as
NIGHT_MODE, which the platform maps onto UiModeManager. Test both. A contrast
choice that reads well at noon can be unusable at 2am.
Multiple screen geometries#
Head units are not one shape. Portrait 9:16 tablets, wide 21:9 letterboxes, curved displays, plus a cluster and a passenger screen on the same vehicle.
res/layout/ ← default
res/layout-w1280dp/ ← wide landscape
res/layout-h1600dp/ ← tall portrait
res/values-w1280dp/dimens.xml ← spacing at that widthDesign in constraints and let the layout adapt. Fixed pixel positions tuned to one head unit will need redoing for the next programme, and there is always a next programme.
The discipline that keeps this working#
- No hard-coded visual values. Colours, dimensions and drawables are named resources, always.
- Overlay before forking. A fork must be justified in writing and re-merged every release.
- Keep OEM styling in the RRO, not scattered through app code.
- Test every theme in both day and night, on every screen geometry the programme ships.
- Version the RRO with the platform. Resource names are an interface; when the target renames one, your overlay silently stops applying that value.
That last one catches people. An overlay referencing a resource name that no longer exists does not error — it just does nothing, and the component quietly reverts to the AOSP default in the middle of your brand experience.
Next#
Audio is the other domain where automotive diverges sharply from phone Android.

