仪表盘 API

使用仪表盘 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 源代码。 从 pi-car-release 分支(或更高版本)中的 https://android.googlesource.com 获取最新版本的 Android 源代码。
  • 车载信息娱乐系统主机 (HU)。 能够运行 Android 9(或更高版本)的 Android 设备。此设备必须具有自己的显示屏,并且能够使用 Android 的新版本刷新显示屏。
  • 仪表盘是以下选项之一
    • 连接到 HU 的物理辅助显示屏。 如果设备硬件和内核支持多显示屏管理。
    • 独立单元。 通过网络连接连接到 HU 的任何计算单元,能够在其自己的显示屏上接收和显示视频流。
    • 模拟显示屏。 在开发期间,您可以使用以下任一模拟环境
      • 模拟辅助显示屏。 要在任何 AOSP Android 发行版上启用模拟辅助显示屏,请转到设置系统应用中的开发者选项设置,然后选择模拟辅助显示屏。此配置等效于连接物理辅助显示屏,但此显示屏会叠加在主显示屏上。
      • 模拟仪表盘。 AAOS 随附的 Android 模拟器提供了一个用于显示带有 ClusterRenderingService 的仪表盘的选项。

集成架构

集成组件

仪表盘 API 的任何集成都包含以下三个组件

  • CarService
  • 导航应用
  • OEM 仪表盘服务

Integration components

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 必须创建一个 Android 软件包 (APK),其中包含 ClusterRenderingService 的子类。

此类有两个用途

  • 提供 Android 与仪表盘渲染设备之间的接口(本页面的目的)。
  • 接收和渲染导航状态更新,例如逐向导航引导。

对于第一个用途,InstrumentClusterRendererService 的 OEM 实现必须初始化用于在车厢屏幕上渲染信息的辅助显示屏,并通过调用 InstrumentClusterRendererService.setClusterActivityOptions()InstrumentClusterRendererService.setClusterActivityState() 方法,将此信息传达给 CarService

对于第二个功能,仪表盘服务必须提供 ClusterRenderingService 接口的实现,以接收导航状态更新事件,这些事件编码为 eventType 和以 Bundle 编码的事件数据。

集成顺序

下图说明了渲染更新的导航状态的实现

Integration sequence

在此图中,颜色表示以下内容

  • 黄色。 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-lib 中的 Car 类获取 CarAppFocusManager
    2. 在逐向导航开始之前,调用 CarAppFocusManager.requestFocus(),以将 CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION 作为 appType 参数传递。
  4. CarAppFocusManager 将此请求传达给 CarService。如果获得批准,CarService 会检查导航应用软件包,并找到标有类别 android.car.cluster.NAVIGATION 的 Activity。
  5. 如果找到,导航应用会使用 InstrumentClusterRenderingService 报告的 ActivityOptions 启动 Activity,并在 intent 中包含仪表盘显示屏属性作为 extra。

集成 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。 可用于在仪表盘中启动 Activity 的 ActivityOptions 实例。例如,来自 AOSP 上的示例仪表盘实现
      getService().setClusterActivityLaunchOptions(
        CATEGORY_NAVIGATION,
        ActivityOptions.makeBasic()
            .setLaunchDisplayId(displayId));
  4. 当仪表盘准备好显示 Activity 时,此服务必须调用 InstrumentClusterRenderingService#setClusterActivityState()。使用以下参数
    • category ClusterRenderingService
    • state 使用 ClusterRenderingService 生成的 Bundle。请务必提供以下数据
      • 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. 转到 Inst. Cluster
    3. 点击 START METADATA

KitchenSink 请求导航焦点,这将指示 DirectRenderingCluster 服务在仪表盘上显示模拟的用户界面。