On a phone, SystemUI draws a status bar and a nav bar and mostly stays out of the way. On a head unit it owns the permanent furniture of the vehicle: climate controls, the user switcher, media transport, and the surfaces that must be reachable in one touch while driving.
What it is#
CarSystemUI lives at packages/apps/Car/SystemUI
and extends the phone SystemUI rather than replacing it. It inherits notification
plumbing and window management, then substitutes automotive behaviour for the
parts that differ.
The pieces you will actually work on:
| Component | Responsibility |
|---|---|
CarSystemBar | Top and bottom bars, their buttons and state |
CarNotificationView | The notification surface, distraction-aware |
| HVAC panel | Climate controls, reachable from any screen |
| User switcher | Profile selection, tied to CarUserService |
| Keyguard | Lock behaviour, which differs a lot from phones |
The bars are not decoration#
A driver must reach climate, home and media in one touch, without hunting. That is why the bars are permanent and why the platform gives OEMs so much control over them — the physical safety argument is doing the work.
System bar contents are configured, not hard-coded:
<com.android.systemui.car.systembar.CarSystemBarButton
android:id="@+id/home"
style="@style/SystemBarButton"
systemui:categories="android.intent.category.HOME"
systemui:highlightWhenSelected="true"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;
category=android.intent.category.HOME;launchFlags=0x14000000;end"/>Which bars exist at all is a config flag:
<bool name="config_enableTopSystemBar">true</bool>
<bool name="config_enableBottomSystemBar">true</bool>
<bool name="config_enableLeftSystemBar">false</bool>
<bool name="config_enableRightSystemBar">false</bool>Left and right bars exist because some head units are portrait, and a bottom bar on a tall screen wastes the wrong dimension.
Themable, not forkable#
Every OEM wants their own look. The platform's answer is Runtime Resource Overlays — a separate APK that replaces resources at runtime without touching CarSystemUI's code.
<manifest package="com.oem.systemui.rro">
<overlay
android:targetPackage="com.android.systemui"
android:targetName="CarSystemUI"
android:isStatic="true"
android:priority="1"/>
</manifest><resources>
<color name="system_bar_background_color">#0B0F1A</color>
<color name="car_nav_icon_fill_color">#22D3EE</color>
</resources>Forking CarSystemUI is a decade-long tax
It is always tempting, and it always costs more than expected. A fork must be re-merged at every platform upgrade, for the life of the vehicle — ten to fifteen years. Overlay first. Fork only when the platform genuinely cannot express what you need, and document why.
Notifications are different here#
Automotive notifications are ranked, filtered and heavily restricted while driving. Heads-up notifications are limited by category, and long text is truncated by UX restrictions rather than by your layout.
val notification = Notification.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_message)
.setContentTitle(sender)
.setContentText(preview)
// CATEGORY_MESSAGE gets automotive-specific handling: read-aloud,
// reply actions, and correct behaviour under distraction rules.
.setCategory(Notification.CATEGORY_MESSAGE)
.addAction(replyAction)
.setStyle(Notification.MessagingStyle(person).addMessage(message))
.build()Set the category honestly. Categories drive whether the platform will read a
notification aloud, whether it can interrupt, and whether it appears at all above
a speed threshold. Mislabelling a marketing message as CATEGORY_CALL to get
visibility is the kind of thing that fails OEM review.
Where your app begins#
Between the bars. That is it.
Consequences worth planning for:
- Your usable height is smaller than the display. Two bars on a 1280×720 head unit can take 150 px or more.
- The bars are always on top. You cannot draw over them, and full-screen immersive mode is generally disallowed for safety.
- Insets still apply. Use
WindowInsets; do not hard-code bar heights, which differ per product and per OEM overlay.
ViewCompat.setOnApplyWindowInsetsListener(root) { view, insets ->
val bars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.updatePadding(top = bars.top, bottom = bars.bottom)
insets
}Debugging it#
# Restart CarSystemUI after an overlay change, without a reboot
adb shell pkill -f com.android.systemui
# Which overlays are applied to SystemUI?
adb shell cmd overlay list | grep -i systemui
# Enable one at runtime while iterating
adb shell cmd overlay enable com.oem.systemui.rro
adb logcat -b all | grep -i carsystembarcmd overlay enable/disable is the fastest theming loop there is — no rebuild, no
reflash, just restart SystemUI and look.
Next#
The rules that decide what your UI is allowed to do while the vehicle moves.

