Instrument Cluster API(一款 Android API)可在车载辅助显示设备(如位于方向盘后方的仪表盘上的辅助显示设备)上显示导航应用,包括 Google 地图。本页面介绍如何创建服务以控制此类辅助显示设备并将该服务与 CarService
集成,以便导航应用可以显示界面。
术语
本页面中使用了以下术语。
CarManager
的实例,使外部应用能够在仪表板上启动 activity,并在仪表板准备好显示 activity 时接收回调。android:singleUser
属性的 Android 服务。在任何给定时间,相应服务最多只有一个实例在 Android 系统上运行。前提条件
在继续之前,请确保具备以下元素:
- Android 开发环境。如需设置 Android 开发环境,请参阅构建要求。
- 下载 Android 源代码。访问 https://android.googlesource.com,从 pi-car-release 分支(或更高版本)获取最新版 Android 源代码。
- 车机 (HU)。能够搭载 Android 9(或更高版本)的 Android 设备。此设备必须具有自己的显示屏,并且能够使用 Android 的新 build 刷写显示屏。
- 仪表板采用以下类型之一:
- 连接到 HU 的实体辅助显示设备。条件是设备硬件和内核支持对多个显示屏进行管理。
- 独立单元。通过网络连接连接到 HU 的任何计算单元,能够接收并在其显示屏上显示视频串流。
- 模拟显示屏。在开发过程中,您可以使用以下模拟环境之一:
- 模拟辅助显示设备。如需在任何 AOSP Android 发行版上启用模拟辅助显示设备,请前往设置系统应用中的开发者选项设置,然后选择模拟辅助显示设备。此配置相当于连接实体辅助显示设备,只不过此显示设备叠加显示在主显示设备之上。
- 模拟仪表板。AAOS 附带的 Android 模拟器提供了一个使用 ClusterRenderingService 显示仪表板的选项。
集成架构
集成组件
Instrument Cluster API 的任何集成都包括以下三个组件:
CarService
- 导航应用
- OEM 仪表板服务
CarService
CarService
可在导航应用与汽车之间进行协调,确保在任何时候都只有一个导航应用处于活跃状态,并且只有具有 android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL
权限的应用才能向汽车发送数据。
CarService
可以启动所有汽车特有服务,并通过一系列管理器提供对这些服务的访问。为了与服务进行交互,在汽车内运行的应用可以访问这些管理器。
对于仪表板实现,汽车 OEM 必须创建自定义的 InstrumentClusterRendererService 实现,并更新 ClusterRenderingService。
当呈现仪表板时,CarService
会在启动过程中读取 ClusterRenderingService 的 InstrumentClusterRendererService
键,以定位 InstrumentClusterService
实现。在 AOSP 中,此条目指向导航状态 API 集群实现呈现服务示例:
<string name="instrumentClusterRendererService"> android.car.cluster/.ClusterRenderingService </string>
此条目中引用的服务经初始化处理并绑定到 CarService
。当导航应用(如 Google 地图)请求 CarInstrumentClusterManager
时,CarService
会提供一个管理器,用于根据绑定的 InstrumentClusterRenderingService
更新仪表板状态。(在这种情况下,“绑定的服务”指的是 Android 服务。)
仪表板服务
OEM 必须创建包含 ClusterRenderingService 子类的 Android 软件包 (APK)。
此类有以下两个用途:
- 提供 Android 与仪表板呈现设备之间的接口(本文的用途)。
- 接收并呈现导航状态更新,如精细导航指导。
为实现第一个用途,OEM 的 InstrumentClusterRendererService
实现必须初始化用于在车厢内屏幕上呈现信息的辅助显示设备,并通过调用 InstrumentClusterRendererService.setClusterActivityOptions()
和 InstrumentClusterRendererService.setClusterActivityState()
方法将此信息传达给 CarService
。
为实现第二个用途,仪表板服务必须提供 ClusterRenderingService 接口的实现,该接口用于接收导航状态更新事件,这些事件编码为 eventType
和编码在一个软件包中的事件数据。
集成序列
下图展示了呈现更新的导航状态的实现:
在此图中,各种颜色的含义如下:
- 黄色:由 Android 平台提供的
CarService
和CarNavigationStatusManager
。如需了解详情,请参阅 Car 和 CAR_NAVIGATION_SERVICE。 - 青色:由 OEM 实现的
InstrumentClusterRendererService
。 - 紫色。由 Google 和第三方开发者实现的导航应用。
- 绿色:
CarAppFocusManager
。如需了解详情,请参阅下文的使用 CarAppFocusManager API 和 CarAppFocusManager。
导航状态信息流程遵循以下序列:
CarService
初始化InstrumentClusterRenderingService
。- 在初始化期间,
InstrumentClusterRenderingService
使用以下选项更新CarService
:- 仪表板屏幕属性,如无遮挡边界(可稍后了解有关无遮挡边界的更多详情)。
- 在仪表板显示屏内启动 activity 所需要的 activity 选项。如需了解详情,请参阅 ActivityOptions。
- 导航应用(如适用于 Android Automotive 的 Google 地图或任何具有所需权限的地图应用):
- 使用 Car 类从 car-lib 中获取
CarAppFocusManager
。 - 在启动精细导航路线之前,调用
CarAppFocusManager.requestFocus()
以将CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION
作为appType
参数传递。
- 使用 Car 类从 car-lib 中获取
CarAppFocusManager
会将此请求传达给CarService
。在授予条件下,CarService
会检查导航应用软件包,并定位标有类别android.car.cluster.NAVIGATION
的 activity。- 找到之后,导航应用会使用
InstrumentClusterRenderingService
报告的ActivityOptions
启动 activity,并在 intent 中包含仪表板显示屏属性作为附加内容。
集成 API
InstrumentClusterRenderingService
实现必须满足以下条件:
- 通过将以下值添加到 AndroidManifest.xml,被指定为单例服务。这是确保仪表板服务的单个副本可运行的前提条件,即使在初始化和用户切换期间也可运行:
android:singleUser="true"
- 具有
BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE
系统权限。这样可以确保只有 Android 系统映像中包含的仪表板呈现服务才由CarService
绑定:<uses-permission android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"/>
实现 InstrumentClusterRenderingService
如需构建服务,请执行以下操作:
- 编写一个从 ClusterRenderingService 扩展的类,然后向您的
AndroidManifest.xml
文件添加相应的条目。此类用于控制仪表板显示屏,并且可以呈现导航状态 API 数据(可选)。 - 在
onCreate()
期间,使用此服务初始化与呈现硬件之间的通信。选项包括:- 确定用于仪表板的辅助显示设备。
- 创建一个虚拟显示屏,以便仪表板应用呈现图像并将呈现的图像传输到外部单元(使用 H.264 等视频串流格式)。
- 当上述屏幕准备就绪后,此服务必须调用
InstrumentClusterRenderingService#setClusterActivityLaunchOptions()
才能对在仪表板上显示 activity 必须使用的确切ActivityOptions
进行定义。请使用以下参数:category.
ClusterRenderingService。ActivityOptions.
。一个ActivityOptions
实例,可以用于在仪表板中启动 activity。例如,从 AOSP 上的仪表板实现示例中:getService().setClusterActivityLaunchOptions( CATEGORY_NAVIGATION, ActivityOptions.makeBasic() .setLaunchDisplayId(displayId));
- 当仪表板准备好显示 activity 时,此服务必须调用
InstrumentClusterRenderingService#setClusterActivityState()
。请使用以下参数:category
ClusterRenderingService。state
通过 ClusterRenderingService 生成的软件包。请务必提供以下数据:visible
。用于将仪表板指定为可见,并准备好显示内容。unobscuredBounds
。一个矩形,用于定义仪表板屏幕内可以安全显示内容的区域。例如,表盘和量表覆盖的区域。
- 替换
Service#dump()
方法并报告可用于调试的状态信息(如需了解详情,请参阅 dumpsys)。
InstrumentClusterRenderingService 实现示例
以下示例概述了 InstrumentClusterRenderingService
实现,这会创建一个 VirtualDisplay
,用以在远程实体屏幕上显示仪表板内容。
或者,如果已知连接到 HU 的实体辅助显示设备可用,此代码就可以传递该实体辅助显示设备的 displayId
。
/** * 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); } }
使用 CarAppFocusManager API
CarAppFocusManager API 提供了一个名为 getAppTypeOwner()
的方法,该方法可让 OEM 编写的仪表板服务了解在任何给定时间获得导航焦点的导航应用。OEM 可以使用现有的 CarAppFocusManager#addFocusListener()
方法,然后使用 getAppTypeOwner()
了解获得焦点的应用。凭借此信息,OEM 可以:
- 将仪表板中显示的 activity 切换为获得焦点的导航应用提供的仪表板 activity。
- 检测聚焦的导航应用是否有仪表板 activity。如果聚焦的导航应用没有仪表板 activity(或者此类 activity 已停用),OEM 可以将此信号发送到车载 DIM,以便完全跳过仪表板的导航分面。
使用 CarAppFocusManager
设置和监听当前应用焦点,例如正在进行的导航或一条语音指令。通常,系统中只有此类应用的一个实例正在运行(或处于聚焦状态)。
使用 CarAppFocusManager#addFocusListener(..)
方法监听应用焦点更改:
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 }
使用 CarAppFocusManager#getAppTypeOwner(..)
方法检索获得焦点的给定应用类型当前所有者的软件包名称。如果当前所有者使用了 android:sharedUserId
功能,此方法可能会返回多个软件包名称。
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 } ...
附录:使用示例应用
AOSP 提供了一个实现导航状态 API 的示例应用。
如需运行此示例应用,请执行以下操作:
- 在受支持的 HU 上构建并刷写 Android Auto。使用适用于您的设备的 Android 构建和刷写说明。如需了解相关说明,请参阅使用参考开发板。
- 将实体辅助显示设备连接到 HU(如支持)或打开虚拟辅助 HU:
- 在“设置”应用中,选择开发者模式。
- 依次前往设置 > 系统 > 高级 > 开发者选项 > 模拟辅助显示设备。
- 重新启动 HU
- 如需启动 KitchenSink 应用,请执行以下操作:
- 打开抽屉式导航栏。
- 转到仪表板。
- 点击启动元数据。
KitchenSink 会请求导航焦点,此举会指示 DirectRenderingCluster
服务在仪表板上显示模拟界面。