Instrument Cluster API

Instrument Cluster API(一款 Android API)可在车载辅助显示设备(如位于方向盘后方的仪表盘上的辅助显示设备)上显示导航应用,包括 Google 地图。本页面介绍如何创建服务以控制此类辅助显示设备并将该服务与 CarService 集成,以便导航应用可以显示界面。

术语

本页面中使用了以下术语。

CarInstrumentClusterManager
一个 CarManager 的实例,使外部应用能够在仪表板上启动 activity,并在仪表板准备好显示 activity 时接收回调。
CarManager
所有管理器的基类,外部应用使用这些管理器与通过 CarService 实现的汽车特有服务进行交互。
CarService
一种 Android 平台服务,可在 Google 地图等外部应用与仪表板等汽车特有功能之间提供通信服务。
目的地
车辆将导航到的最终目的地。
预计到达时间 (ETA)
到达目的地的预计时间。
车机 (HU)
车内嵌入的主要计算单元。HU 会运行所有 Android 代码,并连接到汽车中央显示屏。
仪表板
位于方向盘后方车载仪表之间的辅助显示设备。这可以是通过汽车内部网络(CAN 总线)连接到 HU 的独立计算单元,也可以是连接到 HU 的辅助显示设备。
InstrumentClusterRenderingService
用于与仪表板显示屏交互的服务的基类。原始设备制造商 (OEM) 必须提供该类的扩展,以便与 OEM 特有硬件互动。
KitchenSink 应用
Android Automotive 中包含的测试应用。
路线
车辆导航到达目的地所经的特定路径。
单例服务
一种具有 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 会在启动过程中读取 ClusterRenderingServiceInstrumentClusterRendererService 键,以定位 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 平台提供的 CarServiceCarNavigationStatusManager。如需了解详情,请参阅 CarCAR_NAVIGATION_SERVICE
  • 青色。由 OEM 实现的 InstrumentClusterRendererService
  • 紫色。由 Google 和第三方开发者实现的导航应用。
  • 绿色CarAppFocusManager。如需了解详情,请参阅下文的使用 CarAppFocusManager APICarAppFocusManager

导航状态信息流程遵循以下序列:

  1. CarService 初始化 InstrumentClusterRenderingService
  2. 在初始化期间,InstrumentClusterRenderingService 使用以下选项更新 CarService
    1. 仪表板屏幕属性,如无遮挡边界(可稍后了解有关无遮挡边界的更多详情)。
    2. 在仪表板显示屏内启动 activity 所需要的 activity 选项。如需了解详情,请参阅 ActivityOptions
  3. 导航应用(如适用于 Android Automotive 的 Google 地图或任何具有所需权限的地图应用):
    1. 使用 Car 类从 car-lib 中获取 CarAppFocusManager
    2. 在启动精细导航路线之前,调用 CarAppFocusManager.requestFocus() 以将 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION 作为 appType 参数传递。
  4. CarAppFocusManager 会将此请求传达给 CarService。在授予条件下,CarService 会检查导航应用软件包,并定位标有类别 android.car.cluster.NAVIGATION 的 activity。
  5. 找到之后,导航应用会使用 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

如需构建服务,请执行以下操作:

  1. 编写一个从 ClusterRenderingService 扩展的类,然后向您的 AndroidManifest.xml 文件添加相应的条目。此类用于控制仪表板显示屏,并且可以呈现导航状态 API 数据(可选)。
  2. onCreate() 期间,使用此服务初始化与呈现硬件之间的通信。选项包括:
    • 确定用于仪表板的辅助显示设备。
    • 创建一个虚拟显示屏,以便仪表板应用呈现图像并将呈现的图像传输到外部单元(使用 H.264 等视频串流格式)。
  3. 当上述屏幕准备就绪后,此服务必须调用 InstrumentClusterRenderingService#setClusterActivityLaunchOptions() 才能对在仪表板上显示 activity 必须使用的确切 ActivityOptions 进行定义。请使用以下参数:
    • category. ClusterRenderingService
    • ActivityOptions.。一个 ActivityOptions 实例,可以用于在仪表板中启动 activity。例如,从 AOSP 上的仪表板实现示例中:
      getService().setClusterActivityLaunchOptions(
        CATEGORY_NAVIGATION,
        ActivityOptions.makeBasic()
            .setLaunchDisplayId(displayId));
      
  4. 当仪表板准备好显示 activity 时,此服务必须调用 InstrumentClusterRenderingService#setClusterActivityState()。请使用以下参数:
    • category ClusterRenderingService
    • state 通过 ClusterRenderingService 生成的软件包。请务必提供以下数据:
      • visible。用于将仪表板指定为可见,并准备好显示内容。
      • unobscuredBounds。一个矩形,用于定义仪表板屏幕内可以安全显示内容的区域。例如,表盘和量表覆盖的区域。
  5. 替换 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 的示例应用。

如需运行此示例应用,请执行以下操作:

  1. 在受支持的 HU 上构建并刷写 Android Auto。使用适用于您的设备的 Android 构建和刷写说明。如需了解相关说明,请参阅使用参考开发板
  2. 将实体辅助显示设备连接到 HU(如支持)或打开虚拟辅助 HU:
    1. 在“设置”应用中,选择开发者模式
    2. 依次前往设置 > 系统 > 高级 > 开发者选项 > 模拟辅助显示设备
  3. 重新启动 HU
  4. 如需启动 KitchenSink 应用,请执行以下操作:
    1. 打开抽屉式导航栏。
    2. 转到仪表板
    3. 点击启动元数据

KitchenSink 会请求导航焦点,此举会指示 DirectRenderingCluster 服务在仪表板上显示模拟界面。