Skip to content

feat: 增加 Legacy/VDM 虚拟屏后端选择 - #295

Merged
Aliothmoon merged 7 commits into
Aliothmoon:mainfrom
3265204:feat/minimal-vdm
Oct 21, 2026
Merged

Aliothmoon merged 7 commits into
Aliothmoon:mainfrom
3265204:feat/minimal-vdm

Conversation

@3265204

@3265204 3265204 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Pull Request

提交前请阅读 PR 规范 / PR Guidelines。

关联 Issue

Related #273

#273 中后台任务的虚拟屏由 DisplayManager.createVirtualDisplay 创建。该方式在 Android 16 上即使设置 TRUSTED 与 OWN_DISPLAY_GROUP,display 仍落在 display group 0:用户按电源键灭屏后虚拟屏一起 OFF,游戏 Surface 被销毁,任务链报错。本 PR 增加可选后端,默认行为保持不变。

变更摘要

  • 新增设置项「虚拟屏后端」,默认 Legacy,Android 14 及以上可选 VDM。一次任务只使用所选后端;VDM 任一步失败直接失败,不回退 Legacy。
  • VDM 由提权主服务持有:VirtualDeviceManager.createVirtualDevice → VirtualDevice.createVirtualDisplay,并先通过 CompanionDevice association 建立关联,使 display 进入独立 display group。提权服务在所有后端上保持 shell(2000),同一主服务最多一个 VDM session。
  • 版本差异:cmd companiondevice associate 在 Android 14 只有三个参数,15 增加 device profile 参数,16 再增加 selfManaged 标志;15 及以上使用 COMPANION_DEVICE_APP_STREAMING profile,并清理旧 profile 的关联。display flags 含 TRUSTED,满足 API 36 进入独立 display group 的条件。
  • 创建后校验 displayGroupId > 0 与 Display.STATE_ON;失败按 VirtualDisplay → VirtualDevice → Association 逆序清理,单步失败不中断后续步骤并保留首个异常。
  • 任务启动时同后端复用已建显示;切换后端时先释放旧显示再重建。触控、应用移动、残影保护统一读取本轮活动 displayId。
  • 测试为行为测试:复用规则、版本命令、清理顺序与失败语义、后端分流与「不回退 Legacy」均有断言,不读取源码字符串。
  • 未修改 Legacy 的 VirtualDisplayManager、三个提权 connector、launcher.c 与 R8 规则;未引入 router、registry、factory 或 fallback。

验证

  • sh gradlew :app:testDebugUnitTest 通过:1426 tests / 0 failures / 0 errors(含 VdmDisplayControllerTest 9 个新增用例)。
  • sh gradlew :app:assembleDebug、sh gradlew :app:assembleRelease 通过;Release mapping 中 RemoteServiceImpl、LogcatCaptureServiceImpl 保留原类名与 <init>(),未为 VDM 新增 keep。
  • Samsung SM-S9380 / Android 16 / API 36 / Root(KernelSU):
    • VDM 创建:virtual display backend=VDM、association created id=67、displayId=18、displayGroupId=9、uid/euid=2000,MaaCore 连接 display_id=18。
    • 同后端复用:连续三次启动均为 reusing active displayId=18,MaaCore 均连接 display_id=18;VdmDisplay started 与 association created 各只出现一次。
    • VDM 停止:association removed、VdmDisplay stopped,dumpsys virtualdevice 由 START 变为 CLOSE,无 display、device、association 残留。
    • Legacy 创建与复用:displayId=19,第二次启动 already capturing,MaaCore 连接 display_id=19。
    • 后端切换 VDM→Legacy:releasing active displayId=22 before backend change → 释放 association 与 VirtualDevice → 新建 Legacy displayId=23。
  • 物理屏灭屏保持:VDM 运行期间物理屏进入 DOZE_SUSPEND,MAA_VD 仍为 state ON 并持续接收输入与 MaaCore 回调。

实现依据(AOSP)

