Instrument Cluster API

Use the Instrument Cluster API (an Android API) to display navigation apps, including Google Maps, on a secondary display in a car, such as behind the steering wheel on the instrument panel. This page describes how to create a service to control that secondary display and to integrate the service with CarService so that navigation apps can display a user interface.

Terminology

The following terms are used on this page.

CarInstrumentClusterManager
An instance of CarManager that enables external apps to launch an activity on the Instrument Cluster and receive callbacks when the Instrument Cluster is ready to display activities.
CarManager
Base class of all managers used by external apps to interact with car-specific services implemented by CarService.
CarService
Android Platform service that provides communication between external apps (including Google Maps) and car-specific features, such as Instrument Cluster access.
Destination
The final destination to which the vehicle will navigate.
Estimated time of arrival (ETA)
The estimated time of arrival at a destination.
Head unit (HU)
Primary computational unit embedded in a car. The HU runs all Android code and is connected to the central display in the car.
Instrument Cluster
Secondary display located behind the steering wheel and between the car instruments. This can be an independent computational unit connected to the HU through the car's internal network (CAN bus) or a secondary display attached to the HU.
InstrumentClusterRenderingService
Base class for the service used to interface with the Instrument Cluster display. OEMs must provide an extension of this class that interacts with the OEM-specific hardware.
KitchenSink app
Test app included with Android Automotive.
Route
A specific path along which a vehicle navigates to arrive at a destination.
Singleton service
An Android service with the android:singleUser attribute. At any given time, at most one instance of the service runs on the Android system.

Prerequisites

Before you continue, be sure to have these elements:

  • Android development environment. To set up the Android development environment, see Build requirements.
  • Download the Android source code. Get the latest version of the Android source code from the pi-car-release branch (or later) at https://android.googlesource.com.
  • Head unit (HU). An Android device capable of running Android 9 (or later). This device must have its own display and be capable of flashing the display with new builds of Android.
  • Instrument Cluster is one of the following:
    • Physical secondary display attached to the HU. If the device hardware and kernel support the management of multiple displays.
    • Independent unit. Any computational unit connected to the HU via a network connection, capable of receiving and displaying a video stream on its own display.
    • Emulated display. During development, you can use one of these emulated environments:
      • Simulated secondary displays. To enable a simulated secondary display on any AOSP Android distribution, go to the Developer Options settings in the Settings system app and then select Simulate secondary displays This configuration is equivalent to attaching a physical secondary display, with the limitation that this display is superimposed over the primary display.
      • Emulated instrument cluster. The Android emulator included with AAOS provides an option to display an instrument cluster with ClusterRenderingService.

Integration architecture

Integration components

Any integration of the Instrument Cluster API consists of these three components:

  • CarService
  • Navigation apps
  • OEM Instrument Cluster Service

Integration components

CarService

CarService mediates between navigation apps and the car, ensuring that only one navigation app is active at any given time and only apps with the android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL permission can send data to the car.

CarService bootstraps all car-specific services and provides access to these services through a series of managers. To interact with the services, apps running in the car can access these managers.

For instrument cluster implementation, automotive OEMs must create a custom implementation of InstrumentClusterRendererService and update the ClusterRenderingService.

When rendering an Instrument Cluster, during the boot process the CarService reads the InstrumentClusterRendererService key of the ClusterRenderingService to locate an implementation of InstrumentClusterService. In AOSP, this entry points to the Navigation State API sample cluster implementation render service:

<string name="instrumentClusterRendererService">
android.car.cluster/.ClusterRenderingService
</string>

The service referred to in this entry is initialized and bound to CarService. When navigation apps, like Google Maps, request a CarInstrumentClusterManager, CarService provides a manager that updates the Instrument Cluster state from the bound InstrumentClusterRenderingService. (In this case, bound refers to Android Services.)

Instrument Cluster service

OEMs must create an Android Package (APK) that contains a subclass of ClusterRenderingService.

This class serves two purposes:

  • Provides an interface Android and the Instrument Cluster rendering device (the purpose of this page).
  • Receives and renders navigation state updates, such as turn-by-turn navigation guidance.

For the first purpose, OEM implementations of InstrumentClusterRendererService must initialize the secondary display used to render information on screens in the car cabin and communicate this information to CarService by calling to the InstrumentClusterRendererService.setClusterActivityOptions() and InstrumentClusterRendererService.setClusterActivityState() methods.

For the second function, the Instrument Cluster service must provide an implementation of the ClusterRenderingService interface that receives navigation status update events, which are encoded as an eventType and event data encoded in a bundle.

Integration sequence

