技术详情

本部分提供了特定于 Control Center 参考应用的技术细节。

控制中心是一款非捆绑式、具有特权且经过系统签名的应用,最低 SDK 版本为 35 (Android V [API 级别 35])。该应用安装在 system/priv-app 中,以使用 System APIs。如需读取媒体信息,应用必须经过平台签名。您可以通过无线下载 (OTA) 方式更新应用。

后台服务

“控制中心”应用依赖于后台服务来实现其功能。 Control Center ServiceVendor ServiceController 在用户生命周期的 user-post-unlocked 状态下启动。控制中心必须始终处于活动状态并在后台通信,应用不能依赖于用户打开应用。

Control Center Service 通过使用通信 API 连接到其他居住区域中的其他自身实例并与之通信。请务必阅读集成指南,了解每个用户的控制中心实例如何建立连接以及发送和接收数据。

图示:控制中心服务由供应商 ServiceController 启动。
图 5. 由供应商 ServiceController 启动的控制中心服务。

通信

连接后,Control Center Service 会与传达信息的 protobuf 对象进行通信。如需使用 Communication APIsprotobuf 传递到另一个乘员区域,系统会将 protobuf 转换为 byte array,创建 payload object,并通过 CarOccupantConnectionManager#sendPayload 发送 Payload

message ControlCenterPayload {
    required Type messageType = 1;
    // ...
    enum Type {
       MEDIA_STATUS = 0;
       MEDIA_COMMAND = 1;
       INPUT_LOCK_COMMAND = 2;
       INPUT_LOCK_SUCCESSFUL = 3;
       CANCEL_AUDIO_REQUEST = 4;
       MIRRORING_REQUEST_RECEIVER_DECISION = 5;
       MIRRORING_SESSION_ENDED = 6;
    }
}

private fun parsePayload(
    senderZone: OccupantZoneInfo,
    payload: Payload
) {
     val parsedPayload =
         ControlCenterPayload.parseFrom(payload.bytes)
             when (parsedPayload.messageType) {
                 ControlCenterPayload.Type.MEDIA_STATUS -> {
                     // logic here
                 }
             }
             //…
}

数据

有关乘员区域的信息以 OccupantZoneData 对象的形式存储在控制中心内。对本地 OccupantZoneData 的更改通过 Comms API 发送到其他控制中心实例。

当收到的 Payload 被解析时,解析后的数据会传递给本地 OccupantZoneStateRepository,后者会通知视图发生更改。大多数数据都通过 Kotlin flows on Android 在类之间传递。

处理座舱扬声器音频请求

为了让驾驶员始终能够接收乘客通过车厢扬声器播放音频的请求,驾驶员的 Control Center Service 在创建时会注册 Primary ZoneMedia Audio RequestCallback

回调会收到对 CarAudioManager#requestMediaAudioOnPrimaryZone 的调用通知。驱动程序的 Control Center Service 通过创建浮动通知 (HUN) 来处理请求,该通知可通过 CarAudioManager#allowMediaAudioOnPrimaryZone(boolean) 接受或拒绝。

在其他屏幕上共同观看视频

共同观看功能之所以有效,是因为 CarActivityManager 中存在 Task Mirroring APIsTaskMirroringManager 首先在 CarActivityManager#getVisibleTasks 中搜索正在播放的 MediaSession 应用的软件包,然后创建 VirtualDisplay 并通过 CarActivityManager#moveRootTaskToDisplay 将可见任务移至此显示屏。

这会返回一个 IBinder 令牌,MirroredSurfaceView 可在布局中使用该令牌通过 MirroredSurfaceView#mirrorSurface 显示任务。Communication API Payload 对象将令牌传递给其他居住者区域。

这些乘员区中的每个 Control Center 实例都会启动 Mirroring activity 并使用该令牌填充 MirroredSurfaceView

镜像令牌的流程,用于在另一屏幕上显示任务。
图 6. 镜像令牌流。

任务镜像 API

控制中心使用以下任务镜像 API:

CarActivityManager#getVisibleTasks(int displayId)
<ActivityManager.RunningTaskInfo> 调用,用于显示发件人。

CarActivityManager#moveRootTaskToDisplay(int virtualDisplayId)
将所选的可见任务移至已创建的虚拟显示屏。

CarActivityManager#createTaskMirroringToken(int taskId)
创建一个任务来镜像 IBinder 令牌,应在任务移至虚拟显示屏后调用。

MirroredSurfaceView#mirrorSurface(IBinder token)
一种自定义视图对象,使用令牌显示虚拟显示屏的内容。

控制中心内任务镜像的限制

控制中心仅支持 MediaSession 应用的任务镜像。 不过,该 API 可以镜像任何任务。虚拟显示屏的尺寸与发送方显示屏的尺寸相同。如果接收器的显示屏使用不同的分辨率和尺寸,虚拟显示屏会显示在屏幕中央。

显示可见任务

控制中心将机箱 Theme.CarUi.NoToolbar 扩展为半透明窗口。这意味着,当在任务上打开控制中心时,任务会在 CarActivityManager#getVisibleTasks 中返回,从而允许镜像任务。

接收镜像信息

控制中心会通知其他应用镜像会话。如需接收更新,应用必须绑定到 Control Center Service 并发送 Handler 类作为客户端,该客户端会从 Control Center Service 接收并处理 Messages

客户端应用可以接收镜像应用的软件包名称,并使用以下键为控制中心内托管镜像应用的 activity 启动 intent URI

  • _config_msg_mirroring_pkg_name_key_
  • _config_msg_mirroring_redirect_uri_key_

这些配置必须存在于客户端应用资源和控制中心资源中。

客户端应用从控制中心接收镜像信息。
图 7. 从控制中心接收镜像信息。

调试控制中心

Logger 类用于处理控制中心日志,可以配置为强制记录日志。

class Logger(cls: Class<*>) {

   companion object {
       private const val FORCE_LOGS = false
   }

   private val tag: String

   init {
       tag = cls.simpleName
   }

   fun v(message: String) {
       if (Log.isLoggable(tag, Log.VERBOSE) || FORCE_LOGS) {
           Log.v(tag, message)
       }
   }
...

系统应用和可更新性

由于控制中心是系统应用,并且因使用仅限签名的权限而经过平台签名,因此控制中心必须预安装在设备上,并且只能通过 OTA 更新,与 Car Media App 类似。

从源代码构建控制中心

如需获取控制中心源代码,请参阅集成未捆绑应用

多屏幕设备上的用户隐私

借助控制中心,所有乘客都可以在所有显示屏上查看媒体信息。 Google 建议您插入非阻塞式隐私权声明来通知用户。 Google 建议您在登录显示器时在系统级别执行此操作。