截图 / 日志 / 说明

  • 本机对照:Legacy 虚拟屏 displayGroupId=0;VDM 虚拟屏 displayGroupId=9。物理屏 OFF/DOZE_SUSPEND 后 Legacy 跟随 OFF,VDM 保持 ON。
  • 已知限制与未覆盖项:
    • 未在 Android 14/15 真机验证。这两版的命令参数、profile 迁移与反射绑定暂时按 AOSP 实现。
    • VDM 创建失败路径与 awaitDisplayReady 超时路径未在真机触发;「失败不回退 Legacy」目前由单元测试覆盖分派逻辑。
    • LOCK_STATE_ALWAYS_UNLOCKED 被系统拒绝后的回退路径未在真机触发。
    • 仅支持主用户;工作资料与次级用户下可能会导致 VDM 显式失败,不影响 Legacy。
    • Association 的固定 MAC 地址由 applicationId 与 userId 派生,卸载应用后关联可能残留在系统中。

Checklist

Sourcery 总结

新增可选的 VDM 虚拟显示支持,使后台会话保持在独立的显示组中,同时默认保留现有的 Legacy 行为。

新功能:

  • 新增可配置的虚拟显示后端,默认使用 Legacy,并在 Android 14 及更高版本上提供实验性的 VDM 支持。

错误修复:

  • 防止 VDM 启动失败时静默回退到 Legacy,避免后端意外切换。

增强:

  • 通过特权服务管理 VDM 会话,包括显示组验证、按后端复用、清理,以及用于输入路由和防止显示过时帧的活动显示路由。

测试:

  • 新增侧重行为的测试,覆盖后端选择、特定 Android 版本的关联命令、显示复用、清理顺序和失败语义。
Original summary in English

Sourcery 总结

支持为后台会话选择独立的 VDM 虚拟显示后端,同时默认保留现有的 Legacy 行为。

新功能:

  • 添加可配置的虚拟显示后端,默认使用 Legacy,并在 Android 14 及更高版本上提供实验性的 VDM 支持。

错误修复:

  • 防止 VDM 启动失败时静默回退到 Legacy 后端,从而保留所选后端的行为。

增强功能:

  • 通过显示组验证、感知后端的显示复用与清理,以及将输入和过期帧处理一致地路由到活动显示,来管理 VDM 会话。

测试:

  • 添加侧重行为的测试,覆盖后端选择、特定 Android 版本的关联命令、显示复用、清理顺序和失败语义。
Original summary in English

Sourcery 摘要

新增可选的 VDM 支持,用于创建独立的后台虚拟显示屏,同时默认保留 Legacy 行为。

新功能:

  • 新增可配置的虚拟显示后端设置,默认使用 Legacy;在 Android 14 及更高版本上支持实验性的 VDM。
  • 将 VDM 显示屏置于独立的显示组中,使其在物理屏幕关闭后仍保持活动状态。

错误修复:

  • 避免 VDM 启动失败时静默回退到 Legacy 后端。

增强:

  • 通过所选的活动显示后端处理输入、应用移动、过时帧处理、分辨率、复用和清理。
  • 在遥测数据中公开所选后端和兼容性级别,并在显示屏处于活动状态时禁止更改后端。

文档:

  • 更新遥测文档,纳入虚拟显示后端的相关内容。

测试:

  • 添加行为测试,涵盖特定 Android 版本的关联命令、配置文件迁移、权限级别和后端默认值。
Original summary in English

Summary by Sourcery

Add optional VDM support for independent background virtual displays while preserving Legacy behavior by default.

New Features:

  • Add a configurable virtual-display backend setting, with Legacy as the default and experimental VDM support on Android 14 and newer.
  • Keep VDM displays in an independent display group to remain active when the physical screen is turned off.

Bug Fixes:

  • Prevent VDM startup failures from silently falling back to the Legacy backend.

Enhancements:

  • Route input, app movement, stale-frame handling, resolution, reuse, and cleanup through the selected active display backend.
  • Expose the selected backend and compatibility tier in telemetry and prevent backend changes while a display is active.

Documentation:

  • Update telemetry documentation to include the virtual-display backend.

Tests:

  • Add behavior tests covering Android-version-specific association commands, profile migration, permission tiers, and backend defaults.

- 设置项默认 LEGACY,Android 14+ 可选 VDM;一次任务只用一个后端,VDM 失败不回退 Legacy
- VDM 通过 VirtualDeviceManager + CompanionDevice association 获取独立 display group;
  提权服务保持 shell(2000),单 session,Association 命令按 Android 14/15/16 区分