The following diagram illustrates the implementation of a navigation state that renders updates:

Integration sequence

In this illustration, colors denote the following:

  • Yellow. CarService and CarNavigationStatusManager provided by the Android platform. To learn more, see Car and CAR_NAVIGATION_SERVICE.
  • Cyan. InstrumentClusterRendererService implemented by the OEM.
  • Purple. The Navigation app implemented by Google and third-party developers.
  • Green. CarAppFocusManager. To learn more, see Using the CarAppFocusManager API below and CarAppFocusManager.

The Navigation State information flow follows this sequence:

  1. CarService initializes the InstrumentClusterRenderingService.
  2. During initialization, the InstrumentClusterRenderingService updates CarService with:
    1. Instrument Cluster display properties, such as unobscure boundaries (see more details about unobscure boundaries later).
    2. Activity options needed to launch activities inside the Instrument Cluster display. To learn more, see ActivityOptions.
  3. A navigation app (such as Google Maps for Android Automotive or any maps app with the required permissions):
    1. Obtains a CarAppFocusManager using the Car class from car-lib.
    2. Before turn-by-turn directions start, calls to CarAppFocusManager.requestFocus() to pass CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION as the appType parameter.
  4. CarAppFocusManager communicates this request to CarService. If granted, CarService inspects the navigation app package and locates an activity marked with category android.car.cluster.NAVIGATION.
  5. If found, the navigation app uses the ActivityOptions reported by the InstrumentClusterRenderingService to launch the activity and includes the Instrument Cluster display properties as extras in the intent.

Integrate the API

The InstrumentClusterRenderingService implementation must:

  • Be designated as a singleton service by adding the following value to the AndroidManifest.xml. This is necessary to ensure that a single copy of the Instrument Cluster service runs, even during initialization and user switching:
    android:singleUser="true"
  • Hold the BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE system permission. This guarantees that only the Instrument Cluster rendering service included as part of the Android system image is ever bound by the CarService:
    <uses-permission android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"/>
    

Implement InstrumentClusterRenderingService

To build the service:

  1. Write a class that extends from ClusterRenderingService and then add a corresponding entry to your AndroidManifest.xml file. This class controls the Instrument Cluster display and can (optionally) render Navigation State API data.
  2. During onCreate(), use this service to initialize the communication with the rendering hardware. Options include:
    • Determine the secondary display to be used for the Instrument Cluster.
    • Create a virtual display so that the Instrument Cluster app renders and transmits the rendered image to an external unit (using a video streaming format, such as H.264).
  3. When the display indicated above is ready, this service must call InstrumentClusterRenderingService#setClusterActivityLaunchOptions() to define the exact ActivityOptions that must be used to display an Activity on the Instrument Cluster. Use these parameters:
    • category. ClusterRenderingService.
    • ActivityOptions. An ActivityOptions instance that can be used to launch an Activity in the Instrument Cluster. For example, from the sample Instrument Cluster implementation on AOSP:
      getService().setClusterActivityLaunchOptions(
        CATEGORY_NAVIGATION,
        ActivityOptions.makeBasic()
            .setLaunchDisplayId(displayId));
  4. When the Instrument Cluster is ready to display activities, this service must invoke InstrumentClusterRenderingService#setClusterActivityState(). Use these parameters:
    • category ClusterRenderingService.
    • state Bundle generated with ClusterRenderingService. Be sure to provide this data:
      • visible Specifies the Instrument Cluster as visible and ready to display content.
      • unobscuredBounds A rectangle that defines the area within the Instrument Cluster display in which it's safe to display content. For example, areas covered by dials and gauges.
  5. Override the Service#dump() method and report status information useful for debugging (see dumpsys for more information).

Sample InstrumentClusterRenderingService implementation

The following example outlines an InstrumentClusterRenderingService implementation, which creates a VirtualDisplay to present the Instrument Cluster content on a remote physical display.

Alternatively, this code could pass the displayId of a physical secondary display connected to the HU, if one is known to be available.

