ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

第 52 章 CompanionDeviceManager 与虚拟设备

第 52 章 CompanionDeviceManager 与虚拟设备 Android 的 CompanionDeviceManager(CDM)与 VirtualDeviceManager(VDM)构成一套分层基础设施,让手机能够与外部硬件配对,包括智能手表、平板、车载主机、个人电脑,甚至 AR 眼镜,并将这些外部硬件呈现为一等公民级别的计算显示载体。CDM 负责管理设备关联生命周期、设备在场检测、安全传输通道以及跨设备数据同步。VDM 构建在 CDM 关联关系之上,允许远程配套设备承载虚拟显示器、虚拟输入设备、虚拟传感器、虚拟摄像头以及虚拟音频链路,实际上就是把完整的 Android 体验投射到外部硬件。本章完整讲解两套系统的服务端实现,从最开始的 BLE / 蓝牙发现握手,一直到可注入触摸事件、音频流重路由的运行态虚拟显示器。所有源码路径均相对于 AOSP 源码根目录。52.1 CompanionDeviceManager 架构52.1.1 服务概览服务端入口为CompanionDeviceManagerService,所在路径:frameworks/base/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java该文件在 Android 17 中约 1154 行,作为总调度器。它并不自己实现全部功能,而是将工作委派给一系列专用处理器与管理器,每个组件位于独立子包内:子包核心类职责association/AssociationRequestsProcessor处理传入的关联请求association/AssociationStore关联记录的增删改查association/DisassociationProcessor解除关联以及角色清理工作devicepresence/DevicePresenceProcessorBLE / 蓝牙设备在场监控transport/CompanionTransportManager数据传输通道挂载 / 卸载securechannel/SecureChannel基于 UKEY2 的加密通道datatransfer/SystemDataTransferProcessor跨设备权限同步datatransfer/contextsync/CrossDeviceSyncController通话元数据同步datatransfer/continuity/TaskContinuityManagerService设备间任务交接datasync/DataSyncProcessor通用元数据同步actionrequest/ActionRequestProcessor应用发起的动作请求devicetrust/TrustedDeviceProcessor受信任设备密钥交换(Android 17)powerexemption/CompanionExemptionProcessor电源与自动撤销豁免(Android 17)virtual/VirtualDeviceManagerService虚拟设备创建与管理devicetrust/与powerexemption/子包为 Android 17 新增,将在 52.7 节介绍;actionrequest/子包在 Android 16 就已引入,Android 17 增加了额外返回值常量。CompanionDeviceManagerService还持有顶层的BackupRestoreProcessor,用于设备迁移场景下对关联关系做备份与恢复。下面的类图展示了CompanionDeviceManagerService如何协调各个委托对象:这些处理器成员变量统一在CompanionDeviceManagerService中声明(参考文件 154‑170 行),并在构造函数中完成装配(200‑236 行)。每个处理器都会拿到共享的AssociationStore与CompanionTransportManager,保证所有组件看到同一套关联集合与同一套传输通道。52.1.2 权限模型CDM 执行严格的权限模型。关键权限在CompanionDeviceManagerService.java文件头部以静态导入形式声明:import static android.Manifest.permission.ACCESS_COMPANION_INFO; import static android.Manifest.permission.ACCESS_COMPANION_MESSAGE_PCC; import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES; import static android.Manifest.permission.BLUETOOTH_CONNECT; import static android.Manifest.permission.DELIVER_COMPANION_MESSAGES; import static android.Manifest.permission.MANAGE_COMPANION_DEVICES; import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED; import static android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE; import static android.Manifest.permission.USE_COMPANION_TRANSPORTS;源码位置:frameworks/base/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java,第 20‑28 行。权限对应不同能力:ASSOCIATE_COMPANION_DEVICES:创建任意新关联关系所必需。REQUEST_COMPANION_SELF_MANAGED:自管理关联关系所需(由应用自己管理传输通道,不依赖基于 MAC 地址的在场检测)。REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE:注册设备在场回调所需,接收 BLE / 蓝牙设备出现、消失通知。USE_COMPANION_TRANSPORTS:挂载系统数据传输通道(文件描述符)用于跨设备消息通信。DELIVER_COMPANION_MESSAGES:通过 CDM 传输通道发送消息。MANAGE_COMPANION_DEVICES:系统级权限,用于 shell 命令以及管理类操作。ACCESS_COMPANION_INFO:查询其他用户的配套设备信息。ACCESS_COMPANION_MESSAGE_PCC:Android 17 新增;管控访问私有计算核心消息通路,用于受信任设备与 AI 代理流程。52.1.3 开机启动流程CompanionDeviceManagerService属于SystemService,参与标准服务开机生命周期。在onBootPhase()阶段,该服务执行如下操作:通过AssociationStore.refreshCache()从磁盘读取持久化的关联数据。初始化DevicePresenceProcessor,启动 BLE / 蓝牙连接监控。向CompanionTransportManager注册,接收传输通道生命周期事件。初始化CrossDeviceSyncController,用于通话元数据同步。初始化SystemDataTransferProcessor,用于权限同步。关联数据存储在设备加密存储(DE),因此在用户解锁设备之前就可以访问。该逻辑在AssociationStore.refreshCache()实现中明确体现:// The data is stored in DE directories, so we can read the data for all users now // (which would not be possible if the data was stored to CE directories). MapInteger, Associations userToAssociationsMap = mDiskStore.readAssociationsByUsers(userIds);源码位置:frameworks/base/services/companion/java/com/android/server/companion/association/AssociationStore.java,177‑180 行,位于 164 行的refreshCache()函数内部。52.1.4 内部 Binder Stub实际 IPC 端点是CompanionDeviceManagerService的内部类CompanionDeviceManagerImpl。该类继承ICompanionDeviceManager.Stub,将每一次 Binder 调用分发到对应处理器。 以associate()调用为例:校验调用方身份与权限。委派给AssociationRequestsProcessor.processNewAssociationRequest()。同理,disassociate()转发至DisassociationProcessor.disassociate()。该服务还通过CompanionDeviceManagerServiceInternal暴露内部 API,其他系统服务通过LocalServices获取:frameworks/base/services/companion/java/com/android/server/companion/CompanionDeviceManagerServiceInternal.java52.1.5 Shell 命令接口为调试与测试,CDM 提供 shell 命令,实现文件:frameworks/base/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java支持操作示例:adb shell cmd companiondevice list 0 adb shell cmd companiondevice associate --userId 0 --package com.example.app \ --mac AA:BB:CC:DD:EE:FF adb shell cmd companiondevice disassociate 0 com.example.app AA:BB:CC:DD:EE:FF52.2 设备关联与设备发现52.2.1 关联数据模型每一条配套设备关系由AssociationInfo对象表示。AssociationInfo.Builder展示全部字段(来自AssociationRequestsProcessor.createAssociation()):final AssociationInfo association = new AssociationInfo.Builder(id, userId, packageName) .setDeviceMacAddress(macAddress) .setDisplayName(displayName) .setDeviceProfile(deviceProfile) .setAssociatedDevice(associatedDevice) .setSelfManaged(selfManaged) .setNotifyOnDeviceNearby(false) .setRevoked(false) .setPending(false) .setTimeApproved(timestamp) .setLastTimeConnected(Long.MAX_VALUE) .setSystemDataSyncFlags(0) .setTransportFlags(transportFlags) .setDeviceIcon(deviceIcon) .setDeviceId(null) .setPackagesToNotify(null) .setMetadata(new PersistableBundle()) .setExtraPermissions(extraPermissions) .setRemoteAiAgentSupported(isRemoteAiAgentSupported) .build();源码位置:frameworks/base/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java,335‑355 行。最后两个 setter 为 Android 17 新增:setExtraPermissions()保存与该关联绑定的一组可选权限;setRemoteAiAgentSupported()记录配套设备是否可以承载远程 AI 代理(用于 52.8 节计算机控制流程),该值来源于AssociationRequest.isRemoteAiAgentSupported()。关键字段说明:字段用途id自增的唯一整型标识符userId该关联归属的 Android 用户packageName配套应用包名deviceMacAddress用于硬件层面在场检测的 MAC 地址displayName配套设备可读显示名称deviceProfile基于角色的配置文件(手表、眼镜、应用投屏等)selfManaged为 true 代表应用自行管理传输通道,不做 MAC 监控revoked为 true 代表关联等待最终清理systemDataSyncFlags比特掩码,控制哪些系统数据参与同步transportFlags控制传输通道行为的标志位deviceId可选 DeviceId,支持自定义 ID 与 MACextraPermissionsAndroid17:与设备绑定的额外权限集合remoteAiAgentSupportedAndroid17:配套设备是否支持远程 AI 代理52.2.2 设备配置文件设备配置文件决定配套应用被授予的权限与角色。需要用户确认的配置文件集合定义在AssociationRequestsProcessor:private static final SetString DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION = new ArraySet( Arrays.asList( AssociationRequest.DEVICE_PROFILE_APP_STREAMING, AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING));源码位置:frameworks/base/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java,144‑147 行。完整设备配置文件集合:DEVICE_PROFILE_WATCH:智能手表配套设备DEVICE_PROFILE_GLASSES:AR/VR 眼镜DEVICE_PROFILE_APP_STREAMING:远程显示 / 应用投屏DEVICE_PROFILE_NEARBY_DEVICE_STREAMING:邻近设备投屏DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:车载主机投屏DEVICE_PROFILE_COMPUTER:桌面 / 笔记本配套设备DEVICE_PROFILE_WEARABLE_SENSING:可穿戴健康与传感器设备DEVICE_PROFILE_VIRTUAL_DEVICE:受限虚拟设备角色(android.app.role.COMPANION_DEVICE_VIRTUAL_DEVICE)DEVICE_PROFILE_FITNESS_TRACKER:健身手环配套设备(标志位FLAG_BAND_DEVICE_PROFILE)DEVICE_PROFILE_MEDICAL:医疗设备配套设备(标志位FLAG_ENABLE_MEDICAL_PROFILE)最后两项为标志位管控,Android16 与 Android17 均存在,不属于 Android17 新增。定义于frameworks/base/core/java/android/companion/AssociationRequest.java,由@FlaggedApi注解指向frameworks/base/core/java/android/companion/flags.aconfig内的 aconfig 标志。DEVICE_PROFILE_FITNESS_TRACKER映射角色字符串android.app.role.COMPANION_DEVICE_FITNESS_TRACKER;DEVICE_PROFILE_MEDICAL映射android.app.role.COMPANION_DEVICE_MEDICAL。每一类配置文件对应 Android 角色。创建关联时,如果配套应用尚未拥有该角色,则自动授予:addRoleHolderForAssociation(mContext, association, success - { if (success) { Slog.i(TAG, "Added " + deviceProfile + " role to userId=" + association.getUserId() + ", packageName=" + association.getPackageName()); mAssociationStore.addAssociation(association); sendCallbackAndFinish(association, callback, resultReceiver); } else { Slog.e(TAG, "Failed to add u" + association.getUserId() + "\\" + association.getPackageName() + " to the list of " + deviceProfile + " holders."); sendCallbackAndFinish(null, callback, resultReceiver); } });源码位置:AssociationRequestsProcessor.java,390‑403 行。各个配置文件到权限的映射关系位于frameworks/base/services/companion/java/com/android/server/companion/utils/RolesUtils.java。Android17 的两个配置文件做了差异化处理:DEVICE_PROFILE_FITNESS_TRACKER是角色别名,ROLE_ALIASES将其指向DEVICE_PROFILE_WATCH,健身手环复用手表角色与权限集合(通知、电话、通话记录、短信、联系人、日历、邻近设备、媒体输出),不再定义独立角色。DEVICE_PROFILE_MEDICAL使用独立角色,PROFILE_PERMISSION_SETS中权限范围更窄:发送通知、邻近设备、设置精确闹钟、免打扰绕过。体现医疗配套设备需要发送高优先级告警,但不需要手表那样完整的消息访问权限。52.2.3 关联建立流程关联流程分为两种:带 UI 完整流程、无 UI 流程(用于自管理关联)。AssociationRequestsProcessor的 Javadoc 对两种流程做了解释。processNewAssociationRequest()中完整流程实现片段:public void processNewAssociationRequest(@NonNull AssociationRequest request, @NonNull String packageName, @UserIdInt int userId, @NonNull IAssociationRequestCallback callback) { // 1. Enforce permissions and other requirements. enforcePermissionForCreatingAssociation(mContext, request, packageUid); enforceUsesCompanionDeviceFeature(mContext, userId, packageName); // 2a. Check if association can be created without launching UI if (request.isSelfManaged() !request.isForceConfirmation() !DEVICE_PROFILES_WITH_REQUIRED_CONFIRMATION.contains(request.getDeviceProfile()) !willAddRoleHolder(request, packageName, userId)) { createAssociationAndNotifyApplication(request, packageName, userId, /* macAddress */ null, callback, /* resultReceiver */ null); return; } // ... // 2b. Build a PendingIntent for launching the confirmation UI request.setSkipPrompt(mayAssociateWithoutPrompt(packageName, userId)); // ... }源码位置:AssociationRequestsProcessor.java,171‑249 行。权限校验工具函数enforcePermissionForCreatingAssociation、enforceUsesCompanionDeviceFeature从com.android.server.companion.utils.PermissionsUtils与PackageUtils静态导入,是 Android17 完成的代码重构。52.2.4 频率限制无 UI 关联路径内置频率限制,防止滥用:private static final int ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW = 5; private static final long ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS = 60 * 60 * 1000; // 60 minmayAssociateWithoutPrompt()统计该包过去 60 分钟创建的关联数量。超过 5 条则强制弹出确认弹窗:if (++recent = ASSOCIATE_WITHOUT_PROMPT_MAX_PER_TIME_WINDOW) { Slog.w(TAG, "Too many associations: " + packageName + " already " + "associated " + recent + " devices within the last " + ASSOCIATE_WITHOUT_PROMPT_WINDOW_MS + "ms"); return false; }源码位置:AssociationRequestsProcessor.java,534‑555 行;常量定义在 140‑141 行。52.2.5 AssociationStore—— 持久化与变更通知AssociationStore是关联记录的核心增删改查接口。维护内存缓存mIdToAssociationMap,由AssociationDiskStore做磁盘持久化。文件路径:frameworks/base/services/companion/java/com/android/server/companion/association/AssociationStore.javaAssociationDiskStore.javaAssociations.java存储支持两类变更监听器:本地监听器(OnChangeListener):服务端其他组件使用,如DevicePresenceProcessor、TransportManager。远程监听器(IOnAssociationsChangedListener):应用通过 Binder 注册使用。变更类型枚举:public static final int CHANGE_TYPE_ADDED = 0; public static final int CHANGE_TYPE_REMOVED = 1; public static final int CHANGE_TYPE_UPDATED_ADDRESS_CHANGED = 2; public static final int CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED = 3; public static final int CHANGE_TYPE_UPDATED_DATA_SYNC_TYPES = 4;源码位置:AssociationStore.java,77‑81 行。CHANGE_TYPE_UPDATED_DATA_SYNC_TYPES为 Android17 新增,当单条关联的系统数据同步标志发生变化时触发(参见 52.3.6 节DataSyncProcessor)。通知逻辑区分是否修改设备地址。远程监听器只会收到重大变更(新增、删除、地址变更),配置微调不会推送:// Do NOT notify when UPDATED_ADDRESS_UNCHANGED, which means a minor tweak in // association's configs, which "listeners" won't (and shouldn't) be able to see. if (changeType != CHANGE_TYPE_UPDATED_ADDRESS_UNCHANGED) { mRemoteListeners.broadcast((listener, callbackUserId) - { ... }); }源码位置:AssociationStore.java,601‑608 行。写操作投递至单线程执行器,避免阻塞调用方:private void writeCacheToDisk(@UserIdInt int userId) { mExecutor.execute(() - { Associations associations = new Associations(); synchronized (mLock) { associations.setMaxId(mMaxId); associations.setAssociations( CollectionUtils.filter(mIdToAssociationMap.values().stream().toList(), a - a.getUserId() == userId)); } mDiskStore.writeAssociationsForUser(userId, associations); }); }源码位置:AssociationStore.java,325‑336 行。52.2.6 解除关联DisassociationProcessor同时处理应用 API 发起的解除关联,以及空闲自管理关联的自动清理。文件路径:frameworks/base/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java解除关联原因常量,用于调试:public static final String REASON_REVOKED = "revoked"; public static final String REASON_SELF_IDLE = "self-idle"; public static final String REASON_SHELL = "shell"; public static final String REASON_LEGACY = "legacy"; public static final String REASON_API = "api"; public static final String REASON_PKG_DATA_CLEARED = "pkg-data-cleared";源码位置:DisassociationProcessor.java,71‑76 行。重要设计:如果触发解除关联时配套应用进程处于前台,实际删除操作会延后。关联标记为revoked,注册OnUidImportanceListener,进程退到后台之后才完成清理:if (packageProcessImportance = IMPORTANCE_FOREGROUND deviceProfile != null !isRoleInUseByOtherAssociations) { AssociationInfo revokedAssociation = (new AssociationInfo.Builder( association)).setRevoked(true).build(); mAssociationStore.updateAssociation(revokedAssociation); startListening(); return; }源码位置:DisassociationProcessor.java,160‑174 行。自管理关联连续 90 天无活动会被自动移除:private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);源码位置:DisassociationProcessor.java,第 82 行。InactiveAssociationsRemovalService是一个JobService,周期性调用removeIdleSelfManagedAssociations()清理过期条目。52.2.7 设备在场监控DevicePresenceProcessor跟踪配套设备是否在附近或者已连接。文件路径:frameworks/base/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.javaBleDeviceProcessor.javaBluetoothDeviceProcessor.javaCompanionAppBinder.javaCompanionServiceConnector.javaObservableUuid.javaObservableUuidStore.java处理器处理多种在场事件:EVENT_BLE_APPEAREDEVENT_BLE_DISAPPEAREDEVENT_BT_CONNECTEDEVENT_BT_DISCONNECTEDEVENT_SELF_MANAGED_APPEAREDEVENT_SELF_MANAGED_DISAPPEAREDEVENT_SELF_MANAGED_NEARBYEVENT_SELF_MANAGED_NOT_NEARBYEVENT_ASSOCIATION_REMOVED当 BLE 扫描或者蓝牙连接检测到配套设备出现,DevicePresenceProcessor会尝试绑定配套应用的CompanionDeviceService。绑定逻辑由CompanionAppBinder与CompanionServiceConnector管理,处理设备在场状态变化过程中服务连接的生命周期。52.3 数据传输与上下文同步52.3.1 传输架构传输子系统提供本地 Android 设备和配套设备之间双向消息通道。分层架构文件路径:frameworks/base/services/companion/java/com/android/server/companion/transport/Transport.java—— 抽象基类RawTransport.java—— 非加密传输SecureTransport.java——UKEY2 加密传输CompanionTransportManager.java—— 生命周期管理器CryptoManager.java—— 密码学工具类
返回列表