- 校验 displayGroupId > 0 与 Display.STATE_ON;失败按 VirtualDisplay -> VirtualDevice -> Association 逆序清理
- 同后端启动复用已建显示,切换后端先释放旧显示再重建;R8 不新增 keep
- 补充 VdmDisplayController / AppSettings 单测
- canReuse 辅助函数按后端派生默认 displayId,消除五参数重复书写
- 合并版本兼容、资源清理与启动分流的重复用例(15 -> 9)
- 保留清理顺序/失败继续/suppressed、VDM 不回退(未请求 VDM、Android 13、14 成功、14 失败)等关键覆盖
- 清理失败可能用到的日志 Mock 仅在对应用例内挂载并在 finally 卸载
- RemoteService.aidl:保留上游 getProcessExitRecords = 56,VDM 后端事务改为 57
- RemoteServiceImpl:采用上游 cleanupStep 结构并加入 virtual display 清理步骤;移除上游已删除的 heartbeat 字段与看门狗引用
@sourcery-ai

sourcery-ai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

评审者指南

本 PR 在保持 Legacy 默认行为不变的前提下,引入 Android 14+ 可选 VDM 后端:设置层持久化选择,任务启动经 AIDL 传递至提权服务,由 VDM 控制器管理 association、VirtualDevice 与 VirtualDisplay 的完整生命周期,并通过独立 display group 校验、严格清理、显示复用和活动 displayId 统一使用保证后台任务在灭屏场景下继续运行。

选择并启动 VDM 虚拟显示后端的时序图

sequenceDiagram
    actor User
    participant SettingsViewModel
    participant AppSettingsManager
    participant MaaCompositionService
    participant RemoteServiceImpl
    participant VdmDisplayController
    participant AndroidSystem

    User->>SettingsViewModel: setVirtualDisplayBackend(VDM)
    SettingsViewModel->>AppSettingsManager: setVirtualDisplayBackend(VDM)
    AppSettingsManager-->>SettingsViewModel: virtualDisplayBackend = VDM
    MaaCompositionService->>RemoteServiceImpl: setUseVirtualDeviceDisplay(true, userId)
    RemoteServiceImpl-->>MaaCompositionService: true
    MaaCompositionService->>RemoteServiceImpl: startVirtualDisplay()
    RemoteServiceImpl->>VdmDisplayController: start(userId)
    VdmDisplayController->>AndroidSystem: cmd companiondevice associate
    AndroidSystem-->>VdmDisplayController: AssociationInfo
    VdmDisplayController->>AndroidSystem: createVirtualDevice
    AndroidSystem-->>VdmDisplayController: VirtualDevice
    VdmDisplayController->>AndroidSystem: createVirtualDisplay
    AndroidSystem-->>VdmDisplayController: VirtualDisplay
    VdmDisplayController->>VdmDisplayController: awaitDisplayReady()
    VdmDisplayController-->>RemoteServiceImpl: displayId
    RemoteServiceImpl-->>MaaCompositionService: displayId
    MaaCompositionService->>MaaCompositionService: asyncConnect(displayId)
Loading

虚拟显示复用与后端切换的时序图

sequenceDiagram
    participant MaaCompositionService
    participant RemoteServiceImpl
    participant VdmDisplayController
    participant VirtualDisplayManager

    MaaCompositionService->>RemoteServiceImpl: setUseVirtualDeviceDisplay(enabled, userId)
    alt Active display uses the same backend
        RemoteServiceImpl-->>MaaCompositionService: true
        MaaCompositionService->>RemoteServiceImpl: startVirtualDisplay()
        RemoteServiceImpl->>VdmDisplayController: start(userId)
        VdmDisplayController-->>RemoteServiceImpl: existing displayId
    else Backend changed
        RemoteServiceImpl->>RemoteServiceImpl: stopVirtualDisplay()
        alt Previous backend is VDM
            RemoteServiceImpl->>VdmDisplayController: stop()
        else Previous backend is Legacy
            RemoteServiceImpl->>VirtualDisplayManager: stop()
        end
        RemoteServiceImpl-->>MaaCompositionService: true
        MaaCompositionService->>RemoteServiceImpl: startVirtualDisplay()
        alt Requested backend is VDM
            RemoteServiceImpl->>VdmDisplayController: start(userId)
        else Requested backend is Legacy
            RemoteServiceImpl->>VirtualDisplayManager: start()
        end
        RemoteServiceImpl-->>MaaCompositionService: displayId
    end
Loading

