Instrument Cluster API

使用 Instrument Cluster API(Android API)在汽车的辅助显示屏上(例如仪表板上的方向盘后面)显示导航应用程序,包括 Google 地图。本页介绍如何创建服务来控制辅助显示屏,然后将该服务与CarService集成,以便导航应用程序可以显示用户界面。

术语

本页使用以下术语:

学期描述
CarInstrumentClusterManager一个CarManager实例,使外部应用程序能够在仪表盘上启动活动,并在仪表盘准备好显示活动时接收回调。
CarManager外部应用程序使用的所有管理器的基类,用于与CarService实现的汽车特定服务进行交互。
CarService Android 平台服务,提供外部应用程序(包括 Google 地图)和汽车特定功能(例如仪表组访问)之间的通信。
目的地车辆将导航到的最终目的地。
预计到达时间预计到达目的地的时间。
主机 (HU)嵌入汽车中的主要计算单元。 HU 运行所有 Android 代码,并连接到汽车的中央显示屏。
仪表组辅助显示屏位于方向盘后面和汽车仪表之间。它可以是通过汽车内部网络(CAN 总线)连接到 HU 的独立计算单元,也可以是连接到 HU 的辅助显示器。
InstrumentClusterRenderingService用于与仪表盘显示屏交互的服务的基类。 OEM 必须提供此类的扩展,以便与 OEM 特定的硬件进行交互。
厨房水槽应用程序Android Automotive 附带的测试应用程序。
路线车辆导航到达目的地的特定路径。
单例服务具有android:singleUser属性的 Android 服务。在任何给定时间,Android 系统上最多运行一个服务实例。

先决条件

要开发集成,请务必具备以下要素:

  • 安卓开发环境。要设置 Android 开发环境,请参阅构建要求
  • 下载Android源代码。从 pi-car-release 分支(或更高版本)获取最新版本的 Android 源代码:https: //android.googlesource.com
  • 主机 (HU)。能够运行 Android 9(或更高版本)的 Android 设备。该设备必须有自己的显示屏,并且能够使用新版本的 Android 来刷新显示屏。
  • 仪表组是以下之一:
    • 连接到 HU 的物理辅助显示器。如果设备硬件和内核支持多显示器的管理。
    • 独立单位。通过网络连接连接到 HU 的任何计算单元,能够在其自己的显示器上接收和显示视频流。
    • 模拟显示。在开发过程中,您可以使用以下模拟环境之一:
      • 模拟辅助显示器。要在任何 AOSP Android 发行版上启用模拟辅助显示器,请转到“设置”系统应用程序中的“开发人员选项”设置,然后选择“模拟辅助显示器”。此配置相当于连接物理辅助显示器,但限制是该显示器叠加在主显示器上。
      • 模拟仪表组。 Android Automotive 附带的 Android 模拟器提供了一个选项来显示仪表盘, ClusterRenderingService服务已连接到辅助显示屏。
      • 模拟器 _qemu-pipes 。 ClusterRenderingService服务已连接到辅助显示器。连接到该模拟外部显示器的参考仪表组实现。

集成架构

集成组件

Instrument Cluster API 的任何集成都包含以下三个组件:

  • CarService
  • 导航应用程序
  • OEM仪表组服务

集成组件

汽车服务

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更新仪表集群状态。 (在本例中, bound指的是Android Services 。)

仪表组服务

OEM 必须创建一个 Android 包 (APK),其中包含连接到辅助显示器的ClusterRenderingService服务的子类。 ClusterRenderingService服务已连接到辅助显示器。样品。

这个类有两个目的:

  • 提供 Android 和 Instrument Cluster 渲染设备的接口(本页的目的)。
  • 接收并呈现导航状态更新,例如逐向导航指导。

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

对于第二个功能,Instrument Cluster 服务必须提供连接到辅助显示器的ClusterRenderingService服务的实现。接收导航状态更新事件的接口,这些事件被编码为eventType和编码在捆绑中的事件数据。

积分顺序

下图说明了呈现更新的导航状态的实现:

积分顺序

在此图中,颜色表示以下含义:

  • 黄色的。 Android平台提供的CarServiceCarNavigationStatusManager 。要了解更多信息,请参阅汽车CAR_NAVIGATION_SERVICE
  • 青色。 InstrumentClusterRendererService由 OEM 实现。
  • 紫色的。由 Google 和第三方开发者实施的导航应用程序。
  • 绿色的。 CarAppFocusManager 。要了解更多信息,请参阅下面的使用 CarAppFocusManager APICarAppFocusManager

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

  1. CarService初始化InstrumentClusterRenderingService
  2. 在初始化期间, InstrumentClusterRenderingService使用以下内容更新CarService
    1. 仪表组显示属性,例如清晰边界(请参阅稍后有关清晰边界的更多详细信息)。
    2. 在仪表盘显示屏内启动活动所需的活动选项(请参阅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活动。
  5. 如果找到,导航应用程序将使用InstrumentClusterRenderingService报告的ActivityOptions来启动活动,并将仪表集群显示属性作为附加内容包含在意图中。

集成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扩展的类,将服务连接到辅助显示器。
  2. 然后将相应的条目添加到您的AndroidManifest.xml文件中。此类控制仪表盘显示,并且可以(可选)呈现导航状态 API 数据。
  3. onCreate()期间,使用此服务初始化与渲染硬件的通信。选项包括:
    • 确定用于组合仪表的辅助显示屏。
    • 创建虚拟显示器,以便仪表盘应用程序渲染并将渲染的图像传输到外部设备(使用视频流格式,例如 H.264)。
  4. 当上面指示的显示准备就绪时,此服务必须调用InstrumentClusterRenderingService#setClusterActivityLaunchOptions()来定义必须用于在仪表集群上显示活动的确切ActivityOptions 。使用这些参数:
    • 类别。 ClusterRenderingService服务已连接到辅助显示器。
    • ActivityOptions.一个ActivityOptions实例,可用于启动仪表盘中的活动。例如,来自 AOSP 上的仪表集群实施示例:
      getService().setClusterActivityLaunchOptions(
         CATEGORY_NAVIGATION,
         ActivityOptions.makeBasic()
            .setLaunchDisplayId(displayId));
      
  5. 当仪表集群准备好显示活动时,该服务必须调用InstrumentClusterRenderingService#setClusterActivityState() 。使用这些参数:
    • ClusterRenderingService服务category连接到辅助显示器。
    • 使用ClusterRenderingService服务生成的state捆绑包已连接到辅助显示器。
    • 请务必提供以下数据:
      • visible将组合仪表指定为可见并准备好显示内容。
      • unobscuredBounds定义仪表盘显示屏内可安全显示内容的区域的矩形。例如,刻度盘和仪表覆盖的区域。
  6. 覆盖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 可以将此信号发送到汽车 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。 ClusterRenderingService服务连接到辅助显示器。
  4. 要启动 KitchenSink 应用程序:
    1. 打开抽屉。
    2. 研究所。簇
    3. 单击“开始元数据”

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