Android 14 引入了新的远程访问功能,使合作伙伴能够远程唤醒车辆中的 Android 系统,以执行特定任务。例如,在夜间执行车库模式来应用软件更新。端到端工作流需要多个非 Android 组件。Android 不会为非 Android 组件定义或提供实现,您需自行承担该责任。
如需了解详情,请参阅以下部分:
工作流。示例架构中多个组件之间的工作流,用于客户端注册和任务交付。
编写远程任务客户端。使用远程访问以及学习如何编写远程任务客户端。
供应商实现。示例架构中用于支持远程访问的供应商组件。
恢复出厂设置和转移所有权。了解如何处理恢复出厂设置和车辆所有权转移。
测试远程访问客户端。了解如何测试远程访问功能。
架构
下文内容假定使用了以下示例架构,但这只是假设,可能并不反映实际架构。原始设备制造商 (OEM) 应根据其车辆和服务器架构调整实际实现。
图 1. 示例架构。
示例架构包含以下硬件组件:
硬件组件 | 说明 |
---|---|
应用处理器 | 运行 Android 系统的处理器。在此处理器上,Android 系统可能是在虚拟内存 (VM)(而非实际硬件)上运行。 |
车辆处理器 | 负责控制应用处理器电源的处理器。 |
远程信息处理控制单元 (TCU) | 车载设备上的处理器始终能够接收来自云端的远程消息。假定 TCU 始终处于开启或低功耗模式。使用远程消息唤醒 TCU。 |
唤醒服务器 | 在云端运行的远程服务器,负责与车载 TCU 通信以发出唤醒命令。 |
远程任务服务器 | 远程任务服务器在云端运行,可与人互动并管理远程任务。 |
示例架构包含以下软件组件,这些组件均在 Android 上运行:
在 Android 上运行的软件组件 | 说明 |
---|---|
汽车服务 | AAOS 框架服务,提供远程访问 API。 |
远程任务客户端 | 供应商编写的 Service,用于执行远程任务。一个 Android 系统可以运行多个远程任务客户端。 |
Remote Access HAL | 必须实现,才能进行远程访问。 用于在 AAOS 与非 Android 组件(如 TCU)之间通信的抽象层。 |
非 Android 软件组件如下所述:
非 Android 软件组件 | 说明 |
---|---|
唤醒客户端 | 在 TCU 上运行的软件,用来与唤醒服务器保持长期连接。此外,它还会与 Remote Access HAL 保持连接,以便向汽车服务交付远程任务。 |
唤醒服务器实现 | 用来与在 TCU 上运行的唤醒客户端进行通信的服务器。可向唤醒客户端发送唤醒请求。 |
远程任务服务器实现 | 用于管理远程任务的服务器。用户通过与该服务器互动来发出和监控远程任务。 |
工作流
本部分列出了示例工作流中的各个步骤。
示例工作流
详细的工作流类似于以下流程:
用户将车辆停在车库里。
当车辆不太可能进行交互时,合作伙伴设法在夜间更新车辆。
合作伙伴云服务器向车辆发送更新系统远程任务。具体而言,就是远程信息处理控制单元 (TCU)。
车载 TCU 会唤醒 Android 电子控制单元 (ECU),并且 OEM 服务会触发车库模式。
Android 运行车库模式,以通过 Google Play 下载并安装更新。
应用更新后,Android 会将任务标记为完成,并结束连接或达到指定的超时。
详细的工作流
远程访问需要执行两个重要步骤。第一步是注册客户端,也就是将特定用户关联到在特定车辆上运行的特定远程任务客户端。另一步是交付任务,也就是向特定车辆上运行的特定远程任务客户端交付特定用户的远程任务。
注册客户端
如需使用远程访问功能,用户必须打开远程任务客户端应用至少一次,并完成客户端注册流程(内容加粗的步骤由 AAOS 来实现):
启动时,汽车服务从 Remote Access HAL 获取车辆信息。
启动时,汽车服务根据 intent 过滤器和权限启动所有远程任务客户端。
远程任务客户端启动时,远程任务客户端会向汽车服务自行注册。
汽车服务将注册信息(包括车辆 ID 和客户端 ID)告知远程任务客户端。该客户端 ID 是唯一的,并由汽车服务分配给此客户端。在同一车辆上的所有远程任务客户端中,它肯定是唯一的。
用户通过远程任务客户端登录远程任务服务器,并为此车辆启用远程访问功能。此步骤通常涉及通过远程任务服务器进行身份验证。
远程任务客户端会将用户的信息以及车辆 ID 和客户端 ID 上传到远程任务服务器,并要求服务器将用户与此特定客户端和此特定车辆关联起来。
(可选)此步骤可能会涉及用户进行额外的双重身份验证。
远程任务服务器必须验证请求中提供的车辆 ID 是否与发送者的车辆 ID 相符,这可以通过车辆认证来完成。
除非恢复出厂设置,否则每个用户的每辆车只需进行一次客户端注册。客户端 ID 存储在汽车服务本地,并且对于同一客户端保持不变。
图 2. 注册客户端。
取消注册客户端
用户可以在车辆或远程任务服务器中解除车辆与其账号的关联。了解相关操作:
车辆:用户可以打开远程任务客户端应用并发出解除关联请求,以解除此车辆与其先前关联的用户账号的关联。
远程任务服务器:用户可以登录其账号,并解除之前关联的车辆与此账号的关联。
如果用户解除车辆与其账号的关联,远程任务服务器必须移除为特定用户存储的映射。
交付任务
在云端:
用户使用远程任务服务器向特定车辆发送远程任务。
远程任务服务器将用户 ID 映射到车辆 ID 和客户端 ID。它会将任务数据、车辆 ID 和客户端 ID 发送到唤醒服务器。
唤醒服务器查找车辆 ID 的特定 TCU。我们假设 TCU 注册已完成,并将任务数据和客户端 ID 发送到 TCU。
在车辆上(粗体文本表示 AAOS 执行的任务):
TCU 从远程服务器接收远程任务。
如果运行 AAOS 的应用处理器 (AP) 处于关闭状态,TCU 会使用车载处理器 (VP) 唤醒 AP。
汽车服务从 TCU 接收任务。
汽车服务将任务分发给相应的远程任务客户端。
远程任务客户端接收并执行任务。
(可选)远程任务客户端联系任务服务器来获取更多任务详细信息,然后执行该任务。
(可选)远程任务客户端服务向任务服务器报告任务结果。
完成任务后,远程任务客户端会通知汽车服务。
必要时,汽车服务会恢复车辆的电源状态。
图 3. 交付任务。
编写远程任务客户端
CarRemoteAccessManager
会为远程访问功能提供 API。如需了解详情,请参阅 CarRemoteAccessManager。远程任务客户端是一项执行远程任务并使用 CarRemoteAccessManager
的 Android 服务。这需要 PERMISSION_USE_REMOTE_ACCESS
和 PERMISSION_CONTROL_REMOTE_ACCESS
,并且必须为 RemoteTaskClientService
声明一个 intent 过滤器,例如:
<service android:name=".remoteaccess.RemoteTaskClientService"
android:directBootAware="true"
android:exported="true">
<intent-filter>
<action android:name="android.car.remoteaccess.RemoteTaskClientService" />
</intent-filter>
</service>
远程任务客户端应在创建期间向汽车服务注册自身:
public final class RemoteTaskClientService extends Service {
@Override
public void onCreate() {
// mCar = Car.createCar()...
mRemoteAccessManager = (CarRemoteAccessManager)
mcar.getCarManager(Car.CAR_REMOTE_ACCESS_SERVICE);
if (mRemoteAccessManager == null) {
// Remote access feature is not supported.
return;
}
mRemoteAccessManager.setRemoteTaskClient(executor, mRemoteTaskClient);
}
}
它必须覆盖 onBind 函数,以返回 null。
@Override
public IBinder onBind(Intent intent) {
return null;
}
汽车服务会管理其生命周期。汽车服务会在启动期间和远程任务到达时绑定到此服务。任务完成后,汽车服务会解除与该服务的绑定。如需了解详情,请参阅管理服务的生命周期。
远程任务客户端以系统用户身份运行,因此无法访问任何特定于用户的数据。
以下示例展示了如何处理已注册的回调:
private final class RemoteTaskClient
implements CarRemoteAccessManager.RemoteTaskClientCallback {
@Override
public void onRegistrationUpdated(
RemoteTaskClientRegistrationInfo info) {
// Register to remote task server using info.
}
@Override
public void onRemoteTaskRequested(String taskId,
byte[] data, int remainingTimeSec) {
// Parses the data and execute the task.
// Report task result to remote task server.
mRemoteAccessManager.reportRemoteTaskDone(taskId);
}
@Override
public void onShutdownStarting(CompleteableRemoteTaskFuture future) {
// Stop the executing task.
// Clear the pending task queue.
future.complete();
}
}
供应商实现
远程访问功能为可选功能,默认处于停用状态。如需启用此功能,请添加如下 RRO:
// res/xml/overlays.xml
<?xml version="1.0" encoding="utf-8"?>
<overlay>
<item target="array/config_allowed_optional_car_features" value="@array/config_allowed_optional_car_features" />
</overlay>
// res/values/config.xml
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string-array translatable="false" name="config_allowed_optional_car_features">
<item>car_remote_access_service</item>
</string-array>
</resources>
// Android.bp
runtime_resource_overlay {
name: "RemoteAccessOverlay",
resource_dirs: ["res"],
manifest: "AndroidManifest.xml",
sdk_version: "current",
product_specific: true
}
或者,在 userdebug/eng build 中使用以下 adb 命令:
adb shell cmd car_service enable-feature car_remote_access_service
对 Android 设备的要求
Remote Access HAL
远程访问硬件抽象层 (HAL) 是由供应商实现的抽象层,用于在 AAOS 与其他 ECU(例如 TCU)之间进行通信。这是支持远程访问功能的必备条件。如果没有实现远程访问功能,则无需实现该抽象层。
该接口在 IRemoteAccess.aidl 中定义,包含以下方法:
类 | 说明 |
---|---|
String getVehicleId() |
获取可由唤醒服务器识别的唯一车辆 ID。 |
String getWakeupServiceName() |
获取远程唤醒服务器的名称。 |
String getProcessorId() |
获取可通过唤醒客户端识别的唯一处理器 ID。 |
void setRemoteTaskCallback(IRemoteTaskCallback callback)
设置在请求远程任务时调用的回调。 |
|
void clearRemoteTaskCallback() |
清除先前设置的远程任务回调。 |
void notifyApStateChange(in ApState state)
检测应用处理器是否已准备好接收远程任务。 |
回调接口在 IRemoteTaskCallback.aid
中定义。
类 | 说明 |
---|---|
oneway void onRemoteTaskRequested(String clientId, in byte[] data)
请求远程任务时调用的回调。 |
请参阅使用外部 TCU 的参考实现。该实现使用长期读取流来接收远程任务,并支持以下 debug
命令:
dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default
车载 HAL
若要支持远程访问功能,VHAL 必须支持以下属性:
类 | 说明 |
---|---|
SHUTDOWN_REQUEST |
请求关闭车机。 |
VEHICLE_IN_USE |
|
如需了解详情,请参阅支持的系统属性。
静音模式
远程访问功能必须支持静音模式,以便在没有用户在场的情况下,以静音模式启动车辆执行远程任务。在静音模式下,AAOS 设备启动时会关闭显示和音频。
静音模式通过两个 Linux 内核 sysfs
文件控制。
类 | 说明 |
---|---|
/sys/kernel/silent_boot/pm_silentmode_kernel_state
表示当前的静音模式。 |
|
/sys/kernel/silent_boot/pm_silentmode_hw_state
表示用于设置新静音模式的硬件信号。 |
车载处理器向 Android SoC 发送硬件信号,以开启/关闭静音模式。信号(0 或 1)被写入 /sys/kernel/silent_boot/pm_silentmode_hw_state
。然后,AAOS 框架会相应地更新 /sys/kernel/silent_boot/pm_silentmode_kernel_state
,以表示当前的静音模式。AAOS 模块会检查 /sys/kernel/silent_boot/pm_silentmode_kernel_state
,以了解系统是否处于静音模式。
收到远程任务且 AAOS 启动时,车载处理器会设置静音模式并启动 AAOS,以便系统在关闭显示/音频的状态下启动。
车载非 Android 组件
车载处理器
车辆处理器是车辆中的处理器,可控制搭载 Android 的应用处理器的电源。在示例架构中,TCU 通过向车载处理器发送信号来唤醒应用处理器。
车载非 Android 组件
车载 TCU 可以随时接收远程消息。
唤醒客户端在 TCU 上运行,以确保与远程唤醒服务器的长期连接。
在 AP 上运行的 AAOS 可以通过 Remote Access HAL 与 TCU 上运行的唤醒客户端通信。
图 4. TCU(唤醒客户端)。
云端组件
唤醒服务器
唤醒服务器与 TCU 上的唤醒客户端通信,以便:
- 与车载 TCU 保持长期连接。
- 根据车辆 ID 查找特定 TCU。
- 报告车辆状态。例如,在线或离线,或者远程任务服务器上次在线的时间。
在实际实现中,唤醒服务器可与远程任务服务器合并。
远程任务服务器
远程任务服务器负责管理这些远程任务。
用户与服务器互动,以启动新的远程任务并监控远程任务。
使用远程唤醒服务器唤醒车辆中的应用处理器。
与在车辆上运行的远程任务客户端交互。
存储客户端注册信息。这会将特定用户与特定车辆上的特定远程任务客户端关联起来。
通常,通过远程任务服务器发送到唤醒服务器、车载 TCU,并最终发送到远程任务客户端的“任务数据”只是任务 ID。远程任务客户端将使用任务 ID 从远程任务服务器提取详细信息。
隐私保护和安全性要求
任务 | 条件 | 要求 |
---|---|---|
TCU(唤醒客户端) | 最低要求 |
|
唤醒服务器 | 最低要求 |
|
远程任务客户端 | 最低要求 |
|
远程任务服务器 | 最低要求 |
|
恢复出厂设置和转移所有权
如果用户恢复出厂设置,系统会擦除汽车服务中存储的客户端 ID。不过,系统不会通知相关服务器(远程任务服务器和远程唤醒服务器)。服务器会保留现已过期的客户端 ID 与车辆的映射关系。因此,如果用户为车辆启动新的远程任务,系统会使用已过期的客户端 ID。车辆会被唤醒,但远程任务却无法执行,因为远程任务客户端的客户端 ID 与之不符。
下面介绍了一种可能的恢复出厂设置实现。
当用户恢复出厂设置时,供应商会提示用户登录远程任务服务器,并解除车辆与其账号的关联(如果用户之前已关联车辆)。在恢复出厂设置期间,无法保证设备可以访问网络。因此,在恢复出厂设置时从设备发出解除关联请求可能不可行。
每当转移车辆所有权时,都应执行一些操作,以确保前车主无法再向车辆发出远程任务。例如,新车主可能需要:
将设备恢复出厂设置。 这样可以确保重新生成客户端 ID。完成此步骤后,前车主仍可唤醒车辆,但无法再执行远程任务。
打开远程任务客户端应用,然后按照取消注册客户端流程解除车辆与前车主的账号之间的关联。新车主可以按照客户端流程进行注册,将车辆与其账号关联,然后替换先前关联的账号。
新车主可以通过注册客户端流程将车辆与自己的账号相关联,并替换先前关联的账号。
测试远程任务客户端
我们提供了参考 Remote Access HAL default
目录,用于测试远程任务客户端。您可以使用以下 debug
命令向 HAL 注入一个模拟远程任务,如果您提供了正确的客户端 ID,则该任务就会转发给远程任务客户端。您可以通过在远程任务客户端实现中记录注册信息来获取客户端 ID。
adb root && adb shell dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --inject-task [clientID] [taskData]