VDM 失败与逆序清理的时序图

sequenceDiagram
    participant RemoteServiceImpl
    participant VdmDisplayController
    participant AndroidSystem

    RemoteServiceImpl->>VdmDisplayController: start(userId)
    VdmDisplayController->>AndroidSystem: createVirtualDisplay
    AndroidSystem-->>VdmDisplayController: failure
    VdmDisplayController->>VdmDisplayController: cleanupFailure()
    VdmDisplayController->>AndroidSystem: releaseDisplay
    VdmDisplayController->>AndroidSystem: closeVirtualDevice
    VdmDisplayController->>AndroidSystem: removeAssociation
    VdmDisplayController-->>RemoteServiceImpl: exception
    RemoteServiceImpl->>RemoteServiceImpl: startBackgroundDisplay()
    RemoteServiceImpl-->>RemoteServiceImpl: DISPLAY_NONE
    RemoteServiceImpl-->>RemoteServiceImpl: no Legacy fallback
Loading

文件级变更

变更 详情 文件
增加可配置的 Legacy/VDM 虚拟屏后端,并将 VDM 仅用于 Android 14+ 后台任务。
  • 新增默认值为 Legacy 的持久化设置、枚举、ViewModel、设置页选项与搜索项,并按系统版本隐藏 VDM。
  • 启动任务时将后端选择传入提权远程服务;VDM 创建失败直接终止本轮,不回退 Legacy。
  • 通过 AIDL 新增后端切换接口,在一次任务内复用同后端显示,切换后先释放旧显示再创建新显示。
app/src/main/aidl/com/aliothmoon/maameow/RemoteService.aidl
app/src/main/java/com/aliothmoon/maameow/data/preferences/AppSettingsManager.kt
app/src/main/java/com/aliothmoon/maameow/domain/models/AppSettings.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/MaaCompositionService.kt
app/src/main/java/com/aliothmoon/maameow/presentation/search/SettingSearch.kt
app/src/main/java/com/aliothmoon/maameow/presentation/search/SettingSearchUi.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt
app/src/main/java/com/aliothmoon/maameow/presentation/viewmodel/SettingsViewModel.kt
实现由提权主服务持有的 VDM 显示生命周期及 Android 版本兼容逻辑。
  • 使用 shell 身份通过 CompanionDevice association 创建或迁移关联,再反射调用 VirtualDeviceManager/VirtualDevice 创建显示。
  • 按 Android 14/15/16 差异生成 association 命令,并在 Android 15+ 使用 APP_STREAMING profile 清理旧 profile。
  • 设置 TRUSTED 等显示 flags,校验 display group 独立性与 ON 状态;失败和停止均按显示、设备、关联逆序清理且不中断后续清理。
  • 维护最多一个 VDM session,复用关联与显示;支持分辨率更新、always-unlocked 参数失败后的 Android 16+ 重试及超时清理。
app/src/main/java/com/aliothmoon/maameow/remote/internal/VdmDisplayController.kt
app/src/main/java/com/aliothmoon/maameow/third/wrappers/DisplayManager.java
统一活动显示 ID 的使用,确保 Legacy 与 VDM 在输入、应用移动和残影保护中行为一致。
  • 远程服务按活动后端分流启动和停止虚拟屏,并在后端变化时释放旧资源。
  • 触控、应用所在显示检测、应用移动及 StaleFrameGuard 均改用本轮活动 displayId。
  • 保留 Legacy VirtualDisplayManager 路径不变,并在 VDM 停止时同步释放捕获 surface 与相关状态。
app/src/main/java/com/aliothmoon/maameow/remote/RemoteServiceImpl.kt
app/src/main/java/com/aliothmoon/maameow/remote/internal/StaleFrameGuard.kt
补充后端分流、复用、版本命令和清理失败语义的行为测试及用户提示文案。
  • 覆盖默认 Legacy、Android 版本命令差异、profile 迁移、显示复用、清理顺序与后续清理继续执行。
  • 断言 VDM 成功使用 VDM、失败返回无显示且绝不启动 Legacy。
  • 新增中英文设置说明和 VDM 创建失败提示。
app/src/test/java/com/aliothmoon/maameow/domain/models/AppSettingsTest.kt
app/src/test/java/com/aliothmoon/maameow/remote/internal/VdmDisplayControllerTest.kt
app/src/main/res/values/strings.xml
app/src/main/res/values-en/strings.xml

