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 article describes how to create a service to control that secondary display and to then integrate the service with CarService so that navigation apps can display a user interface.
Terminology
The following terms are used throughout this article:
Term | Description |
---|---|
CarInstrumentClusterManager | A 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. |
ETA | Estimated time of arrival at the 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 application 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
To develop the integration, 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 application 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 Android Automotive provides an option to display an instrument cluster with Android emulator _qemu-pipes. Use the DirectRenderingCluster reference instrument cluster implementation to connect to this emulated external display.
Integration Architecture
Integration Components
An integration of the Instrument Cluster API consists of these three components:
- CarService
- Navigation apps.
- OEM Instrument Cluster Service
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, applications running in the car can access these managers.
For instrument cluster implementation, automotive OEMs must create a custom implementation of InstrumentClusterRendererService and update the config.xml file to point to that customized implementation.
When rendering an Instrument Cluster, during the boot process the CarService reads the InstrumentClusterRendererService key of the config.xml 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 InstrumentClusterRendererService. See ClusterRenderingService for a sample.
This class serves two purposes:
- Provides an interface between Android and the Instrument Cluster rendering device (the purpose of this document).
- 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 NavigationRenderer 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:
Legend:
- Yellow. CarService and CarNavigationStatusManager provided by the Android platform.
- Cyan. InstrumentClusterRendererService implemented by the OEM.
- Purple. The Navigation app implemented by Google and third-party developers.
- Green. CarAppFocusManager.
The Navigation State information flow follows this sequence:
- CarService initializes the InstrumentClusterRenderingService.
- During initialization, the InstrumentClusterRenderingService updates
CarService with:
- Instrument Cluster display properties, such as unobscure boundaries (see more details about unobscure boundaries later).
- Activity Options needed to launch activities inside the Instrument Cluster display (see more details at ActivityOptions.
- A navigation app (such as Google Maps for Android Automotive or any maps app
with the required permissions):
- Obtains a CarAppFocusManager using the Car class from car-lib.
- Before turn-by-turn directions start, calls to
CarAppFocusManager.requestFocus()
to passCarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION
as theappType
parameter.
- 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
. - 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.
Integrating 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 will run, 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"/>
Implementing InstrumentClusterRenderingService
To build the service:
- Write a class that extends from InstrumentClusterRenderingService 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.
- 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).
- 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. CarInstrumentClusterManager#CATEGORY_NAVIGATION
- 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));
- When the Instrument Cluster is ready to display activities, this service must invoke:
InstrumentClusterRenderingService#setClusterActivityState()
.
Use these parameters:- category. CarInstrumentClusterManager#CATEGORY_NAVIGATION
- state. Bundle generated with ClusterActivityState.
You must provide the following 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.
- 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, as mentioned above, 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 that will 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); } }
Appendix: Using the Sample Application
AOSP provides a sample application that implements the Navigation State API.
To run this sample application:
- 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.
- Connect a physical secondary display to the HU (if supported) or turn on the
virtual secondary HU:
- Select Developer Mode in the Settings app.
- Go to Settings > System > Advanced > Developer options > Simulate secondary displays.
- Reboot the HU. The ClusterRenderingService service is connected to the secondary display.
- To launch the KitchenSink app:
- Open the drawer.
- Go to Inst. Cluster.
- Click START METADATA.
KitchenSink requests NAVIGATION focus, which instructs the DirectRenderingCluster service to display a mocked-up user interface on the Instrument Cluster.