# 目标应用副屏(实验功能) 更新:2026-10-04。 本功能让用户选择的目标安卓应用运行在独立虚拟屏幕,再把副屏画面显示到 DSH 独立页面或可拖动小窗。AI 使用专用的状态、截图和动作工具操作该会话。 ## 使用入口 1. 在壳内设置中连接并授权 Shizuku,开启 **允许 AI 调用 Shell**。 2. 打开 **目标应用副屏(实验功能)**,搜索并选择一个有启动入口的应用。 3. 默认使用 726×1600 像素、320 dpi,即 363×800 dp;横屏使用 1280×580 像素、256 dpi,约 800×363 dp。 4. 页面支持点击、滑动、应用返回、结束副屏;预览区域在触摸直传通道可用时逐事件转发指针,底部一行按钮切换预览节奏(省电 / 15fps / 30fps / 60fps)。授予悬浮窗权限后,可切换为可拖动小窗;收起小窗或退出页面保留会话,通知可重新打开或结束副屏。 5. 返回对话后,让 AI 先查询副屏状态、截图观察,再操作。现有主屏截图、主屏无障碍工具继续使用原来的通道。 选择应用会启动或迁移其任务,关闭副屏可能结束该副屏内的任务。目标应用是否允许多屏、多实例以及后台渲染,由其自身和设备系统决定。首次使用需用非关键任务验证。 ## 机制和兼容模式 - `ShellVirtualScreen` 在 Shizuku 用户服务中创建独立显示,并持续消费 `ImageReader` 缓冲区。页面和小窗从同一会话读取 PNG,关闭某个预览不会重建目标应用。 - 图像只在内存中保存;Binder 跨进程通道使用管道传输,避免把整张图塞进单次 Binder 事务。画面静止时复用最后一帧,返回的状态包含最近一帧的时间。采集时按固定步长采样判定空白帧:空白帧不会被当作有效画面缓存,状态与截图结果都会给出 `frameBlank`,避免把白帧当成新画面继续使用。 - 预览节奏可选:默认「省电」沿用限帧采集(180 毫秒合并,静止画面复用最后一帧),也可切到 15/30/60 fps(采集间隔 66/33/16 毫秒)。状态返回实际生效的 `previewMode`(`limited-fps` 或 `realtime-<模式>`)、`frameIntervalMs` 与最近 24 帧测得的 `frameFps`。帧率只提高采集与预览频率,不改变「静止页面复用最近一帧」的事实,也不代表目标应用自身以该帧率渲染。 - 触摸透传:预览区域把手势逐事件发给副屏(进程内 `InputManager.injectInputEvent`),状态字段 `touchChannel` 为 `stream`;通道不可用时自动回退为离散的 `tap`/`swipe` 命令(`touchChannel=discrete`),行为与旧版一致。每次输入前按副屏编号确认目标应用仍处于该屏已恢复的活动中,再调用带 `-d` 的系统输入命令。 - 有 `ADD_TRUSTED_DISPLAY` 权限的 Android 13 以上环境才请求受信任副屏;Android 14 以上才进一步请求独立焦点。权限不足时使用普通独立副屏,不创建主屏镜像。`independentFocusRequested=false` 表示未请求独立焦点,需要单独验证主屏与副屏并行交互体验。 - 无法创建副屏、无法确认目标应用位置、系统命令失败或会话过期时返回错误,不将副屏操作改为主屏操作。部分系统仍可能自行改写应用启动位置,所以不能把“请求副屏启动”视为绝对不影响主屏的保证。 - 显示销毁时使用销毁内容标志;用户结束、授权关闭、Shizuku 断开、宿主进程死亡均有资源回收路径。页面旋转与页面/小窗切换保留服务会话。 ## AI 与命令接口 权限要求:设备桥的本机 Bearer 认证、用户已开启 AI Shell、Shizuku 已授权、用户已从原生页建立副屏会话。副屏接口不能替用户打开授权开关或任选系统显示编号。状态查询不发送全部应用清单。 | 工具 | 本机命令 | 参数与结果 | | --- | --- | --- | | `mobile_virtual_screen_state` | `virtualScreenState` | 无参数;返回是否运行、会话标识、显示编号、目标包名、像素尺寸、采集时间、焦点模式,以及预览节奏(`previewMode`/`frameIntervalMs`/`frameFps`)、触摸通道(`touchChannel`)与节点树可用性(`treeSupported`) | | `mobile_virtual_screen_screenshot` | `virtualScreenCapture` | 必须带 `sessionId`;返回该副屏 PNG,附件服务转换格式后保留真实 MIME;结果带 `frameBlank`,为 true 表示该帧为空白或目标未渲染,可稍后重试或先用 `action` 唤醒目标应用;必须使用支持图像输入的模型 | | `mobile_virtual_screen_action` | `virtualScreenAction` | 必须带 `sessionId` 和 `action`;支持 `tap`、`swipe`、`long_press`、受控 `keyevent`、`text`、`back`、`stop`,以及下列三条工具各自封装的 `config`、`target`、`tree` | | `mobile_virtual_screen_config` | `virtualScreenAction`(`action=config`) | 必须带 `sessionId` 和 `previewMode`(`limited`/`15fps`/`30fps`/`60fps`);只改采集节奏,不重建显示器 | | `mobile_virtual_screen_target` | `virtualScreenAction`(`action=target`) | 必须带 `sessionId` 和 `packageName`;把该应用启动或迁移到当前副屏显示,会话不重启;校验失败时报错且不改变当前目标 | | `mobile_virtual_screen_tree` | `virtualScreenAction`(`action=tree`) | 必须带 `sessionId`,可选 `maxDepth`(1~8,默认 4);返回受限节点树,需要用户先在系统设置里启用 DSH 的无障碍服务 | 动作校验:坐标为整数且在实际副屏宽高内;滑动终点同样校验,持续时间为 100~2000 毫秒;长按持续 500~3000 毫秒。`keyevent` 仅允许 BACK、ENTER、DEL、TAB、方向键、SPACE 和 ESC;`text` 限制为 1~512 个字符且不含控制字符,纯可打印 ASCII 仍由 `/system/bin/input` 输入,含中文、emoji 等非 ASCII 字符时改走无障碍定向注入(需用户已启用无障碍服务,并且副屏上有聚焦的可编辑、非密码输入框;未满足时报 `VIRTUAL_SCREEN_UNAVAILABLE`,可退回 ASCII 或请用户在目标应用内输入)。`previewMode` 只接受四种模式名,`packageName` 必须是完整包名(≤192 字符),`maxDepth` 只接受 1~8。会话必须匹配宿主正在管理的会话;显示编号由服务持有,调用方不能覆盖。会话切换瞬间(包括 `back` 之后)可能返回 `VIRTUAL_SCREEN_STOPPED`,重新读取状态后重试即可,不应把它当成副屏不可用。启动入口和 Shell 参数使用分别校验后的参数数组,不拼接成脚本。接口正文最多 4096 个字符。 错误码包括 `DEVICE_SHELL_DISABLED`、`VIRTUAL_SCREEN_NOT_STARTED`、`VIRTUAL_SCREEN_STOPPED`、`VIRTUAL_SCREEN_LOCKED`、`VIRTUAL_SCREEN_BUSY`、`VIRTUAL_SCREEN_INVALID`、`VIRTUAL_SCREEN_UNAVAILABLE`、`VIRTUAL_SCREEN_UNSUPPORTED`(系统版本低于 Android 10)。错误不包含系统命令输出、图像或私有路径。`VIRTUAL_SCREEN_BUSY` 表示前一步仍在处理,应等待后重新观察,不能盲目重复操作;`VIRTUAL_SCREEN_STOPPED` 表示会话已切换或副屏正在结束,重新读取状态后再操作。 Shell 中也可通过 `dsh-device virtualScreenState` 查询,另两条命令把 JSON 作为单个参数传入。截图响应是 Base64 编码 PNG;模型插件会处理附件,并在工具结果中报告 `packageName`、`frameAtElapsedMs`、`frameReused` 与 `frameBlank`,不需要 AI 自行猜测图像格式。缩放截图使用原始尺寸与附件尺寸的精确比例恢复坐标。 ## 隐私与当前边界 - 画面中可能包含目标应用的个人信息。原生本地预览不上传图像;AI 截图请求会把图像送入当前会话及其模型服务。界面和工具提示明确说明这一点,模型不得采集与任务无关的隐私内容。 - 审计只记录副屏会话、读取、操作的类别和结果,沿用本项目至少 90 天保留规则;不记录目标包名、截图、坐标、输入文本和命令正文。 - 锁屏、熄屏时暂停本通道的读取与输入,预览清空。未请求“始终解锁”显示标志,也不保证目标应用在锁屏后继续渲染或自动恢复。 - 副屏节点树由应用自己的无障碍服务按显示编号读取(只读、限层级与节点数:最多 400 个节点、8 层、单个文本字段 120 字符),需要用户先在系统设置里启用 DSH 的无障碍服务;未启用时 `available=false` 并给出原因,**不会**回退到主屏窗口,也绝不复用主屏的节点树。状态里的 `uiTreeSupported=false` 说明设备 Shell 通道本身不提供节点树,能否读取副屏树一律以应用侧字段 `treeSupported` 为准。敏感窗口整棵拒绝。中文与 emoji 通过同一条无障碍定向注入通道输入,只作用于聚焦的可编辑、非密码输入框。 - 仍不支持多指手势;预览帧率可调但仍是采样与轮询管线,不承诺视频级的连续性。多副屏并行会话本轮未实现:状态里始终只有一块副屏、一个 `sessionId` 与一个显示编号。 - 安全窗口、DRM 内容或应用自身的多屏限制可能导致黑屏或拒绝启动。不会绕过目标应用登录或 Android 的保护。 - 模型推理与网络耗时仍参与整个自动化循环。设备输入耗时不能当成“AI 看到界面后完成操作”的端到端延迟。 ## 验证方法 `androidTest` 包包含独立的 `VirtualScreenProbeActivity`,仅显示计数按钮,不读取用户数据。`scripts/android/VirtualScreenDeviceProbe.java` 可通过 `adb shell app_process` 加载主 APK 的副屏实现,以 Shell UID 验证竖屏、横屏、PNG、定向点击、静止画面、主屏任务和过期会话。 该探针运行于 Android 13 以上测试设备(使用系统 `InputStream.readAllBytes`);应用副屏入口最低为 Android 10,较低版本仍需另行测试。探针不替代 Shizuku 授权、Binder 绑定、真实应用、小窗生命周期和锁屏测试。 构建先执行前端构建和 `cap copy android`,再执行 `:app:assembleDebug`、`:app:assembleDebugAndroidTest`、`:app:testDebugUnitTest`、`:app:lintDebug`。可用 JDK 的 `javac --release 11` 编译探针,类路径为 Android SDK 的 `android.jar` 和 `app/build/tmp/kotlin-classes/debug`,使用 SDK 的 `d8` 转成 dex 并封装到 jar,再与主 APK 一起作为 `CLASSPATH` 交给 `app_process`。产物仅放在忽略目录,不能提交 APK、dex、PNG 或日志。 `VirtualScreenPageTest` 在设备当前尺寸验证原生选择页的列表可用高度和按钮边界;窄屏与宽屏测试后应恢复模拟器原始尺寸和密度。 ### 2026-10-02 本机验证记录 - 前端类型检查、相关 ESLint 检查通过;工具与兼容协议测试 28 项通过,设置入口测试 3 项通过。 - Android JVM 单元测试共 511 项,496 项通过、15 项跳过,0 个失败、0 个错误,其中副屏策略 6 项通过;`lintDebug` 为 0 个错误,存在既有和实验接口相关警告。 - MuMu Android 15(API 35),使用普通 `uid=2000(shell)`,没有使用 root。该设备缺少受信任显示权限,因此走普通独立副屏。 - 竖屏:726×1600 像素,副屏编号 2,PNG 21510 字节;横屏:1280×580 像素,副屏编号 3,PNG 15251 字节。两者点击都使测试应用计数从 0 变为 1。 - 两种方向都验证了主屏原有活动未改变、静止 3.5 秒后仍能截图、关闭后状态为非活动、旧会话操作被拒绝。测试结束后系统显示列表中没有残留的 DSH 副屏。 - 一次最终运行中,竖屏启动约 742 毫秒、横屏约 70 毫秒;点击与校验约 371/330 毫秒,其中包含探针主动等待的 300 毫秒。这些数值只是该模拟器的单次观测,不能代表真机或 AI 端到端延迟。 - 原生选择页分别在实际 `w363dp h800dp`、`w800dp h363dp` 下通过设备测试,列表保留可操作高度,按钮未越出页面边界。测试后恢复模拟器尺寸、密度和旋转设置。 尚未验证:真实 Shizuku Manager 授权与 Binder 完整链路、真实第三方应用兼容性、预览页面与小窗互切的完整会话体验、长期运行、锁屏恢复。该模拟器尚未安装 Shizuku,设备探针仅验证与 Shizuku 同为 Shell UID 的副屏核心。已有运行时需要重新打包或更新本项目的 `dsh-mobile-shizuku` 插件,才会出现新增 AI 工具;仅安装新壳 APK 不会改写用户现有插件数据。 ### 2026-10-03 HONOR AAP-AN00 真机回执 - 运行环境 `0.2.3-mobile-323`、dsh `0.2.0-rc.2`,Android 17;自检可用空间约 151 GB。 - 运行自检:Shell、Node.js、`landlock-run`、Landlock、`node-pty`、访客数据目录、附件目录、ripgrep、Python 3 正常;沙箱内命令执行、沙箱内 PTY 和硬链接失败;C 编译器与 make 未找到。 - 副屏已确认:独立 display `17`(726×1600),与主屏互不干扰;`tap`、`back`、`input -d 17 text` 的 ASCII 输入、`am start --display 17` 均可用;状态可读取 active、sessionId、displayId、packageName、frameAtElapsedMs、previewMode、uiTreeSupported。 - 目标应用副屏当前仍无专用无障碍树;中文输入和剪贴板在该设备上不可用。分享 Intent 可作为中文或 emoji 的替代通道,但发送前必须人工核对内容。 - 发现的兼容问题:DSH 自身副屏预览页在前台时可能读到白帧,切回主页面后才恢复画面;`back` 在会话切换瞬间可能短暂返回 `VIRTUAL_SCREEN_UNAVAILABLE`;静止页面会复用最近一帧。后续版本会在工具结果中携带帧时间和缓存标志,AI 应在状态变化后重新观察,不应把旧帧当成新步骤已完成。 - 沙箱执行和硬链接失败属于该设备的 PRoot/内核策略限制,本项目不将其描述为已解决;需要编译原生模块的插件还需在 Ubuntu 终端内验证 build-essential 是否可用。硬链接失败也不等同于所有文件写入都失败,具体以对应工具结果为准。 ### 2026-10-04 预览节奏、触摸透传、目标切换与节点树(本机单测) - Android JVM 单元测试 72 类、573 项、0 失败(21 项在 Windows 跳过);其中 `io.deepseekharness.mobile.virtualscreen.*` 30 项:策略层帧率映射与目标字段 11 项、空白帧采样 6 项、错误码映射 4 项、节点树文本校验与节点摘要 9 项。`lintRelease` 0 错误、48 警告(仍是未使用资源、图标、`SetTextI18n`、`ClickableViewAccessibility` 等既有类别)。 - 工具与兼容协议:`node --test scripts/*.test.mjs` 166 项、162 通过、0 失败、4 跳过;其中 `scripts/mobile-device-tools.test.mjs` 13 项覆盖新增动作与文本分流,`scripts/mobile-compat-contract.test.mjs` 校验模型侧工具清单已包含 `mobile_virtual_screen_config`、`mobile_virtual_screen_target`、`mobile_virtual_screen_tree`。 - 本机无法覆盖、需要真机确认的部分:`InputManager.injectInputEvent` 反射是否成功(决定 `touchChannel` 是 `stream` 还是 `discrete`)、触摸逐事件直传与离散回退的实际效果、`config`/`target`/`touch` 的端到端行为、`frameFps` 换算是否符合观感、`getWindows()` 能否看到虚拟屏窗口、`ACTION_SET_TEXT` 对副屏聚焦可编辑节点的中文注入,以及无障碍相关的审计与敏感窗口判定。 ### 2026-10-04 HONOR AAP-AN00 真机回执(副屏启动、预览节奏、虚拟显示) - 环境:Android 17,App `0.2.7`(`versionCode 29`,内置运行时 `0.2.4-mobile-339`、dsh `0.2.0-rc.2`),Harness 服务显示「运行中」,Shizuku 显示「已连接」。 - **0.2.6 的副屏启动回归已在真机确认修好**:`设置 → Shizuku 与设备 Shell → 目标应用副屏(实验功能)` 选 Edge(`com.microsoft.emmx`)后不再出现「副屏启动失败」,直接进入副屏视图。 - 预览节奏:状态行为 `副屏预览 · 点击或滑动操作 · 2.3 fps · 静止页面复用最近一帧`,静止页面上读数在 **0.7–2.3 fps** 之间浮动;`省电` / `15FPS` / `30FPS` / `60FPS` 四个键都能点。点 `60FPS` 后状态行读数不变——该行显示的是**实测采集帧率**,静止页面本来就不产生新帧,模式切换的效果要用 `mobile_virtual_screen_state` 的 `previewMode`/`frameIntervalMs` 读数确认(见下)。 - 虚拟显示(`dumpsys display` 里 `DisplayDeviceInfo` 共 2 条,第二条即副屏): `DisplayDeviceInfo{"DSH 目标应用": uniqueId="virtual:com.android.shell,2000,DSH 目标应用,27", 726 x 1600, modeId 32, renderFrameRate 60.0, density 320, touch VIRTUAL, type VIRTUAL, owner com.android.shell (uid 2000), FLAG_OWN_CONTENT_ONLY, FLAG_DESTROY_CONTENT_ON_REMOVAL, FLAG_TRUSTED, FLAG_OWN_FOCUS, FLAG_STEAL_TOP_FOCUS_DISABLED, state ON}` ⇒ 竖屏 726×1600 @320 dpi(= 363×800 dp),与本文档的默认尺寸一致。 - 虚拟显示上的窗口(`dumpsys window windows`,display `29`,`mLogicalSize=726x1600`):可见窗口含 `7cba5b3 com.microsoft.emmx/com.microsoft.ruby.Main frame=[Rect(0, 0 - 726, 1600)]` 与一个同尺寸 `PopupWindow` ⇒ **目标应用确实运行在独立虚拟屏上**(不是镜像或覆盖层)。同一 dump 里还有 `WindowsForAccessibilityObserver{mDisplayId=29, mInitialized=true}` ⇒ **无障碍窗口观察器已挂到该虚拟屏**, 即上文「`getWindows()` 能否看到虚拟屏窗口」这一项在真机上成立。 - **会话与视图生命周期解耦**:用系统返回键离开副屏 Activity、约 15 分钟后再从同一入口进入,副屏会话仍在 (状态行继续刷新),没有重建成新会话。副屏 Activity 带 `FLAG_SECURE`,`adb shell screencap` 对它必然失败, 所以桌面侧不能截图取证,只能读 a11y dump 与 `dumpsys`。 - **本轮仍未能从设备侧取到工具层读数**,原因是设备桥只对 Harness 运行时进程开放:宿主把 `DSH_DEVICE_BRIDGE_PORT`/`DSH_DEVICE_BRIDGE_TOKEN` 注入运行器进程 (`android/app/src/main/java/io/deepseekharness/mobile/runtime/RuntimeCommand.kt:173`、 `…/runtime/RuntimeStore.kt:1529`),App 里的 Ubuntu 终端会话拿不到,于是在终端里跑 `dsh-device virtualScreenState` 一律回 `DEVICE_BRIDGE_UNAVAILABLE`;本文档 P0-6 曾提到的访客文件 `/root/.dsh-mobile/runtime-secrets.env` **在真机上不存在**(`find / -xdev -name runtime-secrets.env` 连跑两次都没有输出),`/root/.dsh-mobile/` 里只有 `dsh-runner/`、`plugin-manager*`、 `launcher-*.patch.json`、`sandbox-runner.sh` ⇒ 那条自取端口/令牌的路子走不通,接口只能由 App 内的 AI 会话调用工具来走查(`mobile_virtual_screen_state` 一次调用即可同时读出 `previewMode`、 `frameIntervalMs`、`frameFps`、`touchChannel`、`treeSupported`)。 ## 参考来源 机制参考:`https://github.com/Aliothmoon/MAA-Meow`,查看的提交为 `faf7ef0e57dbf4e70d5b6e422b74ce5d887c50a3`。重点研究其 `VirtualDisplayManager`、`DisplayManager`、输入显示编号和副屏预览职责划分。参考仓库的主许可证是 AGPL-3.0,第三方代码保留各自许可证;本实现未搬入其原生采集器、桥接库、包装类源码或二进制,使用本项目现有 Shizuku 服务和单独编写的 ImageReader/PNG 路径。