提示与命令

与 Sourcery 交互

  • 触发新的评审: 在 pull request 中评论 @sourcery-ai review。
  • 继续讨论: 直接回复 Sourcery 的评审评论。
  • 根据评审评论创建 GitHub issue: 回复评审评论,请 Sourcery 根据该评论创建 issue。也可以在评审评论下回复 @sourcery-ai issue 来创建 issue。
  • 生成 pull request 标题: 在 pull request 标题的任意位置写入 @sourcery-ai,即可随时生成标题。也可以在 pull request 中评论 @sourcery-ai title,随时重新生成标题。
  • 生成 pull request 摘要: 在 pull request 正文中想要插入摘要的位置写入 @sourcery-ai summary,即可随时生成 PR 摘要。也可以在 pull request 中评论 @sourcery-ai summary,随时重新生成摘要。
  • 生成评审者指南: 在 pull request 中评论 @sourcery-ai guide,即可随时重新生成评审者指南。
  • 解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可解决所有 Sourcery 评论。如果你已经处理完所有评论且不想再看到它们,此功能会很有用。
  • 忽略所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,即可忽略所有现有的 Sourcery 评审。如果你想从新的评审开始,这尤其有用——别忘了评论 @sourcery-ai review 来触发新的评审!

自定义使用体验

访问你的控制面板以:

  • 启用或禁用评审功能,例如 Sourcery 生成的 pull request 摘要、评审者指南等。
  • 更改评审语言。
  • 添加、删除或编辑自定义评审说明。
  • 调整其他评审设置。

获取帮助

Original review guide in English

Reviewer's Guide

本 PR 在保持 Legacy 默认行为不变的前提下,引入 Android 14+ 可选 VDM 后端:设置层持久化选择,任务启动经 AIDL 传递至提权服务,由 VDM 控制器管理 association、VirtualDevice 与 VirtualDisplay 的完整生命周期,并通过独立 display group 校验、严格清理、显示复用和活动 displayId 统一使用保证后台任务在灭屏场景下继续运行。

Sequence diagram for selecting and starting the VDM virtual display backend

sequenceDiagram
    actor User
    participant SettingsViewModel
    participant AppSettingsManager
    participant MaaCompositionService
    participant RemoteServiceImpl
    participant VdmDisplayController
    participant AndroidSystem

    User->>SettingsViewModel: setVirtualDisplayBackend(VDM)
    SettingsViewModel->>AppSettingsManager: setVirtualDisplayBackend(VDM)
    AppSettingsManager-->>SettingsViewModel: virtualDisplayBackend = VDM
    MaaCompositionService->>RemoteServiceImpl: setUseVirtualDeviceDisplay(true, userId)
    RemoteServiceImpl-->>MaaCompositionService: true
    MaaCompositionService->>RemoteServiceImpl: startVirtualDisplay()
    RemoteServiceImpl->>VdmDisplayController: start(userId)
    VdmDisplayController->>AndroidSystem: cmd companiondevice associate
    AndroidSystem-->>VdmDisplayController: AssociationInfo
    VdmDisplayController->>AndroidSystem: createVirtualDevice
    AndroidSystem-->>VdmDisplayController: VirtualDevice
    VdmDisplayController->>AndroidSystem: createVirtualDisplay
    AndroidSystem-->>VdmDisplayController: VirtualDisplay
    VdmDisplayController->>VdmDisplayController: awaitDisplayReady()
    VdmDisplayController-->>RemoteServiceImpl: displayId
    RemoteServiceImpl-->>MaaCompositionService: displayId
    MaaCompositionService->>MaaCompositionService: asyncConnect(displayId)
Loading

Sequence diagram for virtual display reuse and backend switching

