Repository navigation
[RFC] 通用输入源框架:统一 CarPlay、Android Auto、ICCOA、Android 桌面与 VNC 接入 #28
Description
背景与目标
本 Issue 立项时 WheelPlay 仅支持 CarPlay,浏览器触控、后台会话、媒体接口及启动条件与 CarPlay 耦合。目标是抽象视频、触控及控制流,并保留现有音频/麦克风能力,为 Android Auto、ICCOA CarLink、Android 虚拟桌面和 VNC 提供统一接入框架。当前已合入公共框架、CarPlay 兼容适配、实验性 Android Auto/VNC/Shizuku 本机桌面,以及独立 APK 插件框架;ICCOA 协议仍未实现。具体完成度见下方“主线进展”。
本 Issue 作为技术设计与实施跟踪入口。目标是先在不退化现有 CarPlay 体验的前提下建立协议边界,再分阶段加入新输入源。
核心决策:
- 使用协议无关的
ProjectionSourceProvider/ProjectionSession抽象,分离协议适配、会话运行时、媒体管线及浏览器输出。 - 支持压缩访问单元、Surface producer、framebuffer 三种视频入口,保留兼容视频的 WebRTC 直通。
- 按能力声明和后端协商开放输入、控制及媒体路由;仅声明实际实现的能力,通用音频/麦克风 broker 和完整文本等扩展仍待完善。
- 首版一个活动源、一个浏览器控制者,统一管理资源租约与切源代次。
- ICCOA 先验证外部 SDK 的可获得性、离屏输出和交互能力,不将公开包装代码等同于完整协议实现。
已确认的实施优先级(2026-10-21)
优先在现有 Kotlin/C++ 技术栈内建立多输入源框架,先验证协议边界与真实接入,再评估逐步迁移 Rust。Rust 后端重构与普通 Linux 宿主暂不作为本阶段交付前提。
实施顺序:
- 包装现有 CarPlay,实现通用会话、输入和媒体接口,解除 Web 层对具体 CarPlay 控制器的依赖,保留现有协议兼容性及直通/回退行为。
- 使用模拟源验证压缩帧、Surface 和 framebuffer 三类入口,以及背压、切源、输入取消和资源释放。
- 将 Android Auto 作为第二个真实输入源,优先利用 DiAuto 的 Kotlin 实现验证通用接口,完成视频、触控、音频与焦点闭环。
- ICCOA 的 SDK 可行性验证可提前进行;正式接入依据验证结果推进,不阻塞公共框架和 AA 接入。
- 在第二个真实源能够通过公共接口接入、无需在公共层大量增加协议分支后,再选择稳定边界逐步迁移 Rust。
为后续跨平台迁移保留的约束:
- 公共数据模型不携带 AirPlay、AAP protobuf 或 ICCOA SDK 类型。
- 核心接口不依赖 Activity/Context;Surface 等放在 Android 专用接口中。
- 明确缓冲区所有权、线程、背压、取消与停止完成语义。
- 协议适配器与浏览器输出互不依赖。
- 保留协议样本和契约测试,供未来 Rust 实现复用。
本节确认的是技术路线与优先级;下方阶段任务仍按实际实现和验证情况勾选。
主线进展(2026-10-27)
核对主线:ccd01f0。#33 建立公共框架与 CarPlay 兼容适配,#39/#51 接入并修正 Android Auto,#43 接入 VNC,#50 提供独立 APK 插件框架,#52 接入 Shizuku 本机桌面并扩展公共生命周期契约。本 Issue 保持开放:代码交付、自动化验证和目标设备验收分别记录,不将实验性接入或 PR 合并等同于完整验收。
| 方向 | 当前主线状态 | 验证边界 / 剩余工作 |
|---|---|---|
| 公共框架 | provider/session、单活动源、资源占用、三类视频入口、控制租约与旧会话隔离;公共 Pointer/Scroll/Key/Back 能力、独立取消、状态/错误及启动取消机制已落地 | 设备性能基线、长期运行和慢网络验收待补;通用音频/麦克风 broker、完整 Text/IME/Rotary 和细粒度资源预算尚未完成 |
| CarPlay | 兼容适配保留发现、认证、协议和音频路径;宿主启动不依赖 MFi 就绪;取消语义为 RELEASE_ONLY | #33 合并前用户确认人工测试通过;缺少设备矩阵和量化性能对比,不能据此宣称所有场景均无退化 |
| 三类诊断源 | encoded / Surface / framebuffer 已实现;有序增量/CopyRect 合成,编码源复用直通/回退 | 自动化已覆盖多项所有权、切源与清理场景;真实车机和性能待验收。Surface/framebuffer 当前输出 JPEG/WebP,无 WebRTC 重编码 |
| Android Auto | 实验性 AAP 本机 Head Unit Server、USB AOA、显式 TCP;H.264、触控、夜间模式、宿主 PCM 和可选本机麦克风;原生 ACTION_CANCEL | #51 修复 Annex-B 图像回退及录音权限流程。真实手机/USB、焦点/语音助手、拔插和跨源切换仍待验收;无浏览器音频/麦克风、自动无线发现/配对或自动重连 |
| VNC | #43 已实现 RFB 3.3/3.7/3.8、传统认证、Raw/CopyRect/Hextile/DesktopSize、framebuffer、Pointer/Scroll/Key 和只读模式;取消为 RELEASE_ONLY | 实际服务端/车机及性能待验收;无 TLS/VeNCrypt、音频、剪贴板、完整 IME、真正多点触控或自动重连。它连接已有服务端,不创建本机桌面 |
| Android 虚拟桌面 | #52 已实现 Shizuku UserService、本机应用选择、启动尺寸/DPI 设置、Surface→JPEG/WebP、多指/Back、原生取消、显示/会话状态与清理机制 | #40 跟踪验收;尚无非 root 真机 P0 结果。Android 10+/Shizuku 13+ 是代码门槛,不是已验证支持范围;无 Launcher/Home、动态布局、浏览器音频/麦克风或直接 ADB/VDM 后端 |
| 独立 APK 插件 | #50 提供 API v2 SDK/示例、发现和用户启用、独立 UID/进程、Surface/触控/夜间模式、停止确认及可选专用进程退出;#52 增加可选原生取消 | 自动化含跨进程回归;OEM/设备和性能待验收。插件端口尚不支持音频/麦克风、键盘/鼠标、编码帧/framebuffer IPC 或多显示器 |
| ICCOA CarLink | 宿主已有插件关联、设置、启用和连接入口;正式适配采用独立插件 APK | 没有 ICCOA 协议实现。仍需适用的专有 SDK、真实 Surface/输入/音频能力验证和网络/服务清理验收;示例插件不能代替 CarLink |
| Rust / 普通 Linux 宿主 | 继续使用 Kotlin/C++,保留迁移边界 | 后续评估,不作为本阶段交付前提 |
当前公共契约(#43 / #50 / #52)
- 接入边界:内置源保留应用内 provider/session;插件通过显式 Service 绑定、Binder 控制和 Surface 句柄接入,宿主不加载插件 dex/native,也不通过 Binder 逐帧搬运图像。Shizuku 辅助进程与插件 APK 是不同的后端模型,公共接口不携带它们的协议类型。
- 启动与就绪:实际 API 使用
CompletableFuture。open()完成表示运行时已接纳会话并调用activate(),awaitReady(session)单独等待 RUNNING。ProjectionStartup是协作取消信号;调用runtime.stop()请求取消并等待清理,取消信号或 future.cancel 本身不证明资源已释放。已加入晚到会话清理、获取/就绪默认各 60 秒及取消/停止清理默认 15 秒机制,失败/超时保留资源隔离;这些是实现机制,不替代竞态及设备验收。 - 状态与错误:
ProjectionSessionState描述阶段、有效能力、输入就绪和错误;stateSnapshot()/subscribeState()提供ProjectionSnapshot,另含清理状态并保留终止原因。阶段为 CONNECTING / NEGOTIATING / RUNNING / STOPPING / STOPPED / FAILED,清理为 NONE / PENDING / RELEASED / UNCONFIRMED;只读源也可 RUNNING。错误按公共类别处理,backend/backendCode和独立diagnostic保留后端信息。UI 本地化展示,浏览器不接收原始诊断。原设计的 StateFlow、完整轨道/配对快照及通用 ControlPort 并未原样落地。 - 取消语义:
cancelInput(reason)用于失焦、断连、租约失效、布局变化及切源;touch(emptyList())仍是普通抬手。AA/Desktop 声明 NATIVE,CarPlay/VNC 为 RELEASE_ONLY,后者不能保证没有点击副作用。插件 API v2 的nativeCancelInput缺省 false;只有声明支持时才调用末尾追加的取消事务,旧插件继续收到兼容释放。 - 能力与接纳结果:已提供触控上限、Pointer/Scroll/Key、独立 Back、取消语义及本机/浏览器媒体路由标志。Back 不代表完整键盘支持,本机播放也不代表浏览器音频。输入 Boolean 只表示非阻塞接纳;当前
ProjectionCommandResult为 SENT / UNSUPPORTED / UNAVAILABLE / FAILED,不承诺远端已执行。Desktop v7 的服务支持→连接协商→会话最终启用是该后端的分层协议,尚非所有源统一的握手格式。 - 浏览器与布局:版本化输入使用
sessionId/videoEpoch校验,配置增加sessionPhase/sourceError及 Back 能力。当前会话/代次的取消可在画面或输入暂停时提交,仍校验租约所有者。Desktop 的layoutRevision/inputEpoch位于其 AIDL 边界,不与浏览器字段混同;旧浏览器保留兼容释放,切源仍需重新连接观看。 - 资源与媒体边界:公共资源类型目前为 USB / WIRELESS / MICROPHONE / DISPLAY,是单活动源下的粗粒度协调;尚未实现原设计的独立 BLE/VPN、音频焦点和 codec 预算仲裁。Surface/framebuffer 重编码、通用音频/麦克风 broker、Text/IME/Rotary 仍属后续工作。
#39 阶段记录(2026-10-23,保留历史)
- 底部中间 Tab 改为“输入源”,首页依次展示 CarPlay (iPhone)、Android Auto、ICCOA CarLink 和诊断源大卡片;具体连接/设置在二级页面,页面导航本身不切源。
- AAP 版本交换、TLS 1.2、服务发现、通道管理、焦点及退出;H.264 1280×720 / 30 fps、逐片 ACK、有界媒体重组、公共视频输出和最多十点触控。参考 DiAuto 协议资料实现独立适配,未把整个上游 App 当作依赖。
- 本机入口固定
127.0.0.1:5277,提供首次引导及设置跳转;用户仍需手动开启 AA 开发者模式和 Head Unit Server。远程 TCP 需要实际可访问的服务器,不等于自动无线发现/配对。 - 音频在 WheelPlay 宿主播放;仅在已授权并选择 Android 麦克风时声明录音能力。AA 浏览器音频/麦克风尚未实现,也不包含 Now Playing、HFP、动态分辨率、HEVC 或自动重连。
- Review 的三项修复已随 feat: 接入实验性 Android Auto 本机/USB/TCP 协议源 #39 合入:保留并重试未写完的 PCM;关键帧通知无效时执行有预算和超时的焦点恢复,仍失败则提示重连;修复 USB 授权等待注册与停止之间的竞态。回归覆盖取消、错误、恢复限频及媒体 stop/start 后继续输出。
验证证据(按提交保留,不合并为当前测试总数)
-
feat: 实现多输入源框架、CarPlay 适配与三类诊断源 #33:用户确认人工测试通过后合并;该确认不替代缺失的设备清单和量化指标。
-
feat: 接入实验性 Android Auto 本机/USB/TCP 协议源 #39 合并前修复提交
e0e5121:shared 445 项、common 370 项,共 815 项 Android 单元测试通过,零失败、零跳过;Android Auto 专项 18 项包含在上述总数内。 -
Web 单元测试 167 项通过;Debug APK、instrumentation 测试 APK、lint 均通过,lint 无 Error/Fatal,20 项 Warning。
-
GitHub Actions #97 全部通过:构建检查及 Android 28 / 36 浏览器 HTTP 运行验证。该结果覆盖 feat: 接入实验性 Android Auto 本机/USB/TCP 协议源 #39 阶段,也补上了 feat: 实现多输入源框架、CarPlay 适配与三类诊断源 #33 当时未取得结果的 Android 28 浏览器运行验证;它不是 AA 手机协议的真机验收。
-
真实 SSLEngine + socket 模拟手机测试覆盖握手、服务发现、视频分片 ACK、触控、PCM、焦点切换、媒体重启、视频恢复和退出清理。
-
feat: 支持独立 APK 输入源插件,为 CarLink 独立接入提供 SDK #50 阶段:SDK 8、shared 478、common 422,共 908 项单元测试通过;CI #130 含 Android 28/36 运行及插件跨进程验证。详见既有进展评论。
-
feat: add Shizuku desktop and shared projection lifecycle contracts #52 阶段:提交
80ac0f6的 Android Debug Build #156 通过;PR 记录了 structured Parcelable/Binder、能力分层、取消屏障、状态/启动取消及旧插件兼容等自动化验证,另有浏览器测试、lint 和 Debug 构建。Android 桌面尚未完成非 root 真机验证,不能用这些结果勾选 [RFC] Shizuku/ADB 本机 Android 虚拟桌面输入源方案 #40 的 P0/P2 验收。 -
以上为各提交已经取得的证据,本次 Issue 同步未重新运行测试;模拟器/模拟协议端不等于目标手机、车机或 OEM 兼容性验收。
当前实现说明(固定至本次核对提交):公共框架、Android Auto、VNC、插件 API、Shizuku 桌面。
实施跟踪
以下将代码交付与设备验收拆开勾选,避免把整个 P0–P5 计划误标为完成。
-
P0 代码基础:中立模型、provider/session、输入/视频契约与资源所有权(feat: 实现多输入源框架、CarPlay 适配与三类诊断源 #33)。
-
P0 设备基线:记录同机同网的 CarPlay 帧率、延迟、恢复时间、CPU/内存和耗电对比。
-
P1 代码与首轮人工回归:CarPlay 兼容适配、WebSession 解耦和认证门控调整;用户确认人工测试通过后合并(feat: 实现多输入源框架、CarPlay 适配与三类诊断源 #33)。
-
P2 代码与自动化:三类诊断视频源、切源隔离、背压/输入取消及资源释放契约测试(feat: 实现多输入源框架、CarPlay 适配与三类诊断源 #33)。
-
P1/P2 设备验收记录:补齐有线/无线 CarPlay、音频/麦克风、慢网络/图像回退、真实车机三类源和至少 30 次切源的逐项结果与性能数据。
-
P3 实验性协议代码:Android Auto 本机/USB/TCP、视频/触控、宿主音频/麦克风、焦点与异常恢复(feat: 接入实验性 Android Auto 本机/USB/TCP 协议源 #39)。
-
P3 目标设备验收:USB 首次授权、真实投影与触控、媒体/导航声音、语音助手、本机 Head Unit Server、重启/拔插、CarPlay/AA/诊断源切换及慢浏览器恢复;记录设备和 AA 版本。
-
插件基础设施:独立 APK 输入源、API v2 SDK/示例、启用授权、停止确认与进程清理、ICCOA 卡片关联及首页输入源选择(feat: 支持独立 APK 输入源插件,为 CarLink 独立接入提供 SDK #50)。
-
公共契约增强:独立取消、原生/释放型能力、阶段/错误/清理状态、启动取消与超时机制,以及旧插件取消兼容(feat: add Shizuku desktop and shared projection lifecycle contracts #52)。
-
插件目标设备验收:后台/锁屏、更新/卸载/强停、OEM 服务限制、至少 30 次跨源切换及资源/性能记录。
-
P4:取得适用 ICCOA SDK,完成独立插件 APK 的可行性验证与真实适配,明确不支持的能力并完成设备验收。
-
P5 Android 桌面代码:Shizuku UserService、本机应用选择、启动尺寸/DPI、图像输出、多指/Back及生命周期机制(feat: add Shizuku desktop and shared projection lifecycle contracts #52)。
-
P5 Android 桌面设备验收:非 root shell 模式 P0 闭环、主屏隔离、跨屏任务、异常清理、升级重启及性能记录([RFC] Shizuku/ADB 本机 Android 虚拟桌面输入源方案 #40);直接 ADB/VDM 是可选后续方向,不作为 Shizuku 首版前提。
-
P5 VNC 代码与自动化:独立 RFB 适配器、framebuffer 输出、Pointer/Key、只读模式及输入隔离(feat: add VNC input source with desktop controls #43)。
-
P5 VNC 设备验收:实际服务端与 Android/车机浏览器的连接、画面、交互、只读、resize、断线重连、跨源切换及性能记录。
-
公共扩展:按实际需求完善音频/麦克风 broker、插件媒体端口、Surface/framebuffer→WebRTC 编码、完整 Text/IME/Rotary 与更细的资源预算;不将设计接口当作已交付能力。
下一步优先完成 AA 真机验收、#40 的非 root P0、VNC/插件验收及 CarPlay/公共框架性能与跨源切换记录。ICCOA 仍待适用 SDK;桌面专项按 #40 进展评论 跟踪。Rust/Linux 维持后续评估顺序。
以下保留原始技术方案及第 9 节阶段验收目标;接口示例与规划能力不代表当前主线已全部实现。
以下为 2026-10-21 的研究与设计基线,保留用于追溯;当前实现与验证状态以本 Issue 上方 2026-10-27 的进展及关联 PR 为准。下文保留原始设计;各节的实施注记说明与当前代码的差异。
研究基线:WheelPlay 4ed3536822998babd086e4fcb1a384e791b0e2d4;DiAuto d53dad71e02e70340d968896140b1c601b38b917;MiAuto 059b2bb6b4d7fd3b506e7c29bbf153a8e38a0c4d。本次检查了源码和项目文档,没有构建外部项目或进行协议真机验证。下文区分已观察到的实现、设计建议和待验证能力。
1. 建议与范围
2026-10-27 实施注记:本节“不引入动态插件加载”是首版研究范围。当前已支持独立 APK 的 Binder/Surface 接入,仍不向宿主加载插件 dex/native,不进行逐帧图像 Binder IPC;详见上方当前公共契约。
将 WheelPlay 定义为双向投影宿主:输入源提供画面、音频和状态,宿主返回触控、键盘、麦克风和控制请求。内部建议命名 ProjectionSource,避免与触摸输入设备的 InputSource 混淆。
采用「协议适配器 → 会话运行时 → 媒体与交互路由 → 输出端」架构。协议适配器拥有发现、认证、传输、协议状态机;运行时拥有活动源、资源仲裁、生命周期;媒体管线拥有共享编解码和输出选择;浏览器只依赖通用会话接口。
首版保持一个活动投影源、一个浏览器控制者。允许多个源被注册,不承诺多个无线协议同时广播或连接。先采用应用内模块和显式注册,不引入动态插件加载、跨进程帧 IPC 或全能协议总线。
2. 研究发现与架构约束
WheelPlay
当前基础能力已经适合复用,但接口仍携带 CarPlay 语义:
| 证据位置 | 观察 | 设计影响 |
|---|---|---|
common/.../web/WebSession.kt,TouchLease.kt |
触控直接查找 CarPlayBackgroundSession;租约输入为 AirPlayContact,固定两个槽位 |
租约与协议编码分离;浏览器路由只捕获通用活动会话 |
shared/.../airplay/CarPlayMediaEngine.kt 的 MediaSink |
同一接口含视频、RTP 音频、麦克风、iAP 消息、解密器创建 | 保留为 CarPlay 内部接口,外面增加适配层,不直接改名作为通用接口 |
shared/.../media/EncodedVideoSink.kt,CompressedVideoFrame.kt |
已有压缩帧与 retain/close 约定;仍依赖 airplay.VideoCodec、整数 type 和解密密钥 |
提取中立媒体模型;保留缓冲区生命周期;解密服务留在 CarPlay 边界 |
| common/.../web/WebVideoSource.kt | 可配置 H.264/H.265 直通、请求关键帧,还创建 NativeVideoDecoder(key) |
拆开源侧解密、通用视频格式、浏览器输出协商 |
| shared/.../media/AndroidMediaSink.kt | WebRTC 已显示后可关闭本地解码;回退等待随机访问帧 | 保留该优化与恢复语义,不统一强制解码再编码 |
| shared/.../web/OffscreenVideoOutput.kt | Activity 无关的 SurfaceTexture/EGL/JPEG 输出 | 可演进为共享离屏画面入口;当前没有通用 Surface→WebRTC 编码链 |
common/.../CarPlayHostActivity.kt 中 CarPlayBackgroundSession |
快照暴露 controller、sink、尺寸;已有所有者与停止完成回调 | 先用兼容适配器包装,逐步迁到通用运行时 |
| common/.../DiPlaySessionService.kt | DiPlayBootstrap.ensure 失败会停止 Web 服务和 CarPlay 会话 |
将 CarPlay 认证就绪检查下沉到 CarPlay provider;宿主/Web 自身检查独立保留 |
| shared/.../media/CarPlayMediaSource.kt | 实际是 Now Playing 和媒体命令接口,并非视频源 | 可演进成可选 MediaControlPort,不要把它当整个投影抽象 |
上述相对路径中的 ... 均为 src/main/java/com/shilapi/xcertplay。
DiAuto:适合提取协议栈,不适合整个应用作为库接入
项目提供 USB/无线 AA 接收,README 的已验证环境是 BYD DiLink 5.1 / Android 13。不能据此推定普通 Android 服务手机的网络能力和后台行为已经验证。
AapVideo.process()在分片组装完成后直接调用VideoDecoder.decode(...):这是引入EncodedVideoIngress的合适接缝,但须把配置参数、时间戳和完整访问单元整理清楚,不能把任意网络分片直接作为帧交给下游。AapMessageHandlerType在视频/音频处理后发送协议 ACK。AapControl的maxUnacked是消息窗口,不能把它误当完整帧数量,也不能将 ACK 绑定浏览器显示完成。TouchEvent明确包含 pointer action、actionIndex、pointerId 和坐标。现有 CarPlay 两槽位快照不能无条件当作 AA 事件使用。AapTransport.hasVideoSurface()和gainVideoFocus()与本地解码器、Activity 广播耦合。抽取时须改为投影输出就绪通知和语义焦点请求,否则纯压缩帧直通可能因没有本地 Surface 而无法启动。- 主屏与仪表视频有不同 channel;音频、媒体状态、导航、传感器也是独立通道。因此从第一版就保留
streamId/displayId,UI 可以只开放主屏。
MiAuto:公开部分是 SDK 集成层,不能视为完整开源 ICCOA 协议栈
app/libs/README.md明确说明icar-sdk.dex是不随仓库提供的第三方专有运行库;仓库仅有编译期签名存根。缺运行库也能构建成功,CI APK 没有 CarLink 能力。IccoaManager通过UCarAdapter.startCast(surface, w, h)接入画面;公开桥接层没有压缩视频帧回调。IcocaProjectionActivity的注释对解码职责表述不够清楚,应以实际 API 边界为准:宿主交入 Surface,内部处理细节待 SDK 验证。IcocaBridge提供连接状态、PIN、导航与 Surface 绑定;所检查的 Activity/bridge/manager 没有形成可供 WheelPlay 使用的触控回传链路,不能宣称已验证交互完整。- 配置中有
setUseCustomAudioRecord(false),音乐、通话、音频焦点回调也有空实现。音频截取、麦克风注入与控制 API 需要单独确认,不能默认为可走浏览器路由。 - 集成文档明确禁用了继承自 AA 的 P2P/SoftAP 等副作用以避免争抢无线资源。框架不能仅靠“当前选择哪个源”来隔离资源。
- README 仅报告 Android 11 / arm64 Newland 设备完成一次 CarWith 会话;其他品牌手机、量产车机、仪表/HUD 均不能按成熟支持处理。
- 文档列出的特权权限并不意味着普通应用声明后就会获得;应在目标服务手机上检测实际权限和网络能力。
3. 分层与依赖方向
以下按 2026-10-27 主线 ccd01f0 展示单一架构拓扑:所有适配器集中在左侧,插件与 Shizuku 辅助进程直接连接各自的宿主适配器,画面统一汇入同一个公共视频管线,控制统一经过同一个运行时与浏览器入口。每个组件只出现一次,不再用上下两幅视图重复表示同一组件。
本地 Mermaid 11 渲染约 1502×932,宽高比约 1.61:1,处于 4:3–16:9 范围内;GitHub 字体及渲染器版本可能使尺寸略有变化。
%%{init: {"flowchart": {"nodeSpacing": 70, "rankSpacing": 25, "padding": 12, "subGraphTitleMargin": {"top": 8, "bottom": 12}}}}%%
flowchart LR
subgraph ADAPTERS["输入源适配器 · 宿主"]
PORT["统一会话契约<br/>ProjectionSession"]
CPAA["CarPlay<br/>Android Auto"]
VNC["VNC"]
DIAG["诊断源"]
DESKTOP["Android 桌面<br/>ShizukuDesktop<br/>Backend"]
REMOTE["插件适配器<br/>RemotePlugin<br/>Session<br/>发现 / 用户启用门控"]
end
subgraph SHIZUKU["Shizuku · shell/root"]
HELPER["Desktop UserService<br/>虚拟屏 / 输入注入"]
end
subgraph PLUGIN["独立 APK · 自有 UID"]
SERVICE["ProjectionPlugin<br/>Service<br/>SDK / 画面 producer"]
ICCOA["CarLink 插件<br/>待实现"]
SERVICE -.-> ICCOA
end
RT["ProjectionRuntime<br/>生命周期 / 资源占用"]
MP["公共视频管线<br/>创建 Surface<br/>消费画面"]
OUT["浏览器<br/>视频输出"]
INPUT["WebSession<br/>控制租约"]
WEB["车机浏览器"]
PORT <-->|"生命周期 / 控制<br/>状态"| RT
CPAA -->|"编码视频"| MP
VNC -->|"framebuffer"| MP
DIAG -->|"三类入口"| MP
DESKTOP <-->|"Desktop v7 AIDL<br/>配置 / Surface / 输入<br/>状态查询 / 清理结果"| HELPER
REMOTE <-->|"API v2 AIDL<br/>start / Surface / 输入<br/>状态回调 / 停止确认"| SERVICE
HELPER -->|"虚拟屏写入 Surface"| MP
SERVICE -->|"producer 写入 Surface"| MP
MP -->|"直通 / 图像"| OUT
OUT -->|"WebRTC<br/>JPEG / WebP"| WEB
RT <-->|"输入投递<br/>状态查询"| INPUT
INPUT <-->|"触控 / 按键<br/>取消 / 状态"| WEB
所有适配器实现公共 provider/session 契约;ProjectionSession 节点表示这一共用接口,不是额外输入源。CarPlay/AA 提供编码视频,VNC 提供 framebuffer,诊断源覆盖三类入口;插件和 Shizuku 助手作为 Surface producer 直接进入公共视频管线,画面不经过 ProjectionRuntime。WebRTC 直通仅适用于兼容编码源,Surface/framebuffer 当前走 JPEG/WebP。
插件 API v2 与 Desktop v7 是两套独立协议,分别终止于对应适配器及远端进程。图中 Surface 句柄传递包含在各自 AIDL 连线中:Surface 由公共视频管线创建,宿主适配器在 start 时将句柄交给远端;producer 写入画面的方向另有明确箭头,不通过 Binder 逐帧传 Bitmap/字节数组。插件发现/用户启用由宿主注册与授权逻辑负责,图中归入插件接入入口;不是插件 Service 自行注册或自动获得权限。
双向 AIDL 连线表示请求与响应/回调关系,不表示所有调用都是同步 RPC:插件 API v2 方法为 oneway,状态与停止确认通过回调返回;Desktop 控制操作返回同步状态,touch 是 oneway。插件正常释放需清理确认,声明 processExitRequired 时还等待原 Binder 死亡;Desktop 的 stop 只确认 producer 释放,之后仍需 destroy 并等待助手退出,再释放主进程 Surface。输入取消、迟到请求隔离及清理失败处理遵守上方“当前公共契约”。
虚线 CarLink 节点只表示计划中的插件实现,不表示已取得专有 SDK 或已有 ICCOA 协议。图中展开视频、输入及跨进程主链路;已有 CarPlay/AA 本机音频、CarPlay 浏览器音频及麦克风路径按各源能力保留,尚不存在所有源共用的完整音频/麦克风 broker,也不表示插件已支持音频。
当前依赖和所有权:
- 公共运行时:内置源与宿主插件适配层都实现协议无关的 provider/session。运行时负责接纳、切源、取消和资源占用;协议 I/O、设备状态机及媒体生产者由适配器/后端负责。浏览器不直接绑定插件 Service 或 Shizuku,也不认识 ICCOA SDK 类型。
- 插件 API 模块:
projection-plugin-api是独立 Android Library,宿主与插件共享 AIDL、基础能力字段和 SDK 契约;它不依赖common/shared、CarPlay、AAP、浏览器或厂商 SDK。projection-plugin-sample是独立 APK。第三方 SDK、native 库及其服务由插件 APK 持有,宿主不加载插件 dex/native。宿主侧签名/启用授权与 IPC 的 UID/token 检查约束连接,不授予插件额外 Android 权限。 - 跨进程画面:宿主创建并管理视频 consumer,通过 Binder 传递 Surface 句柄;插件或 Shizuku 助手持有 producer。像素不作为逐帧 Binder 参数传递。停止确认、必要的进程退出及本地清理共同决定何时释放资源;不能将宿主关闭 consumer 当成远端 SDK 已停止。
- 两套 IPC 边界:插件 API v2 服务于普通独立 APK;Desktop AIDL v7 服务于 Shizuku 启动的特权辅助进程。二者在宿主侧适配为同一 ProjectionSession,但不是同一套协议,也不因使用 Binder 而共享权限。插件 v2 目前只开放 Surface/触控/可选原生取消/夜间模式等能力;Desktop 另有显示配置、Back、输入代次及诊断快照。
- ICCOA 的位置:正式协议实现放在未来的 CarLink 插件 APK,接入宿主现有插件适配层。宿主内置 ICCOA 卡片仅关联插件入口;不是内置 ICCOA 协议适配器,也不意味着专有 SDK 已取得或协议已实现。
- 输出限制:当前插件、Shizuku 桌面及 framebuffer 源走 JPEG/WebP;WebRTC 直通仅适用于兼容的编码源。Surface/framebuffer→WebRTC 编码、插件音频/麦克风与编码帧 IPC 仍未实现。
内置实现继续主要以 common/shared 中的 package 划分边界;projection-plugin-api 和示例 APK 已拆为独立 Gradle 模块。原提案中的 projection-runtime、media-pipeline-android、output-browser、各 source-* 名称仍是逻辑分层,并非都已拆成同名模块。应用组合层负责装配,公共数据模型不暴露具体源协议或厂商 SDK 类型。
4. 核心接口契约
2026-10-27 实施注记:下列 suspend / StateFlow / SourceHost / InputPort / ControlPort 保留为设计轮廓,不是 SDK API。现实现采用 CompletableFuture、ProjectionStartup、ProjectionSessionState 和运行时状态订阅;能力及命令结果也仅落地了上方“当前公共契约”所述子集。
以下是设计轮廓,不是可直接编译的实现,也不要求改写现有协议线程为协程:
interface ProjectionSourceProvider {
val descriptor: SourceDescriptor
suspend fun probe(host: HostCapabilities): SourceAvailability
suspend fun open(request: OpenRequest, host: SourceHost): ProjectionSession
}
interface ProjectionSession {
val id: SessionId
val snapshot: StateFlow<SessionSnapshot>
val input: InputPort
val control: ControlPort
suspend fun close(reason: CloseReason)
}
interface SourceHost {
val video: VideoIngressRegistry
val audio: AudioIngressRegistry
val microphone: MicrophoneBroker
val resources: ResourceLeaseScope
}
interface InputPort {
fun submit(batch: InputBatch): InputAcceptance
suspend fun cancelAll(): CommandResult
}
interface ControlPort {
suspend fun execute(command: SessionCommand): CommandResult
}probe仅检查可用性,不占用无线资源或发起配对;返回 MissingRuntime、NeedsPermission、NeedsProvisioning、Unavailable 等具体原因。open在运行时授予资源后创建会话,不等待第一张画面才返回。发现、配对和协商进度由状态呈现;仅用户选中的源启动有副作用的发现流程。SessionSnapshot原子包含生命周期、当前能力、显示布局版本、可恢复错误、配对提示和各轨道状态。低频状态用 StateFlow;媒体不用 StateFlow 或无界事件总线传递。SourceCapabilities表示实现可能支持的上限;会话能力取手机、宿主权限/硬件、协议协商、输出端能力的交集,并可随连接变化。CommandResult区分 Unsupported、Unavailable、Rejected、Accepted、Confirmed、Failed;协议只有“发出”证据时不能报告已执行。- 通用控制覆盖请求关键帧、显示/音频焦点、夜间主题、媒体操作、退出投影等语义。原始协议消息、SDK 对象和整数命令码不穿越该边界。协议专用设置保留在适配器配置中。
5. 视频:三种入口,一个输出策略
| 入口 | 适用来源 | 公共管线 |
|---|---|---|
| EncodedVideo:完整压缩访问单元 + codec config | CarPlay、提取后的 AA、远程编码桌面 | 兼容时 WebRTC 直通;否则解码到共享画面层 |
| SurfaceProducer:源向宿主提供的 Surface 写画面 | MiAuto 当前 SDK 边界、可输出到 Surface 的虚拟桌面 | SurfaceTexture/EGL → JPEG;后续增加 GPU→MediaCodec encoder→WebRTC |
| Framebuffer:像素/脏矩形更新 | 典型 VNC/RFB | 顺序合成持久 framebuffer,再发布快照/纹理,接 JPEG 或编码器 |
不要要求所有源必须提供 H.264,也不要要求所有源必须提供 Bitmap。Surface 是输出目标句柄,不是像素数据;纯 Kotlin 核心仅描述入口能力,Android 扩展负责 Surface 的创建与租约。
压缩视频契约至少包含:sessionId、streamId、formatRevision、codec/profile/level、编码尺寸、显示区域/旋转、NAL framing、codec parameters、PTS/timebase、随机访问标记、缓冲区引用。建议中立格式归一为 Annex-B NAL access unit,CarPlay 的配置记录及 AA 的参数集由各自适配器转换。若源存在帧重排,保留解码顺序信息;不能只凭 PTS 假设无 B 帧。
时间戳同时区分源媒体时钟和本地单调接收时间;重连、跳变需重建映射。缺源 PTS 时允许使用合成时间并明确标记,不能用于宣称端到端延迟或精确音画同步。
输出策略由源格式、宿主解码/编码能力、浏览器 SDP 能力决定:
- 编码格式/profile/packetization 都兼容时直接转封装,不解码。H.265 不能假设所有浏览器都支持。
- 不兼容时先保留现有解码→JPEG 回退;转码是后续可选能力,会增加耗电、延迟与编码器占用。
- Surface 源先证明离屏纹理可被消费,再评估重编码。不能把 SDK
startCast()返回成功当作首帧到达或浏览器已显示。 - VNC 必须先应用全部有序增量更新(包括 CopyRect),再对合成后的完整画面做 latest-frame 覆盖。不能丢掉中间脏矩形更新,否则最终画面会错误。
背压与所有权必须成为接口契约:
- 回调内帧是借用引用;异步消费者 retain,发送/丢弃/关闭均 close;支持多消费者各自持有引用。禁止回调返回后继续读取会被协议层复用的数组。
- 每条媒体分支有按时长/字节/帧数限制的独立队列。保留现有 WebRTC 50 ms / 6 帧 / 2 MiB 作为当前路径基线,不把该预算硬编码为所有源限制。
- 压缩预测帧超时丢弃后须恢复到可解码边界,请求关键帧并等待;不可采用 JPEG 的任意 latest-frame 策略。
- 协议 ACK 留在适配器,浏览器 ACK 留在输出端;入队、协议消费、网络发送、远端显示是不同状态。协议允许主动丢弃时仍按其消费契约推进 ACK,防止窗口死锁。
- Surface 的创建、解绑、释放有明确完成顺序;旧 producer 停止写入后才能释放。不能只将一份 Surface 长期挂在 Activity 字段中。
6. 触控与控制:保留语义,不暴露协议
2026-10-27 实施注记:Touch、Pointer、Scroll、Key、独立 Back 及 cancelInput 已落地,Text/IME/Rotary 尚未完整实现。浏览器实际校验 sessionId/videoEpoch;下文 layoutRevision/v2 是原提案术语。取消与普通抬手已经分离,并明确 NATIVE/RELEASE_ONLY,不能要求所有源实现相同的原生取消效果。
输入分为 Touch、Pointer、Key、Text、Scroll、Rotary;按能力开放,不要求每个源全部实现。触控至少包含稳定 pointerId、DOWN/MOVE/UP/CANCEL、完整触点快照、变化触点 ID、序号、本地单调时间和 displayId。
浏览器传输使用实际内容区域的归一化坐标,并带 sessionId 与 layoutRevision。输入路由验证控制租约后统一处理 letterbox、旋转、crop 和显示区域映射;协议适配器最后转换成设备坐标及消息。显示布局变化时取消旧手势,拒绝旧版本输入,避免画面切换后误点。
- CarPlay:适配器将稳定 ID 映射到其两个 HID 槽位;双指上限属于 CarPlay 能力,不能成为框架全局上限。
- AA:生成明确的 action/actionIndex/pointer 集合,不能机械发送每次心跳快照作为 DOWN;转换时保证 index 与本次集合一致。
- VNC:优先 pointer/button mask/keysym;单指映射左键、滚动映射滚轮是显式交互策略。标准 RFB 不保证多点触控、音频、麦克风或文本提交,扩展须协商。
- Android 桌面:按实际输入后端声明能力。MediaProjection 的画面授权不等于跨应用注入触控权限;创建 VirtualDisplay 也不自动获得任意应用启动和输入注入能力。自有应用桌面、Shizuku/系统权限桌面、远端桌面应明确区分。
继承现有租约超时释放、失焦释放与边界事件保序。只允许合并相邻 MOVE,不丢 DOWN/UP/CANCEL;队列溢出时取消手势并撤销租约。源切换必须向旧会话取消输入,而不是重新查找“当前源”后把抬手发送给新源。
浏览器协议建议升级 v2,增加源描述、能力、布局版本和会话标识;保留 v1→中立触控转换。v1 无法携带会话代次,切源时应关闭其控制连接并要求重新握手,避免旧消息误投新源。
7. 音频、麦克风与扩展数据
虽然首要目标是视频、触控和控制,接口从第一版就应给音频与麦克风独立端口,否则 AA 焦点、语音助手和 CarPlay 现有浏览器音频会再次耦合回主会话。
- 下行音频以 PCM 为首个通用接缝,包含 streamId、用途(media/navigation/voice/system)、sample rate、channels、sample format、时钟/样本位置。CarPlay RTP、AA channel 和压缩音频解包留在适配器侧,共享 codec utility 可复用。
- 上行麦克风由 broker 选择 Android/浏览器/禁用,按源要求重采样;协议编码与封包留在源内。禁用意味着不采集、不回退,并在可协商时不声明输入能力。
- 音频焦点由公共策略协调,再映射回协议;本机音频焦点、手机协议焦点、浏览器就绪是三个不同状态。
- SDK 自己占用录放音但未提供数据回调时,明确声明
SourceManaged,不能假装能转发浏览器音频或注入麦克风。 - Now Playing、导航、通话与传感器各是可选端口;用中立快照表达,不暴露 iAP2 protobuf/ICCOA 类型。手机向宿主发送导航与宿主向手机提供定位是不同方向。
8. 生命周期、资源与切源
2026-10-27 实施注记:实际阶段/错误/清理状态、启动与就绪分离、协作取消及超时机制见上方当前公共契约。下文的完整状态图和资源仲裁范围仍是目标设计;目前公共资源仅有 USB/WIRELESS/MICROPHONE/DISPLAY,未实现完整 BLE/VPN、音频焦点与 codec 预算仲裁。
会话主状态建议为 Idle → Preparing → AwaitingUser/Connecting → Negotiating → Connected → Stopping → Closed;失败含原因、是否可恢复和重试预算。媒体各自有 Waiting/Active/Paused/Recovering/Ended 状态。连接存在不等于视频活动,静止画面不等于断线;浏览器重新连接也不应自动重建手机协议会话。
资源仲裁器管理 USB 设备句柄、P2P/SoftAP 控制、BLE 广播、VPN、麦克风、音频焦点与 codec 实例预算。租约是宿主内部协调手段,不保证系统或外部应用不会抢占。对 ICCOA 这类内部自行管理网络的 SDK,需在启动前预留粗粒度独占租约,并证明 stop 后资源确实释放。
首版只启动一个具有网络副作用的源。无线源上游链路与车机浏览器下游 LAN 必须一起规划:许多设备不能同时维持所需的 STA/P2P/热点组合;改热点可能把浏览器断开。连接前做可用性检查,绑定正确网络/接口,并在手机界面提供重新连接浏览器的入口。不能承诺切换输入源时浏览器 TCP 一定不断。
切源事务:
- 无副作用地检查目标源依赖、权限和资源要求。
- 封闭旧会话输入入口,取消所有触点/按键,停止麦克风和新媒体提交。
- 将旧会话标为 retiring,使迟到回调失效;关闭旧帧队列和 RTC,清除旧画面/音频/元数据。
- 等待旧源停止完成后释放资源,再启动新源;使用新 sessionId/epoch。不要在超时后直接假装资源已释放。
- 新源协商成功后发布布局/能力,配置输出并等待首帧,再开放控制。失败返回可恢复的空闲/错误状态;恢复旧源应新建会话,不能复活旧回调。
数据、命令、定时器、Surface 和输出连接都绑定 sessionId/epoch。控制与状态在串行执行上下文中处理,媒体走独立有界工作队列;网络 I/O 不在状态锁内等待。close 幂等并完成未结束命令。Activity 仅承载授权与 UI,服务/运行时持有会话与媒体资源。
9. 分阶段实施与验收
| 阶段 | 工作 | 完成条件 |
|---|---|---|
| P0:接缝与基线 | 定义中立模型、会话/输入/视频端口;记录现有 CarPlay 延迟与恢复基线 | 能明确每个回调、队列和资源的所有者 |
| P1:CarPlay 适配 | 包装原控制器/后台快照;WebSession 改依赖活动会话;认证门控下沉;保留原媒体引擎 | CarPlay 有线/无线、双指、浏览器音频、麦克风禁用、重连、RTC→JPEG 回退不退化;无 MFi 资源可启动宿主页面并显示 CarPlay 不可用 |
| P2:验证抽象 | 增加 synthetic source:编码帧、Surface 图案和 framebuffer 增量;做快速切源、重配置、慢消费者 | 旧帧/旧触控不能污染新会话;无丢引用、卡手指、队列无界增长;输出模块没有具体协议依赖 |
| P3:Android Auto | 提取 DiAuto 协议相关部分,替换解码器直连与 Activity 焦点假设;先实现一种传输,再加无线 | 目标 Android 服务设备上完成画面→浏览器、触控→手机、音频/语音闭环;协议 ACK 不随浏览器拥塞停止 |
| P4:ICCOA 可行性与适配 | 先完成下列 SDK 验证,再包装 manager/bridge;Surface→JPEG 为首条输出 | 目标硬件完成可交互离屏投影;所有不可接管能力准确标示;切源能释放网络/麦克风 |
| P5:桌面/VNC | 按需求加独立适配器,复用 Surface/framebuffer 与 Pointer/Key 端口 | 证明无视频压缩流、无音频、无多点能力的源仍能正常工作 |
ICCOA 小规模验证可以提前进行,避免架构落地后才发现 SDK 边界不可用;正式集成仍建议晚于 CarPlay 与 AA。
必要的契约测试:配置先于帧、格式变更后旧帧拒收、压缩队列溢出恢复、Surface 重建/迟到回调、buffer retain/close 平衡、触控边界与布局变更、旧命令跨切源、资源释放失败、无音频/只读源、VNC 增量合成后限帧。复用现有媒体、TouchLease、WebRTC 恢复测试作为回归;测试协议契约而非只验证方法被调用。
验收指标记录源接收、入队、丢弃、输出发送、浏览器呈现分段耗时与速率,外加内存、CPU、耗电和重连时间。真实性能改善须在相同设备/网络场景下比较,不从架构图推断。
10. 接入前仍需回答的问题
- ICCOA 是否能合法获得适合 WheelPlay 分发方式的运行 SDK?MiAuto 的 AGPL 源码许可不覆盖该专有运行库。DiAuto/MiAuto 均标注 AGPL-3.0;WheelPlay 根许可为 GPLv3 且已有 AGPL UI 来源声明,复用时应保留来源并核对最终组合分发义务。模块或进程隔离本身不等于豁免。
- ICCOA 的 Surface 是否接受 SurfaceTexture 离屏目标?是否有受保护内容或 SDK 生命周期限制?能否可靠暂停、替换 Surface、停止后台服务?公开包装层尚不足以证明。
- 实际 SDK 的触控、按键、麦克风注入、音频导出与关键帧请求接口是什么?没有接口或验证证据的能力保持 Unknown/Unsupported。
- AA/ICCOA 在普通 Android 服务手机上能否维持上游手机连接与下游车机浏览器网络?当前外部项目的运行位置和测试硬件与 WheelPlay 不同。
- 已明确([RFC] Shizuku/ADB 本机 Android 虚拟桌面输入源方案 #40 / feat: add Shizuku desktop and shared projection lifecycle contracts #52):Android 虚拟桌面指宿主同一 Android 系统中由 Shizuku UserService 创建独立显示屏并启动所选本机应用,不是模拟器或远程 Android 实例,也不提供独立用户/应用数据隔离。当前仅启动前设置尺寸/DPI,会话内布局变化要求重新连接;应用、副屏、输入和清理兼容性仍待 [RFC] Shizuku/ADB 本机 Android 虚拟桌面输入源方案 #40 真机验收。
11. 外部证据入口
链接固定至本次研究提交,避免后续默认分支变化造成结论漂移。
Activity
feat: add multi-source projection framework (#33)
fython commentedon Oct 23, 2026
已按最新主线
a4e3a1f同步本 Issue 正文与实施清单:icar-sdk.dex缺失阻塞;Shizuku/ADB 本机虚拟桌面单独跟踪 [RFC] Shizuku/ADB 本机 Android 虚拟桌面输入源方案 #40,VNC 与 Rust/Linux 后端仍未实现。正文已区分代码完成与设备验收,保留原始设计供追溯。#28 继续开放,后续优先完成 AA 真机验收和框架/CarPlay 的设备性能记录。
fython commentedon Oct 26, 2026
同步主线进展(2026-10-26),核对提交:54a636a。本次以追加评论记录,正文仅调整实施 checklist;正文中 2026-10-23 的状态表保留为历史记录,以下更新补充其后变化。
已合入主线
projection-plugin-apiSDK、示例 APK 和接入文档。插件通过独立 UID/进程和 API v2 AIDL 接入,支持 Surface 视频、最多十点触控、昼夜模式及设置入口。安装后可发现,默认禁用,需用户明确启用;授权关联组件、签名和首次安装时间。SOURCE_ID=iccoa-carlink的插件后,内置 ICCOA 卡片展示对应插件的设置、启用、连接和停止入口。首页默认 CarPlay (iPhone),支持选择并记住 Android Auto、VNC 和已启用的兼容插件;单纯选择不会自动切换正在运行的会话。验证结果
剩余工作
#28 继续保持开放;本次仅将已交付的插件基础设施和 VNC 代码任务标记完成,设备验收任务不提前勾选。