A premium cockpit is five screens: centre stack, cluster, head-up display, passenger, and one or two rear. They may run different apps, for different people, under different rules, at the same time.
Configuring displays#
Displays are enumerated by the kernel and mapped to occupant zones in product config.
<string-array name="config_occupant_zones">
<item>occupantZoneId=0,occupantType=DRIVER,seatRow=1,seatSide=driver</item>
<item>occupantZoneId=1,occupantType=FRONT_PASSENGER,seatRow=1,seatSide=oppositeDriver</item>
<item>occupantZoneId=2,occupantType=REAR_PASSENGER,seatRow=2,seatSide=left</item>
</string-array>
<string-array name="config_display_uniqueId_to_occupantZone">
<item>displayPort:0,occupantZoneId=0</item> <!-- centre stack -->
<item>displayPort:1,occupantZoneId=0</item> <!-- cluster, same occupant -->
<item>displayPort:2,occupantZoneId=1</item> <!-- passenger -->
<item>displayPort:3,occupantZoneId=2</item> <!-- rear left -->
</string-array>adb shell dumpsys display | grep -E 'mDisplayId|uniqueId|mBaseDisplayInfo'
adb shell dumpsys car_service --services CarOccupantZoneServiceWhen a new board shows a black secondary screen, this mapping is nearly always the cause — the port number in config does not match what the kernel reported.
Launching onto a display#
val options = ActivityOptions.makeBasic().apply {
launchDisplayId = passengerDisplayId
}
context.startActivity(intent, options.toBundle())Requirements that catch people:
- The activity must be resizeable or explicitly support multi-display.
- Launching onto a display owned by another user needs
INTERACT_ACROSS_USERS. - The app must be installed for that user — a passenger display running user 11 cannot launch an app installed only for user 10.
# Simulated secondary display
adb shell settings put global overlay_display_devices "1920x720/213"
adb shell am start --display 2 -n com.example/.PassengerActivity
adb shell dumpsys activity activities | grep -i -A5 displayIdPer-display everything#
The mental shift is that display is now a dimension of almost every question.
UX restrictions are per display. A passenger screen may permit video while the driver's does not.
val restrictions = uxManager.getCurrentCarUxRestrictions(myDisplayId)
if (restrictions.isRequiresDistractionOptimization) degrade()Configuration is per display. Size, density and orientation differ wildly. A cluster might be 1920×720; a rear screen 1280×800.
val display = context.display
val metrics = context.resources.displayMetrics // for THIS context's displayInput is per display. A touch on the rear screen is not a touch on the driver's. Do not route input globally.
Audio is per zone, not per display — but zones and displays are related through occupant zones, which is why the two subsystems are wired together.
"The" display and "the" user are both wrong now
WindowManager.getDefaultDisplay() and ActivityManager.getCurrentUser() both
return the driver's. On a passenger app, both are the wrong answer. Get the
display from your Context and the user from your own process.
Moving content between screens#
"Send this to the rear screen" is a common OEM feature. It is an activity launch on another display plus a state handoff — and the state handoff is the hard part, because the target may be a different user with different accounts.
val handoff = Intent(context, PlayerActivity::class.java).apply {
putExtra(EXTRA_MEDIA_ID, currentMediaId)
putExtra(EXTRA_POSITION_MS, player.currentPosition)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivityAsUser(
handoff,
ActivityOptions.makeBasic().apply { launchDisplayId = rearDisplayId }.toBundle(),
UserHandle.of(rearUserId),
)If the content is licensed to an account, the target user may not have it. Design the failure: "sign in on the rear screen to continue" is a legitimate outcome and much better than a blank player.
Performance#
Every additional display is another surface to compose, at its own refresh rate, from the same GPU and memory bandwidth.
adb shell dumpsys SurfaceFlinger --timestats
adb shell dumpsys SurfaceFlinger | grep -A10 'Display '
adb shell perfetto -o /data/misc/perfetto-traces/t -t 15s gfx view schedPractical rules: do not animate on a display nobody is looking at, drop the frame rate on secondary displays where you can, and never run a video decode per screen if one decode can be shared.
Next#
The head-up display — the smallest screen with the strictest rules.