sequenceDiagram
    participant MaaCompositionService
    participant RemoteServiceImpl
    participant VdmDisplayController
    participant VirtualDisplayManager

    MaaCompositionService->>RemoteServiceImpl: setUseVirtualDeviceDisplay(enabled, userId)
    alt Active display uses the same backend
        RemoteServiceImpl-->>MaaCompositionService: true
        MaaCompositionService->>RemoteServiceImpl: startVirtualDisplay()
        RemoteServiceImpl->>VdmDisplayController: start(userId)
        VdmDisplayController-->>RemoteServiceImpl: existing displayId
    else Backend changed
        RemoteServiceImpl->>RemoteServiceImpl: stopVirtualDisplay()
        alt Previous backend is VDM
            RemoteServiceImpl->>VdmDisplayController: stop()
        else Previous backend is Legacy
            RemoteServiceImpl->>VirtualDisplayManager: stop()
        end
        RemoteServiceImpl-->>MaaCompositionService: true
        MaaCompositionService->>RemoteServiceImpl: startVirtualDisplay()
        alt Requested backend is VDM
            RemoteServiceImpl->>VdmDisplayController: start(userId)
        else Requested backend is Legacy
            RemoteServiceImpl->>VirtualDisplayManager: start()
        end
        RemoteServiceImpl-->>MaaCompositionService: displayId
    end
Loading

Sequence diagram for VDM failure and reverse-order cleanup

sequenceDiagram
    participant RemoteServiceImpl
    participant VdmDisplayController
    participant AndroidSystem

    RemoteServiceImpl->>VdmDisplayController: start(userId)
    VdmDisplayController->>AndroidSystem: createVirtualDisplay
    AndroidSystem-->>VdmDisplayController: failure
    VdmDisplayController->>VdmDisplayController: cleanupFailure()
    VdmDisplayController->>AndroidSystem: releaseDisplay
    VdmDisplayController->>AndroidSystem: closeVirtualDevice
    VdmDisplayController->>AndroidSystem: removeAssociation
    VdmDisplayController-->>RemoteServiceImpl: exception
    RemoteServiceImpl->>RemoteServiceImpl: startBackgroundDisplay()
    RemoteServiceImpl-->>RemoteServiceImpl: DISPLAY_NONE
    RemoteServiceImpl-->>RemoteServiceImpl: no Legacy fallback
Loading

File-Level Changes

Change Details Files
增加可配置的 Legacy/VDM 虚拟屏后端,并将 VDM 仅用于 Android 14+ 后台任务。
  • 新增默认值为 Legacy 的持久化设置、枚举、ViewModel、设置页选项与搜索项,并按系统版本隐藏 VDM。
  • 启动任务时将后端选择传入提权远程服务;VDM 创建失败直接终止本轮,不回退 Legacy。
  • 通过 AIDL 新增后端切换接口,在一次任务内复用同后端显示,切换后先释放旧显示再创建新显示。
app/src/main/aidl/com/aliothmoon/maameow/RemoteService.aidl
app/src/main/java/com/aliothmoon/maameow/data/preferences/AppSettingsManager.kt
app/src/main/java/com/aliothmoon/maameow/domain/models/AppSettings.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/MaaCompositionService.kt
app/src/main/java/com/aliothmoon/maameow/presentation/search/SettingSearch.kt
app/src/main/java/com/aliothmoon/maameow/presentation/search/SettingSearchUi.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt
app/src/main/java/com/aliothmoon/maameow/presentation/viewmodel/SettingsViewModel.kt
实现由提权主服务持有的 VDM 显示生命周期及 Android 版本兼容逻辑。
  • 使用 shell 身份通过 CompanionDevice association 创建或迁移关联,再反射调用 VirtualDeviceManager/VirtualDevice 创建显示。
  • 按 Android 14/15/16 差异生成 association 命令,并在 Android 15+ 使用 APP_STREAMING profile 清理旧 profile。
  • 设置 TRUSTED 等显示 flags,校验 display group 独立性与 ON 状态;失败和停止均按显示、设备、关联逆序清理且不中断后续清理。
  • 维护最多一个 VDM session,复用关联与显示;支持分辨率更新、always-unlocked 参数失败后的 Android 16+ 重试及超时清理。
app/src/main/java/com/aliothmoon/maameow/remote/internal/VdmDisplayController.kt
app/src/main/java/com/aliothmoon/maameow/third/wrappers/DisplayManager.java
统一活动显示 ID 的使用,确保 Legacy 与 VDM 在输入、应用移动和残影保护中行为一致。
  • 远程服务按活动后端分流启动和停止虚拟屏,并在后端变化时释放旧资源。
  • 触控、应用所在显示检测、应用移动及 StaleFrameGuard 均改用本轮活动 displayId。
  • 保留 Legacy VirtualDisplayManager 路径不变,并在 VDM 停止时同步释放捕获 surface 与相关状态。
