Technical details

This section provides technical details specific to the Control Center reference app.

Control Center is an unbundled, privileged, system signed app that requires a minimum SDK version of 35 (Android V (API level 35)). The app is installed in system/priv-app to use System APIs. To read media information, the app must be platform-signed. You can update the app Over-the-Air (OTA).

Background service

The Control Center app relies on a background service for its functionality. Control Center Service is started on the user-post-unlocked state in the user lifecycle by Vendor ServiceController. Control Center must always be active and communicating in the background—the app can't rely on the app being opened by the user.

Control Center Service connects to and communicates with other instances of itself in other occupant zones by using the Communication API. Reading the integration guide is essential to understanding how the Control Center instances on each user establish connections and send and receive data.

Diagram illustrating the Control Center Service being initiated by the Vendor ServiceController.
Figure 5. Control Center Service started by Vendor ServiceController.

Communication

Once connected, the Control Center Service communicates with protobuf objects that convey information. To pass the protobuf to another occupant zone with the Communication APIs, the protobuf is converted to a byte array, a payload object is created, and the Payload is sent through CarOccupantConnectionManager#sendPayload.

message ControlCenterPayload {
    required Type messageType = 1;
    // ...
    enum Type {
       MEDIA_STATUS = 0;
       MEDIA_COMMAND = 1;
       INPUT_LOCK_COMMAND = 2;
       INPUT_LOCK_SUCCESSFUL = 3;
       CANCEL_AUDIO_REQUEST = 4;
       MIRRORING_REQUEST_RECEIVER_DECISION = 5;
       MIRRORING_SESSION_ENDED = 6;
    }
}

private fun parsePayload(
    senderZone: OccupantZoneInfo,
    payload: Payload
) {
     val parsedPayload =
         ControlCenterPayload.parseFrom(payload.bytes)
             when (parsedPayload.messageType) {
                 ControlCenterPayload.Type.MEDIA_STATUS -> {
                     // logic here
                 }
             }
             //…
}

Data

Information about occupant zones is stored in the Control Center in the form of OccupantZoneData objects. Changes to the local OccupantZoneData are sent to other Control Center instances through the Comms API.

When the received Payload is parsed, the parsed data is passed to the local OccupantZoneStateRepository which, in turn, notifies views of the change. Most data is passed between classes with Kotlin flows on Android.

Handle cabin speaker audio requests

So that the driver can always receive requests for passengers to play audio over cabin speakers, when created, the driver's Control Center Service registers the Primary ZoneMedia Audio RequestCallback.

The callback is notified of calls to CarAudioManager#requestMediaAudioOnPrimaryZone. The driver's Control Center Service handles requests by creating a heads-up notification (HUN) that can be accepted or declined through CarAudioManager#allowMediaAudioOnPrimaryZone(boolean).

Co-watch a video with other screens

Co-watching works due to the Task Mirroring APIs in CarActivityManager. The TaskMirroringManager searches first for the playing MediaSession app's package in CarActivityManager#getVisibleTasks and then creates a VirtualDisplay and moves the visible task to this display through CarActivityManager#moveRootTaskToDisplay.

This returns an IBinder token that MirroredSurfaceView can use in a layout to display the task through MirroredSurfaceView#mirrorSurface. The Communication API Payload object passed the token to other occupant zones.

Each Control Center instance in those occupant zones launches a Mirroring activity and uses that token to populate the MirroredSurfaceView.

Flow of a mirroring token to display a task on another screen.
Figure 6. Mirror a token flow.

Task mirroring APIs

Control Center uses these task mirroring APIs:

CarActivityManager#getVisibleTasks(int displayId)
<ActivityManager.RunningTaskInfo> called for the sender display.

CarActivityManager#moveRootTaskToDisplay(int virtualDisplayId)
Moves the selected visible task to a created virtual display.

CarActivityManager#createTaskMirroringToken(int taskId)
Creates a task to mirror the IBinder token and should be called after the task is moved to the virtual display.

MirroredSurfaceView#mirrorSurface(IBinder token)
A custom view object that uses the token to display the contents of the |virtual display.

Limitations of task mirroring in Control Center

The Control Center only supports task mirroring for MediaSession apps. However, the API can mirror any task. The virtual display is made with the dimensions of the sender display. If the receiver's display uses different resolutions and dimensions, the virtual display appears in the center of the screen.

Surface visible tasks

Control Center extends the chassis Theme.CarUi.NoToolbar to be a translucent window. This means that when Control Center is opened over a task, the task is returned in CarActivityManager#getVisibleTasks, which allows the task to be mirrored.

Receive mirroring information

The Control Center notifies other apps of mirroring sessions. To receive updates, apps must bind to the Control Center Service and send a Handler class as a client, that receives and handles Messages from the Control Center Service.

Client apps can receive the package name of the mirrored app and launch an intent URI for the activity in Control Center hosting the mirrored app with these keys:

  • _config_msg_mirroring_pkg_name_key_
  • _config_msg_mirroring_redirect_uri_key_

These configs must exist in the client app resources and in Control Center resources.

Client apps receive mirroring information from the Control Center.
Figure 7. Receive mirroring information from Control Center.

Debug Control Center

The Logger class handles Control Center logs, which can be configured to force logs.

class Logger(cls: Class<*>) {

   companion object {
       private const val FORCE_LOGS = false
   }

   private val tag: String

   init {
       tag = cls.simpleName
   }

   fun v(message: String) {
       if (Log.isLoggable(tag, Log.VERBOSE) || FORCE_LOGS) {
           Log.v(tag, message)
       }
   }
...

System app and updatability

Because Control Center is a system app and platform-signed due to the use of signature-only permissions, Control Center must be preinstalled on the device and can be updated OTA only, similar to the Car Media App.

Build Control Center from source

To get the Control Center source code, see Integrate unbundled apps.

User privacy with multi-displays

Control Center lets all car passengers view media information on all displays. Google recommends you insert a non-blocking privacy notice to notify users. Google recommends you do this at the system level, when you sign in to a display.