/**
* Sample {@link InstrumentClusterRenderingService} implementation
*/
public class SampleClusterServiceImpl extends InstrumentClusterRenderingService {
   // Used to retrieve or create displays
   private final DisplayManager mDisplayManager;
   // Unique identifier for the display to be used for instrument
   // cluster
   private final String mUniqueId = UUID.randomUUID().toString();
   // Format of the instrument cluster display
   private static final int DISPLAY_WIDTH = 1280;
   private static final int DISPLAY_HEIGHT = 720;
   private static final int DISPLAY_DPI = 320;
   // Area not covered by instruments
   private static final int DISPLAY_UNOBSCURED_LEFT = 40;
   private static final int DISPLAY_UNOBSCURED_TOP = 0;
   private static final int DISPLAY_UNOBSCURED_RIGHT = 1200;
   private static final int DISPLAY_UNOBSCURED_BOTTOM = 680;
   @Override
   public void onCreate() {
      super.onCreate();
      // Create a virtual display to render instrument cluster activities on
      mDisplayManager = getSystemService(DisplayManager.class);
      VirtualDisplay display = mDisplayManager.createVirtualDisplay(
          mUniqueId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_DPI, null,
          0 /* flags */, null, null);
      // Do any additional initialization (e.g.: start a video stream
      // based on this virtual display to present activities on a remote
      // display).
      onDisplayReady(display.getDisplay());
}
private void onDisplayReady(Display display) {
    // Report activity options that should be used to launch activities on
    // the instrument cluster.
    String category = CarInstrumentClusterManager.CATEGORY_NAVIGATION;
    ActionOptions options = ActivityOptions.makeBasic()
        .setLaunchDisplayId(display.getDisplayId());
    setClusterActivityOptions(category, options);
    // Report instrument cluster state.
    Rect unobscuredBounds = new Rect(DISPLAY_UNOBSCURED_LEFT,
        DISPLAY_UNOBSCURED_TOP, DISPLAY_UNOBSCURED_RIGHT,
        DISPLAY_UNOBSCURED_BOTTOM);
    boolean visible = true;
    ClusterActivityState state = ClusterActivityState.create(visible,
       unobscuredBounds);
    setClusterActivityState(category, options);
  }
}

Use the CarAppFocusManager API

The CarAppFocusManager API provides a method named getAppTypeOwner(), which allows the cluster service written by OEMs to know which navigation app has navigation focus at any given time. OEMs can use the existing CarAppFocusManager#addFocusListener() method, and then use getAppTypeOwner() to learn which app has focus. With this information, OEMs can:

  • Switch the activity shown in the cluster to the cluster activity provided by the navigation app holding focus.
  • Can detect if the focused navigation app has a cluster activity or not. If the focused navigation app doesn't have a cluster activity (or if such activity is disabled), OEMs can send this signal to the car DIM so the navigation facet of the cluster is skipped altogether.

Use CarAppFocusManager to set and listen for the current app focus, such as active navigation or a voice command. Usually only one instance of such an app is actively running (or focused) in the system.

Use the CarAppFocusManager#addFocusListener(..) method to listen for app focus changes:

import android.car.CarAppFocusManager;

...

Car car = Car.createCar(this);
mAppFocusManager = (CarAppFocusManager)car.getCarManager(Car.APP_FOCUS_SERVICE);
mAppFocusManager.addFocusListener(this, CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);

...

public void onAppFocusChanged(int appType, boolean active) {
    // Use the CarAppFocusManager#getAppTypeOwner(appType) method call
    // to retrieve a list of active package names
}

Use the CarAppFocusManager#getAppTypeOwner(..) method to retrieve the package names of the current owner of a given app type that is in focus. This method may return more than one package name if the current owner uses the android:sharedUserId feature.

import android.car.CarAppFocusManager;

...

Car car = Car.createCar(this);
mAppFocusManager = (CarAppFocusManager)car.getCarManager(Car.APP_FOCUS_SERVICE);
List<String> focusOwnerPackageNames = mAppFocusManager.getAppTypeOwner(
              CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION);

if (focusOwnerPackageNames == null || focusOwnerPackageNames.isEmpty()) {
        // No Navigation app has focus
        // OEM may choose to show their default cluster view
} else {
       // focusOwnerPackageNames
       // Use the PackageManager to retrieve the cluster activity for the package(s)
       // returned in focusOwnerPackageNames
}

...

Appendix: Use the sample app

AOSP provides a sample app that implements the Navigation State API.

To run this sample app:

  1. Build and flash Android Auto on a supported HU. Use the Android building and flashing instructions specific to your device. For instructions, see Using Reference Boards.
  2. Connect a physical secondary display to the HU (if supported) or turn on the virtual secondary HU:
    1. Select Developer Mode in the Settings app.
    2. Go to Settings > System > Advanced > Developer options > Simulate secondary displays.
  3. Reboot the HU
  4. To launch the KitchenSink app:
    1. Open the drawer.
    2. Go to Inst. Cluster.
    3. Click START METADATA.

KitchenSink requests NAVIGATION focus, which instructs the DirectRenderingCluster service to display a mocked-up user interface on the Instrument Cluster.