app/src/main/java/com/aliothmoon/maameow/remote/RemoteServiceImpl.kt
app/src/main/java/com/aliothmoon/maameow/remote/internal/StaleFrameGuard.kt
补充后端分流、复用、版本命令和清理失败语义的行为测试及用户提示文案。
  • 覆盖默认 Legacy、Android 版本命令差异、profile 迁移、显示复用、清理顺序与后续清理继续执行。
  • 断言 VDM 成功使用 VDM、失败返回无显示且绝不启动 Legacy。
  • 新增中英文设置说明和 VDM 创建失败提示。
app/src/test/java/com/aliothmoon/maameow/domain/models/AppSettingsTest.kt
app/src/test/java/com/aliothmoon/maameow/remote/internal/VdmDisplayControllerTest.kt
app/src/main/res/values/strings.xml
app/src/main/res/values-en/strings.xml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你好 - 我发现了 1 个问题

AI 代理提示
请处理此代码审查中的评论:

## 单条评论

### 评论 1
<location path="app/src/main/java/com/aliothmoon/maameow/remote/internal/VdmDisplayController.kt" line_range="108" />
<code_context>
+    @Synchronized
+    fun stop() {
+        val active = session ?: return
+        session = null
+        val displayId = active.display.display.displayId
+        val failure = cleanupInReverseOrder(
</code_context>
<issue_to_address>
**清理失败后无法重试**

在 `stop` 期间,如果释放显示器、关闭虚拟设备或移除关联失败,`stop` 会在清理完成前清除 `session`,因此后续调用无法重试失败的清理步骤。VDM 资源或关联会残留,并可能影响后续启动。

在每项资源成功释放之前保留清理失败状态,以便后续调用 `stop` 时可以重试。
</issue_to_address>

Sourcery 评估

需要人工审查。 有 1 个问题需要优先处理;此外,选择启用的 VDM 路径会创建可信虚拟显示器和系统伴侣关联;生命周期缺陷可能导致该关联在应用回滚后仍然残留。残留状态范围有限且可以移除,但仅回滚可能无法清理它。

阻塞性问题:app/src/main/java/com/aliothmoon/maameow/remote/internal/VdmDisplayController.kt:108


Sourcery 对开源项目免费——如果您喜欢我们的审查,欢迎分享 ✨
Original comment in English

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="app/src/main/java/com/aliothmoon/maameow/remote/internal/VdmDisplayController.kt" line_range="108" />
<code_context>
+    @Synchronized
+    fun stop() {
+        val active = session ?: return
+        session = null
+        val displayId = active.display.display.displayId
+        val failure = cleanupInReverseOrder(
</code_context>
<issue_to_address>
**Failed cleanup cannot be retried**

When display release, virtual-device close, or association removal fails during `stop`, `stop` clears `session` before cleanup finishes, so a later call cannot retry the failed cleanup step. The VDM resource or association remains behind and can interfere with later starts.

Retain failed cleanup state until each resource is successfully released, so later `stop` calls can retry it.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and the opt-in VDM path creates a trusted virtual display and a system companion association; a lifecycle defect could leave that association behind even after the app is reverted. The leftover state is bounded and can be removed, but reverting alone may not clean it up.

Blocking findings: app/src/main/java/com/aliothmoon/maameow/remote/internal/VdmDisplayController.kt:108


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread app/src/main/java/com/aliothmoon/maameow/remote/internal/VdmDisplayController.kt Outdated
- 抽取 startBackgroundDisplay 后,VdmDisplayController.start 的调用点失去 lint 可见的 SDK 守卫
- 新增私有 startVdmDisplay() 显式判断 Build.VERSION.SDK_INT >= 34
- 与 startBackgroundDisplay 内部的 sdkInt 判断形成双重运行时保护
@Aliothmoon

Copy link
Copy Markdown
Owner

看起来是 display group id 分配的问题?看看最新提交的构建看看调试日志里的 display group id 呢,但是按理来说都是应该需要 trusted display 的权限,可以先试试看?

@3265204

3265204 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

Screenshot_20261020_144546_ANE  Android Native Explorer.jpg

似乎看上去没什么问题?这个组成功被分配了

@Aliothmoon

Copy link
Copy Markdown
Owner

Screenshot_20261020_144546_ANE Android Native Explorer.jpg

似乎看上去没什么问题?这个组成功被分配了

嗯,我的意思是现在main分支上那个不行吗,可以再试试?

@3265204

3265204 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

1.VirtualDeviceManager 配合 FLAG_TRUSTED 创建受系统认可的可信显示器。
2.Android 根据可信状态和 VirtualDevice 关联,自动分配独立 Display Group。

现在是这样做的,还有什么能优化的吗?

@Aliothmoon

Copy link
Copy Markdown
Owner

1.VirtualDeviceManager 配合 FLAG_TRUSTED 创建受系统认可的可信显示器。 2.Android 根据可信状态和 VirtualDevice 关联,自动分配独立 Display Group。

现在是这样做的,还有什么能优化的吗?

我的意思是让你直接测试一下新的主分支的代码,我没有你那个测试环境可以复现问题,我想看看主分支上的是不是没办法自动分配新的 display group id

@3265204

3265204 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

能打个包吗?我这里没电脑不好打包

@Aliothmoon

Copy link
Copy Markdown
Owner

能打个包吗?我这里没电脑不好打包

https://github.com/Aliothmoon/MAA-Meow/actions/runs/39362542855/artifacts/12237419314

@3265204

3265204 commented Oct 20, 2026

Copy link
Copy Markdown
Contributor Author

原始日志:

VD created: id=46, configured=1280x720, actual=1280x720, rotation=0, flags=0x1fd49, tier=0

VD effective: group=0, flags=[TRUSTED, OWN_DISPLAY_GROUP, ALWAYS_UNLOCKED, TOUCH_FEEDBACK_DISABLED, OWN_FOCUS, STEAL_TOP_FOCUS_DISABLED]

VD requested OWN_DISPLAY_GROUP but landed in group 0

看上去发生在系统的 LogicalDisplay 分组阶段,而非 VirtualDisplay 创建权限检查阶段。

Legacy 同时设置了 OWN_DISPLAY_GROUP 和 DEVICE_DISPLAY_GROUP,但并未关联 VirtualDevice。可以考虑先单独移除 DEVICE_DISPLAY_GROUP,观察实际分组是否变化。

另外,Android 16 的 LogicalDisplayMapper 存在预先决定显示组的路径,也值得检查 separate_timeouts 等策略是否影响 OWN_DISPLAY_GROUP 的最终分配结果。不过,目前尚未确认 One UI 实际进入了哪条分支。

因此,aa9193f 的 SecurityException fallback 应该无法解决当前问题。建议优先定位为什么 Legacy 请求独立显示组后仍然得到 Group 0,再决定是否只能通过 VDM 实现独立分组。

手机只能测成这样了 再测就要用 MAC 了,等回头看吧

- RemoteService.aidl:57 已被 getVirtualDisplayFlagTier 占用,setUseVirtualDeviceDisplay 顺延到 58
- DisplayManager.getDisplayGroupId 沿用 main 的实现(取不到返回 -1)
- MaaCompositionService:保留 VDM 失败文案,同时接上 main 的 flag tier 上报
- RemoteServiceImpl:去掉 main 已删除的 VirtualDisplayManager.setMonitorSurface 调用
- values-ko 补上 VDM 新增的 5 条文案
- 新增 BackgroundDisplay 统一分流 Legacy / VDM,去掉 RemoteServiceImpl 与 StaleFrameGuard 里的平行状态
- 运行时不允许切换虚拟屏模式:提权侧有活动后台屏就拒绝;设置项运行中置灰,空闲时先关旧屏再保存
- VDM 换分辨率时重建显示,复用前校验 display 仍有效
- VDM 按 锁屏常亮 × trusted 四档降级,补 TOUCH_FEEDBACK_DISABLED / DESTROY_CONTENT_ON_REMOVAL,旋转修正与 Legacy 共用
- 关联固定在 shell 所在的 user 0,去掉 userId 传参;紧急清理不再同步拆关联
- 反射收进 third/wrappers 的 CompanionDeviceManager / VirtualDeviceManager,命令走 RemoteUtils.shellExec
- setVirtualDisplayMode 加锁;遥测补 vd.backend 标签与 VDM_DISPLAY_ERROR
- 设置项改名「虚拟屏模式」并移到后台运行最下方,补齐 ko 文案
@Aliothmoon
Aliothmoon merged commit 95b27c5 into Aliothmoon:main Oct 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants