Skip to content

Conversation

@MistEO
Copy link
Member

@MistEO MistEO commented Dec 6, 2025

Summary by Sourcery

在框架、CLI、构建系统和文档中新增并集成原生 Android 控制器实现。

新功能:

  • 引入 AndroidControlUnitAndroidControlUnitMgr,通过辅助功能(accessibility)和 MediaProjection 提供用于输入和截图的原生 Android 控制器。
  • 新增 MaaAndroidControllerCreate API、Android 专用的截图与输入方式枚举,并通过 C++ 模块接口和 CLI 运行时配置对外暴露。
  • 实现一个基于 Kotlin 的 Android 库(AAR),内含辅助服务(accessibility service)、MediaProjection 支持,以及被原生 Android 控制单元使用的 NativeBridge

增强:

  • 将 Android 控制器接入 MaaPiCli 的配置器、运行器和交互器,以便选择并运行原生 Android 控制器。
  • 扩展控制单元库持有者(control unit library holder)基础设施和控制器句柄类型定义(controller handle typedefs),以在现有 ADB 和 Win32 控制器之外同时支持 Android 控制单元。

构建:

  • 新增可选的 WITH_ANDROID_CONTROLLER CMake 标志、Android 专用的 MaaAndroidControlUnit 目标,并在 Android 平台构建时将其链接进主构建。
  • 更新 CI 构建与测试工作流,以安装 Java/Gradle,构建 Android Kotlin 库 AAR,并将其打包到构建产物中。

文档:

  • 在英文版的控制方法(control-method)和接口总览(interface overview)文档中,补充 Android 控制方法、枚举以及 MaaAndroidControllerCreate 的使用说明。
Original summary in English

Summary by Sourcery

Add a native Android controller implementation and integrate it into the framework, CLI, build system, and documentation.

New Features:

  • Introduce AndroidControlUnit and AndroidControlUnitMgr to provide a native Android controller using accessibility and MediaProjection for input and screenshots.
  • Add MaaAndroidControllerCreate API, Android-specific screencap and input method enums, and expose them through the C++ module interface and CLI runtime configuration.
  • Implement a Kotlin-based Android library (AAR) with an accessibility service, MediaProjection support, and a NativeBridge used by the native Android control unit.

Enhancements:

  • Wire the Android controller into the MaaPiCli configurator, runner, and interactor to allow selecting and running the native Android controller.
  • Extend the control unit library holder infrastructure and controller handle typedefs to support the Android control unit alongside existing ADB and Win32 controllers.

Build:

  • Add optional WITH_ANDROID_CONTROLLER CMake flag, Android-only MaaAndroidControlUnit target, and link it into the main build when running on Android.
  • Update CI build and test workflows to install Java/Gradle, build the Android Kotlin library AAR, and package it in artifacts.

Documentation:

  • Document Android control methods, enums, and MaaAndroidControllerCreate usage in both English control-method and interface overview guides.

Copilot AI review requested due to automatic review settings December 6, 2025 08:27
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Dec 6, 2025

Reviewer's Guide

添加一个原生 Android 控制单元(controller)实现,它基于 Android 控制单元共享库和 Kotlin 无障碍/MediaProjection 桥接;将其接入框架 API/CLI/类型系统,并更新构建/测试工作流以构建和打包 Android 工件。

创建和使用 Android 控制单元的时序图

sequenceDiagram
    actor User
    participant Cli as MaaPiCli_Runner
    participant Framework as MaaAndroidControllerCreate
    participant LibHolder as AndroidControlUnitLibraryHolder
    participant So as MaaAndroidControlUnit_so
    participant CU as AndroidControlUnitMgr
    participant JVM as JVM_ART
    participant Bridge as NativeBridge
    participant AccSvc as MaaAccessibilityService

    User->>Cli: run(param with AndroidParam)
    Cli->>Framework: MaaAndroidControllerCreate(screencap_methods, input_methods)
    activate Framework
    Framework->>LibHolder: create_control_unit(screencap_methods, input_methods)
    activate LibHolder
    LibHolder->>So: MaaAndroidControlUnitCreate(screencap_methods, input_methods)
    activate So
    So->>So: new AndroidControlUnitMgr
    So-->>LibHolder: AndroidControlUnitHandle
    deactivate So
    LibHolder-->>Framework: shared_ptr AndroidControlUnitAPI
    deactivate LibHolder
    Framework->>Framework: new ControllerAgent(control_unit)
    Framework-->>Cli: MaaController*
    deactivate Framework

    Note over Cli,Cli: Later, during controller initialization
    Cli->>CU: connect()
    activate CU
    CU->>JVM: ensure_env()
    CU->>Bridge: init(screencap_methods, input_methods)
    CU->>Bridge: connect()
    activate Bridge
    Bridge->>Bridge: verify accessibility/media_projection availability
    Bridge-->>CU: true/false
    deactivate Bridge
    CU-->>Cli: connect result
    deactivate CU

    Note over CU,Bridge: During operations (example: screencap)
    Cli->>CU: screencap(image)
    activate CU
    CU->>Bridge: screencap()
    activate Bridge
    alt MediaProjection enabled and available
        Bridge->>Bridge: mediaProjectionHolder.capture()
    else Accessibility screenshot fallback
        Bridge->>AccSvc: screencap()
        activate AccSvc
        AccSvc-->>Bridge: Bitmap
        deactivate AccSvc
    end
    Bridge-->>CU: Bitmap
    deactivate Bridge
    CU-->>Cli: fill cv::Mat from Bitmap
    deactivate CU
Loading

新增 Android 控制单元与 Kotlin 桥接的类图

classDiagram
    direction LR

    class ControlUnitAPI {
        <<abstract>>
        +connect() bool
        +request_uuid(uuid string) bool
        +get_features() MaaControllerFeature
        +start_app(intent string) bool
        +stop_app(intent string) bool
        +screencap(image cv_Mat) bool
        +click(x int, y int) bool
        +swipe(x1 int, y1 int, x2 int, y2 int, duration int) bool
        +touch_down(contact int, x int, y int, pressure int) bool
        +touch_move(contact int, x int, y int, pressure int) bool
        +touch_up(contact int) bool
        +click_key(key int) bool
        +input_text(text string) bool
        +key_down(key int) bool
        +key_up(key int) bool
        +scroll(dx int, dy int) bool
    }

    class AndroidControlUnitAPI {
        <<abstract>>
    }

    class AndroidControlUnitMgr {
        -screencap_methods_ MaaAndroidScreencapMethod
        -input_methods_ MaaAndroidInputMethod
        -vm_ JavaVM*
        -bridge_cls_ jclass
        +AndroidControlUnitMgr(screencap_methods MaaAndroidScreencapMethod, input_methods MaaAndroidInputMethod)
        +connect() bool
        +request_uuid(uuid string) bool
        +get_features() MaaControllerFeature
        +start_app(intent string) bool
        +stop_app(intent string) bool
        +screencap(image cv_Mat) bool
        +click(x int, y int) bool
        +swipe(x1 int, y1 int, x2 int, y2 int, duration int) bool
        +touch_down(contact int, x int, y int, pressure int) bool
        +touch_move(contact int, x int, y int, pressure int) bool
        +touch_up(contact int) bool
        +click_key(key int) bool
        +input_text(text string) bool
        +key_down(key int) bool
        +key_up(key int) bool
        +scroll(dx int, dy int) bool
        -ensure_env() JNIEnv*
        -bridge_class() jclass
        -call_bool(name char*, sig char*, caller Callable) bool
    }

    class AndroidControlUnitLibraryHolder {
        +create_control_unit(screencap_methods MaaAndroidScreencapMethod, input_methods MaaAndroidInputMethod) shared_ptr~AndroidControlUnitAPI~
        -libname_ filesystem_path
        -version_func_name_ string
        -create_func_name_ string
        -destroy_func_name_ string
    }

    class MaaAndroidControlUnit_API {
        +MaaAndroidControlUnitGetVersion() const_char_ptr
        +MaaAndroidControlUnitCreate(screencap_methods MaaAndroidScreencapMethod, input_methods MaaAndroidInputMethod) MaaAndroidControlUnitHandle
        +MaaAndroidControlUnitDestroy(handle MaaAndroidControlUnitHandle) void
    }

    class MaaController {
    }

    class ControllerAgent {
        +ControllerAgent(control_unit shared_ptr~ControlUnitAPI~)
    }

    class MaaFramework_API {
        +MaaAndroidControllerCreate(screencap_methods MaaAndroidScreencapMethod, input_methods MaaAndroidInputMethod) MaaController*
    }

    class RuntimeParam {
        +controller_param variant
    }

    class AndroidParam {
        +screencap MaaAndroidScreencapMethod
        +input MaaAndroidInputMethod
    }

    class Runner {
        +run(param RuntimeParam) bool
    }

    class NativeBridge {
        <<object>>
        +SCREENCAP_METHOD_NONE long
        +SCREENCAP_METHOD_ACCESSIBILITY long
        +SCREENCAP_METHOD_MEDIA_PROJECTION long
        +INPUT_METHOD_NONE long
        +INPUT_METHOD_ACCESSIBILITY long
        -accessibilityService MaaAccessibilityService
        -mediaProjectionHolder MediaProjectionHolder
        -screencapMethods long
        -inputMethods long
        +attachAccessibilityService(service MaaAccessibilityService) void
        +detachAccessibilityService(service MaaAccessibilityService) void
        +attachMediaProjection(holder MediaProjectionHolder) void
        +detachMediaProjection() void
        +init(screencapMethods long, inputMethods long) bool
        +connect() bool
        +requestUuid() String
        +startApp(intent String) Boolean
        +stopApp(intent String) Boolean
        +tap(x Int, y Int) Boolean
        +swipe(x1 Int, y1 Int, x2 Int, y2 Int, duration Int) Boolean
        +scroll(dx Int, dy Int) Boolean
        +keyEvent(key Int, down Boolean) Boolean
        +inputText(text String) Boolean
        +screencap() Bitmap
        +getScreenSize() IntArray
    }

    class MaaAccessibilityService {
        +onServiceConnected() void
        +onUnbind(intent Intent) Boolean
        +onAccessibilityEvent(event AccessibilityEvent) void
        +onInterrupt() void
        +uuid() String
        +getScreenSize() IntArray
        +startApp(intentUri String) Boolean
        +stopApp(intentUri String) Boolean
        +tap(x Int, y Int) Boolean
        +swipe(x1 Int, y1 Int, x2 Int, y2 Int, duration Int) Boolean
        +scroll(dx Int, dy Int) Boolean
        +keyEvent(key Int, down Boolean) Boolean
        +inputText(text String) Boolean
        +screencap() Bitmap
    }

    class MediaProjectionHolder {
        -context Context
        -mediaProjection MediaProjection
        -virtualDisplay VirtualDisplay
        -imageReader ImageReader
        -handlerThread HandlerThread
        -handler Handler
        +MediaProjectionHolder(context Context, mediaProjection MediaProjection)
        +capture() Bitmap
        +release() void
        -initScreen() void
        -setupImageReader() void
        -imageToBitmap(image Image) Bitmap
    }

    class MediaProjectionActivity {
        +onCreate(savedInstanceState Bundle) void
        +onActivityResult(requestCode Int, resultCode Int, data Intent) void
        +onDestroy() void
        +requestPermission(context Context, callback Function) void
        -clearCallback() void
    }

    ControlUnitAPI <|-- AndroidControlUnitAPI
    AndroidControlUnitAPI <|-- AndroidControlUnitMgr

    AndroidControlUnitLibraryHolder ..> AndroidControlUnitAPI : creates
    AndroidControlUnitMgr ..> NativeBridge : uses_via_JNI

    MaaAndroidControlUnit_API ..> AndroidControlUnitMgr : returns_instance

    ControllerAgent ..> ControlUnitAPI : holds_shared_ptr
    MaaFramework_API ..> ControllerAgent : constructs

    Runner ..> RuntimeParam : uses
    RuntimeParam o-- AndroidParam
    Runner ..> MaaFramework_API : calls_MaaAndroidControllerCreate

    NativeBridge ..> MaaAccessibilityService : holds_reference
    NativeBridge ..> MediaProjectionHolder : holds_reference
    MaaAccessibilityService ..> NativeBridge : calls_attach_detach
    MediaProjectionActivity ..> MediaProjectionHolder : creates_and_attaches
    MediaProjectionActivity ..> NativeBridge : attachMediaProjection
Loading

文件级变更

Change Details Files
引入 Android 控制单元 API 和管理器,并将其接入通用控制单元加载路径。
  • 新增 AndroidControlUnitAPI 接口和 MaaAndroidControlUnitHandle typedef,使其与其他控制单元保持一致。
  • 实现 AndroidControlUnitMgr 作为一个 ControlUnitAPI,通过 JNI 调用 Java/Kotlin 的 NativeBridge 来完成连接、UUID、应用控制、输入、滚动以及截屏(包含位图格式处理和回收)。
  • 扩展 LibraryHolder,新增 AndroidControlUnitLibraryHolder,通过 dlopen 加载 MaaAndroidControlUnit,检查版本,并对外暴露可接受 Android 截屏/输入位掩码的 create_control_unit。
source/include/ControlUnit/ControlUnitAPI.h
source/include/ControlUnit/AndroidControlUnitAPI.h
source/MaaAndroidControlUnit/Manager/AndroidControlUnitMgr.h
source/MaaAndroidControlUnit/Manager/AndroidControlUnitMgr.cpp
source/LibraryHolder/ControlUnit/ControlUnit.cpp
source/include/LibraryHolder/ControlUnit.h
新增基于 C 的 Android 控制单元共享库,并将其接入 CMake 构建。
  • 创建 MaaAndroidControlUnit 共享库目标来构建管理器和 API,链接 MaaUtils、OpenCV、log、jnigraphics 和 android,仅在 Android 上启用。
  • 对外暴露 MaaAndroidControlUnitGetVersion、MaaAndroidControlUnitCreate 和 MaaAndroidControlUnitDestroy 函数,用于构造/销毁 AndroidControlUnitMgr 实例。
  • 当启用 WITH_ANDROID_CONTROLLER 时,将 MaaAndroidControlUnit 作为 LibraryHolder 和 MaaFramework 的依赖;并新增 WITH_ANDROID_CONTROLLER 选项,在非 Android 平台自动禁用。
source/MaaAndroidControlUnit/API/AndroidControlUnitAPI.cpp
source/MaaAndroidControlUnit/CMakeLists.txt
CMakeLists.txt
source/CMakeLists.txt
source/LibraryHolder/CMakeLists.txt
source/MaaFramework/CMakeLists.txt
通过公共框架和模块接口暴露 Android 控制单元类型和创建 API。
  • 在 MaaDef.h 中定义 MaaAndroidScreencapMethod 和 MaaAndroidInputMethod 位掩码枚举及默认值,并从 MaaFramework C++20 模块中导出它们。
  • 在框架 C API 中新增 MaaAndroidControllerCreate,在 Android 上构建时通过 library holder 创建 AndroidControlUnit,并返回一个 ControllerAgent。
  • 在英文控制方式和接口总览文档中记录 Android 输入/截屏枚举以及 MaaAndroidControllerCreate 的语义,同时也在中文文档中补充说明。
include/MaaFramework/MaaDef.h
include/MaaFramework/Instance/MaaController.h
source/MaaFramework/API/MaaFramework.cpp
source/modules/MaaFramework.cppm
docs/en_us/2.4-ControlMethods.md
docs/en_us/2.2-IntegratedInterfaceOverview.md
docs/zh_cn/2.4-控制方式说明.md
docs/zh_cn/2.2-集成接口总览.md
将 Android 控制单元集成到 CLI 的配置/运行时流程中。
  • 在 InterfaceData::Controller::Type 中增加 Android 变体,并新增 RuntimeParam::AndroidParam,同时更新 controller_param variant 以包含该变体。
  • 将 AndroidParam 的默认值设置为组合截屏默认值 + 无障碍输入。
  • 更新 Configurator 以创建 Android 运行时参数,更新 Runner 以使用这些参数调用 MaaAndroidControllerCreate,并在 CLI 交互模块中将 Android 暴露为可选择/可打印的控制单元类型。
source/include/ProjectInterface/Types.h
source/MaaPiCli/Impl/Configurator.cpp
source/MaaPiCli/Impl/Runner.cpp
source/MaaPiCli/CLI/interactor.cpp
新增 Kotlin Android 库,实现用于原生控制的无障碍和 MediaProjection 桥接。
  • 在 MaaAndroidControlUnit/java 下定义 Android library Gradle 工程,启用 Kotlin/Android 插件,包含 manifest、无障碍服务、半透明 MediaProjectionActivity 和 accessibility_service_config,构建为 AAR。
  • 实现 MaaAccessibilityService,处理手势、按键事件、文本输入,并在 Android 13+ 上使用 takeScreenshot(),同时支持 UUID 持久化和屏幕尺寸上报。
  • 实现 NativeBridge 单例,用于保存配置的截屏/输入位掩码,并将截屏、输入、按键事件、滚动及应用启动/停止委托给 MaaAccessibilityService 和 MediaProjectionHolder;额外实现 MediaProjectionHolder 用于虚拟显示截屏,以及 MediaProjectionActivity 用于请求/建立 MediaProjection。
  • 在 .github workflow 中新增步骤,安装 JDK/Gradle、构建 Kotlin AAR,并在 CI 工件中将其包含到 install/android 目录。
source/MaaAndroidControlUnit/java/build.gradle.kts
source/MaaAndroidControlUnit/java/settings.gradle.kts
source/MaaAndroidControlUnit/java/gradle.properties
source/MaaAndroidControlUnit/java/AndroidManifest.xml
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MaaAccessibilityService.kt
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/NativeBridge.kt
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MediaProjectionHolder.kt
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MediaProjectionActivity.kt
source/MaaAndroidControlUnit/java/res/xml/accessibility_service_config.xml
.github/workflows/build.yml
简化 Linux CI 软件包安装,并确保构建和测试工作流中都包含 Android 依赖。
  • 移除全局 apt 升级/删除步骤,将 apt-get update 移动到 Linux 任务的依赖安装步骤中。
  • 确保在 Linux 的构建和测试工作流中都安装 ninja、cmake 和 ccache。
  • 在 Linux 构建任务中安装 JDK 17 和 Gradle,以便在原生 Android 构建之前构建 Android Kotlin 库。
.github/workflows/build.yml
.github/workflows/test.yml

Tips and commands

Interacting with 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 摘要。你也可以评论 @sourcery-ai summary 来在任意时间(重新)生成摘要。
  • 生成 reviewer's guide: 在 pull request 中评论 @sourcery-ai guide,即可在任意时间(重新)生成评审指南。
  • 批量解决所有 Sourcery 评论: 在 pull request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。适用于你已经处理完所有评论且不再希望看到它们的情况。
  • 批量撤销所有 Sourcery 评审: 在 pull request 中评论 @sourcery-ai dismiss,即可撤销所有现有的 Sourcery 评审。尤其适用于你想从头开始新的评审——不要忘记随后再评论 @sourcery-ai review 触发新评审!

Customizing Your Experience

进入你的 dashboard 以:

  • 启用或禁用诸如 Sourcery 自动生成 PR 摘要、评审指南等评审功能。
  • 修改评审语言。
  • 添加、删除或编辑自定义评审指令。
  • 调整其他评审设置。

Getting Help

Original review guide in English

Reviewer's Guide

Adds a native Android controller implementation backed by an Android control unit shared library and Kotlin accessibility/MediaProjection bridge, wires it into the framework API/CLI/types, and updates build/test workflows to build and package the Android artifacts.

Sequence diagram for creating and using an Android controller

sequenceDiagram
    actor User
    participant Cli as MaaPiCli_Runner
    participant Framework as MaaAndroidControllerCreate
    participant LibHolder as AndroidControlUnitLibraryHolder
    participant So as MaaAndroidControlUnit_so
    participant CU as AndroidControlUnitMgr
    participant JVM as JVM_ART
    participant Bridge as NativeBridge
    participant AccSvc as MaaAccessibilityService

    User->>Cli: run(param with AndroidParam)
    Cli->>Framework: MaaAndroidControllerCreate(screencap_methods, input_methods)
    activate Framework
    Framework->>LibHolder: create_control_unit(screencap_methods, input_methods)
    activate LibHolder
    LibHolder->>So: MaaAndroidControlUnitCreate(screencap_methods, input_methods)
    activate So
    So->>So: new AndroidControlUnitMgr
    So-->>LibHolder: AndroidControlUnitHandle
    deactivate So
    LibHolder-->>Framework: shared_ptr AndroidControlUnitAPI
    deactivate LibHolder
    Framework->>Framework: new ControllerAgent(control_unit)
    Framework-->>Cli: MaaController*
    deactivate Framework

    Note over Cli,Cli: Later, during controller initialization
    Cli->>CU: connect()
    activate CU
    CU->>JVM: ensure_env()
    CU->>Bridge: init(screencap_methods, input_methods)
    CU->>Bridge: connect()
    activate Bridge
    Bridge->>Bridge: verify accessibility/media_projection availability
    Bridge-->>CU: true/false
    deactivate Bridge
    CU-->>Cli: connect result
    deactivate CU

    Note over CU,Bridge: During operations (example: screencap)
    Cli->>CU: screencap(image)
    activate CU
    CU->>Bridge: screencap()
    activate Bridge
    alt MediaProjection enabled and available
        Bridge->>Bridge: mediaProjectionHolder.capture()
    else Accessibility screenshot fallback
        Bridge->>AccSvc: screencap()
        activate AccSvc
        AccSvc-->>Bridge: Bitmap
        deactivate AccSvc
    end
    Bridge-->>CU: Bitmap
    deactivate Bridge
    CU-->>Cli: fill cv::Mat from Bitmap
    deactivate CU
Loading

Class diagram for new Android control unit and Kotlin bridge

classDiagram
    direction LR

    class ControlUnitAPI {
        <<abstract>>
        +connect() bool
        +request_uuid(uuid string) bool
        +get_features() MaaControllerFeature
        +start_app(intent string) bool
        +stop_app(intent string) bool
        +screencap(image cv_Mat) bool
        +click(x int, y int) bool
        +swipe(x1 int, y1 int, x2 int, y2 int, duration int) bool
        +touch_down(contact int, x int, y int, pressure int) bool
        +touch_move(contact int, x int, y int, pressure int) bool
        +touch_up(contact int) bool
        +click_key(key int) bool
        +input_text(text string) bool
        +key_down(key int) bool
        +key_up(key int) bool
        +scroll(dx int, dy int) bool
    }

    class AndroidControlUnitAPI {
        <<abstract>>
    }

    class AndroidControlUnitMgr {
        -screencap_methods_ MaaAndroidScreencapMethod
        -input_methods_ MaaAndroidInputMethod
        -vm_ JavaVM*
        -bridge_cls_ jclass
        +AndroidControlUnitMgr(screencap_methods MaaAndroidScreencapMethod, input_methods MaaAndroidInputMethod)
        +connect() bool
        +request_uuid(uuid string) bool
        +get_features() MaaControllerFeature
        +start_app(intent string) bool
        +stop_app(intent string) bool
        +screencap(image cv_Mat) bool
        +click(x int, y int) bool
        +swipe(x1 int, y1 int, x2 int, y2 int, duration int) bool
        +touch_down(contact int, x int, y int, pressure int) bool
        +touch_move(contact int, x int, y int, pressure int) bool
        +touch_up(contact int) bool
        +click_key(key int) bool
        +input_text(text string) bool
        +key_down(key int) bool
        +key_up(key int) bool
        +scroll(dx int, dy int) bool
        -ensure_env() JNIEnv*
        -bridge_class() jclass
        -call_bool(name char*, sig char*, caller Callable) bool
    }

    class AndroidControlUnitLibraryHolder {
        +create_control_unit(screencap_methods MaaAndroidScreencapMethod, input_methods MaaAndroidInputMethod) shared_ptr~AndroidControlUnitAPI~
        -libname_ filesystem_path
        -version_func_name_ string
        -create_func_name_ string
        -destroy_func_name_ string
    }

    class MaaAndroidControlUnit_API {
        +MaaAndroidControlUnitGetVersion() const_char_ptr
        +MaaAndroidControlUnitCreate(screencap_methods MaaAndroidScreencapMethod, input_methods MaaAndroidInputMethod) MaaAndroidControlUnitHandle
        +MaaAndroidControlUnitDestroy(handle MaaAndroidControlUnitHandle) void
    }

    class MaaController {
    }

    class ControllerAgent {
        +ControllerAgent(control_unit shared_ptr~ControlUnitAPI~)
    }

    class MaaFramework_API {
        +MaaAndroidControllerCreate(screencap_methods MaaAndroidScreencapMethod, input_methods MaaAndroidInputMethod) MaaController*
    }

    class RuntimeParam {
        +controller_param variant
    }

    class AndroidParam {
        +screencap MaaAndroidScreencapMethod
        +input MaaAndroidInputMethod
    }

    class Runner {
        +run(param RuntimeParam) bool
    }

    class NativeBridge {
        <<object>>
        +SCREENCAP_METHOD_NONE long
        +SCREENCAP_METHOD_ACCESSIBILITY long
        +SCREENCAP_METHOD_MEDIA_PROJECTION long
        +INPUT_METHOD_NONE long
        +INPUT_METHOD_ACCESSIBILITY long
        -accessibilityService MaaAccessibilityService
        -mediaProjectionHolder MediaProjectionHolder
        -screencapMethods long
        -inputMethods long
        +attachAccessibilityService(service MaaAccessibilityService) void
        +detachAccessibilityService(service MaaAccessibilityService) void
        +attachMediaProjection(holder MediaProjectionHolder) void
        +detachMediaProjection() void
        +init(screencapMethods long, inputMethods long) bool
        +connect() bool
        +requestUuid() String
        +startApp(intent String) Boolean
        +stopApp(intent String) Boolean
        +tap(x Int, y Int) Boolean
        +swipe(x1 Int, y1 Int, x2 Int, y2 Int, duration Int) Boolean
        +scroll(dx Int, dy Int) Boolean
        +keyEvent(key Int, down Boolean) Boolean
        +inputText(text String) Boolean
        +screencap() Bitmap
        +getScreenSize() IntArray
    }

    class MaaAccessibilityService {
        +onServiceConnected() void
        +onUnbind(intent Intent) Boolean
        +onAccessibilityEvent(event AccessibilityEvent) void
        +onInterrupt() void
        +uuid() String
        +getScreenSize() IntArray
        +startApp(intentUri String) Boolean
        +stopApp(intentUri String) Boolean
        +tap(x Int, y Int) Boolean
        +swipe(x1 Int, y1 Int, x2 Int, y2 Int, duration Int) Boolean
        +scroll(dx Int, dy Int) Boolean
        +keyEvent(key Int, down Boolean) Boolean
        +inputText(text String) Boolean
        +screencap() Bitmap
    }

    class MediaProjectionHolder {
        -context Context
        -mediaProjection MediaProjection
        -virtualDisplay VirtualDisplay
        -imageReader ImageReader
        -handlerThread HandlerThread
        -handler Handler
        +MediaProjectionHolder(context Context, mediaProjection MediaProjection)
        +capture() Bitmap
        +release() void
        -initScreen() void
        -setupImageReader() void
        -imageToBitmap(image Image) Bitmap
    }

    class MediaProjectionActivity {
        +onCreate(savedInstanceState Bundle) void
        +onActivityResult(requestCode Int, resultCode Int, data Intent) void
        +onDestroy() void
        +requestPermission(context Context, callback Function) void
        -clearCallback() void
    }

    ControlUnitAPI <|-- AndroidControlUnitAPI
    AndroidControlUnitAPI <|-- AndroidControlUnitMgr

    AndroidControlUnitLibraryHolder ..> AndroidControlUnitAPI : creates
    AndroidControlUnitMgr ..> NativeBridge : uses_via_JNI

    MaaAndroidControlUnit_API ..> AndroidControlUnitMgr : returns_instance

    ControllerAgent ..> ControlUnitAPI : holds_shared_ptr
    MaaFramework_API ..> ControllerAgent : constructs

    Runner ..> RuntimeParam : uses
    RuntimeParam o-- AndroidParam
    Runner ..> MaaFramework_API : calls_MaaAndroidControllerCreate

    NativeBridge ..> MaaAccessibilityService : holds_reference
    NativeBridge ..> MediaProjectionHolder : holds_reference
    MaaAccessibilityService ..> NativeBridge : calls_attach_detach
    MediaProjectionActivity ..> MediaProjectionHolder : creates_and_attaches
    MediaProjectionActivity ..> NativeBridge : attachMediaProjection
Loading

File-Level Changes

Change Details Files
Introduce Android control unit API and manager and hook it into the generic control-unit loading path.
  • Add AndroidControlUnitAPI interface and MaaAndroidControlUnitHandle typedef mirroring other control units.
  • Implement AndroidControlUnitMgr as a ControlUnitAPI that calls into a Java/Kotlin NativeBridge via JNI for connection, UUID, app control, input, scroll, and screencap (with bitmap format handling and recycling).
  • Extend LibraryHolder with AndroidControlUnitLibraryHolder that dlopens MaaAndroidControlUnit, checks version, and exposes create_control_unit for Android screencap/input bitmasks.
source/include/ControlUnit/ControlUnitAPI.h
source/include/ControlUnit/AndroidControlUnitAPI.h
source/MaaAndroidControlUnit/Manager/AndroidControlUnitMgr.h
source/MaaAndroidControlUnit/Manager/AndroidControlUnitMgr.cpp
source/LibraryHolder/ControlUnit/ControlUnit.cpp
source/include/LibraryHolder/ControlUnit.h
Add C-level Android control unit shared library and hook it into CMake build.
  • Create MaaAndroidControlUnit shared library target that builds the manager and API, links against MaaUtils, OpenCV, log, jnigraphics, and android, and is only enabled on Android.
  • Expose MaaAndroidControlUnitGetVersion, MaaAndroidControlUnitCreate, and MaaAndroidControlUnitDestroy functions to construct/destroy AndroidControlUnitMgr instances.
  • Wire MaaAndroidControlUnit as a dependency of LibraryHolder and MaaFramework when WITH_ANDROID_CONTROLLER is enabled, and add WITH_ANDROID_CONTROLLER option that auto-disables off-Android.
source/MaaAndroidControlUnit/API/AndroidControlUnitAPI.cpp
source/MaaAndroidControlUnit/CMakeLists.txt
CMakeLists.txt
source/CMakeLists.txt
source/LibraryHolder/CMakeLists.txt
source/MaaFramework/CMakeLists.txt
Expose Android controller types and creation API through the public framework and module interface.
  • Define MaaAndroidScreencapMethod and MaaAndroidInputMethod bitmask enums and defaults in MaaDef.h, and export them from the MaaFramework C++20 module.
  • Add MaaAndroidControllerCreate to the framework C API, creating an AndroidControlUnit via the library holder when building on Android and returning a ControllerAgent.
  • Document Android input/screencap enums and MaaAndroidControllerCreate semantics in both English control-method and interface overview docs.
include/MaaFramework/MaaDef.h
include/MaaFramework/Instance/MaaController.h
source/MaaFramework/API/MaaFramework.cpp
source/modules/MaaFramework.cppm
docs/en_us/2.4-ControlMethods.md
docs/en_us/2.2-IntegratedInterfaceOverview.md
docs/zh_cn/2.4-控制方式说明.md
docs/zh_cn/2.2-集成接口总览.md
Integrate Android controller into the CLI configuration/runtime flow.
  • Extend InterfaceData::Controller::Type with an Android variant and add RuntimeParam::AndroidParam, updating controller_param variant to include it.
  • Default AndroidParam to use the combined screencap default and accessibility input.
  • Update Configurator to create Android runtime params, Runner to call MaaAndroidControllerCreate with those parameters, and CLI interactor to expose Android as a selectable/printable controller type.
source/include/ProjectInterface/Types.h
source/MaaPiCli/Impl/Configurator.cpp
source/MaaPiCli/Impl/Runner.cpp
source/MaaPiCli/CLI/interactor.cpp
Add Kotlin Android library that implements accessibility and MediaProjection bridges for native control.
  • Define Android library Gradle project under MaaAndroidControlUnit/java with Kotlin/Android plugins, manifest, accessibility service, translucent MediaProjectionActivity, and accessibility_service_config, building an AAR.
  • Implement MaaAccessibilityService handling gestures, key events, text input, and Android 13+ takeScreenshot(), with UUID persistence and screen-size reporting.
  • Implement NativeBridge singleton that stores configured screencap/input bitmasks and delegates screencap, input, key events, scroll, and app start/stop to MaaAccessibilityService and MediaProjectionHolder; add MediaProjectionHolder for virtual display captures and a MediaProjectionActivity to request/establish MediaProjection.
  • Add .github workflow steps to set up JDK/Gradle, build the Kotlin AAR, and include it in the install/android directory in CI artifacts.
source/MaaAndroidControlUnit/java/build.gradle.kts
source/MaaAndroidControlUnit/java/settings.gradle.kts
source/MaaAndroidControlUnit/java/gradle.properties
source/MaaAndroidControlUnit/java/AndroidManifest.xml
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MaaAccessibilityService.kt
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/NativeBridge.kt
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MediaProjectionHolder.kt
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MediaProjectionActivity.kt
source/MaaAndroidControlUnit/java/res/xml/accessibility_service_config.xml
.github/workflows/build.yml
Streamline Linux CI package installation and ensure Android deps are present in both build and test workflows.
  • Remove global apt upgrade/removal step and move apt-get update into the dependency installation steps for Linux jobs.
  • Ensure ninja, cmake, and ccache are installed in both build and test workflows for Linux.
  • Install JDK 17 and Gradle in the Linux build job to support building the Android Kotlin library before native Android build.
.github/workflows/build.yml
.github/workflows/test.yml

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

@MistEO MistEO marked this pull request as draft December 6, 2025 08:28
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

你好,我已经审查了你的修改,发现有一些需要处理的问题。

  • AndroidControlUnitMgr 在 bridge_cls_ 中缓存了指向 NativeBridge 的全局引用,但从未释放;请考虑删除该全局引用(例如在管理器析构函数或专门的关闭路径中)以避免在进程生命周期内泄漏 JNI 全局引用。
  • ensure_env() 可能会将任意线程附加到 JVM 上,但从不将其分离;如果 MaaFramework 在控制器操作中可能使用许多不同的线程,那么最好明确一个线程生命周期策略,或者添加一个在线程结束时调用 DetachCurrentThread 的 RAII 辅助工具,以提高安全性。
面向 AI Agent 的提示
请根据这次代码审查的评论进行修改:

## 总体评论
- AndroidControlUnitMgr 在 bridge_cls_ 中缓存了指向 NativeBridge 的全局引用,但从未释放;请考虑删除该全局引用(例如在管理器析构函数或专门的关闭路径中)以避免在进程生命周期内泄漏 JNI 全局引用。
- ensure_env() 可能会将任意线程附加到 JVM 上,但从不将其分离;如果 MaaFramework 在控制器操作中可能使用许多不同的线程,那么最好明确一个线程生命周期策略,或者添加一个在线程结束时调用 DetachCurrentThread 的 RAII 辅助工具,以提高安全性。

## 逐条评论

### 评论 1
<location> `include/MaaFramework/MaaDef.h:269-271` </location>
<code_context>
+/**
+ * Use bitwise OR to set the method you need.
+ */
+typedef uint64_t MaaAndroidInputMethod;
+#define MaaAndroidInputMethod_None 0ULL
+#define MaaAndroidInputMethod_Accessibility 1ULL
+
 // MaaWin32ScreencapMethod:
</code_context>

<issue_to_address>
**issue (bug_risk):** 定义 MaaAndroidInputMethod_All 和 MaaAndroidInputMethod_Default 以与模块导出和用法保持一致

`MaaFramework.cppm` 导出了 `_MaaAndroidInputMethod_All` / `_MaaAndroidInputMethod_Default`,并引用了 `MaaAndroidInputMethod_All` / `MaaAndroidInputMethod_Default`,但这些宏在此处没有定义。这会导致编译或链接错误。请在这里定义它们(例如:

```c
#define MaaAndroidInputMethod_All (~MaaAndroidInputMethod_None)
#define MaaAndroidInputMethod_Default MaaAndroidInputMethod_Accessibility
```

或者使用你偏好的语义),以保证 C API 与模块导出保持一致。
</issue_to_address>

### 评论 2
<location> `source/MaaAndroidControlUnit/Manager/AndroidControlUnitMgr.cpp:180-189` </location>
<code_context>
+bool AndroidControlUnitMgr::screencap(/*out*/ cv::Mat& image)
</code_context>

<issue_to_address>
**suggestion (bug_risk):** 明确或调整像素格式处理,以匹配预期的 OpenCV 通道顺序

`image` 始终以 `CV_8UC4` 创建,但对于 `RGBA_8888` 位图,你似乎是按原样复制通道,而 RGB565 路径则构造了类似 BGRA 的布局(`b, g, r, 255`)。如果后续处理流水线假定特定的通道顺序(通常是 BGRA),那么 `RGBA_8888` 路径可能会导致颜色通道对调。建议显式地将所有输入转换为一个统一且有文档说明的通道顺序(例如通过 `cv::cvtColor` 或手动通道交换),以保证跨平台行为一致。

建议实现:

```cpp
bool AndroidControlUnitMgr::screencap(/*out*/ cv::Mat& image)
{
    // NOTE: All outputs from this function are expected to be BGRA (CV_8UC4).
    // Be sure to convert any other input formats (e.g. RGBA_8888) accordingly.
    JNIEnv* env = ensure_env();
    if (!env) {
        LogError << "JNIEnv null";
        return false;
    }
    jclass cls = bridge_class();
    if (!cls) {
        return false;
    }

```

```cpp
        case ANDROID_BITMAP_FORMAT_RGBA_8888: {
            // Convert RGBA_8888 input into a BGRA CV_8UC4 Mat to keep a consistent channel order.
            cv::Mat rgba(height, width, CV_8UC4, bitmapPixels);
            cv::cvtColor(rgba, image, cv::COLOR_RGBA2BGRA);
            break;
        }

```

由于我目前只能看到 `screencap` 的开头部分,你可能需要根据现有代码对 `SEARCH` 区块进行适配:

1. 确保存在一个 `case ANDROID_BITMAP_FORMAT_RGBA_8888`(或等价的条件分支)来处理当前“按原样”复制 RGBA 像素到 `image` 的逻辑。将该分支体替换为:
   - 使用临时 `cv::Mat rgba(height, width, CV_8UC4, bitmapPixels);` 包装 `bitmapPixels`
   - 调用 `cv::cvtColor(rgba, image, cv::COLOR_RGBA2BGRA);`
2. 如果 RGBA 路径当前是 `image = cv::Mat(height, width, CV_8UC4);` 然后再手动拷贝到 `image.data`,请移除该手动拷贝,改用 `cv::Mat rgba(...)` + `cvtColor` 的方式,以避免重复分配或步幅不匹配。
3. 验证 RGB565 路径是否确实生成了 BGRA(`b, g, r, 255`),以确保“BGRA (CV_8UC4)” 这一预期是正确的;如果不是,要么调整 RGB565 转换也输出 BGRA,要么修改注释和 `cvtColor` 的目标格式以匹配你选择的规范通道顺序。
4. 确保在该翻译单元中包含 `<opencv2/imgproc.hpp>`(或相应的 OpenCV 头文件),以便可以使用 `cv::cvtColor` 和 `cv::COLOR_RGBA2BGRA`。
</issue_to_address>

### 评论 3
<location> `docs/en_us/2.4-ControlMethods.md:57` </location>
<code_context>
+
+| Name | Value | Description |
+| --- | --- | --- |
+| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on current focused input node. |
+
+### Android Screencap
</code_context>

<issue_to_address>
**issue (typo):** 将 "current focused" 改为 "currently focused" 以符合正确的语法

将 "on current focused input node" 修改为 "on the currently focused input node",以保证语法正确且表达更清晰。

```suggestion
| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on the currently focused input node. |
```
</issue_to_address>

### 评论 4
<location> `docs/en_us/2.4-ControlMethods.md:63` </location>
<code_context>
+
+> Reference: `MaaAndroidScreencapMethod`.
+
+Combine the selected methods below using **bitwise OR**. Framework will choose available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.
+
+| Name | Value | Description |
</code_context>

<issue_to_address>
**suggestion (typo):** 建议在 "Framework will choose available ones" 中补充冠词,使语句更顺畅

可以改成这样以使表达更自然且语法完整:"The framework will choose the available ones, preferring MediaProjection …"。

```suggestion
Combine the selected methods below using **bitwise OR**. The framework will choose the available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.
```
</issue_to_address>

Sourcery 对开源项目免费 —— 如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请对每条评论点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey there - I've reviewed your changes and found some issues that need to be addressed.

  • AndroidControlUnitMgr caches a global reference to NativeBridge in bridge_cls_ but never releases it; consider deleting the global ref (e.g. in the manager destructor or a dedicated shutdown path) to avoid leaking JNI global references over the lifetime of the process.
  • ensure_env() may attach arbitrary threads to the JVM without ever detaching them; if MaaFramework can use many different threads for controller operations, it would be safer to define a clear thread-lifetime policy or add an RAII helper that calls DetachCurrentThread when threads are done.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- AndroidControlUnitMgr caches a global reference to NativeBridge in bridge_cls_ but never releases it; consider deleting the global ref (e.g. in the manager destructor or a dedicated shutdown path) to avoid leaking JNI global references over the lifetime of the process.
- ensure_env() may attach arbitrary threads to the JVM without ever detaching them; if MaaFramework can use many different threads for controller operations, it would be safer to define a clear thread-lifetime policy or add an RAII helper that calls DetachCurrentThread when threads are done.

## Individual Comments

### Comment 1
<location> `include/MaaFramework/MaaDef.h:269-271` </location>
<code_context>
+/**
+ * Use bitwise OR to set the method you need.
+ */
+typedef uint64_t MaaAndroidInputMethod;
+#define MaaAndroidInputMethod_None 0ULL
+#define MaaAndroidInputMethod_Accessibility 1ULL
+
 // MaaWin32ScreencapMethod:
</code_context>

<issue_to_address>
**issue (bug_risk):** Define MaaAndroidInputMethod_All and MaaAndroidInputMethod_Default to match module exports and usage

`MaaFramework.cppm` exports `_MaaAndroidInputMethod_All` / `_MaaAndroidInputMethod_Default` and refers to `MaaAndroidInputMethod_All` / `MaaAndroidInputMethod_Default`, but these macros are not defined here. That will cause compile or link errors. Please define them (e.g.

```c
#define MaaAndroidInputMethod_All (~MaaAndroidInputMethod_None)
#define MaaAndroidInputMethod_Default MaaAndroidInputMethod_Accessibility
```

or whatever semantics you prefer) so the C API and module exports remain consistent.
</issue_to_address>

### Comment 2
<location> `source/MaaAndroidControlUnit/Manager/AndroidControlUnitMgr.cpp:180-189` </location>
<code_context>
+bool AndroidControlUnitMgr::screencap(/*out*/ cv::Mat& image)
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Clarify or adjust pixel format handling to match the expected OpenCV channel order

`image` is always created as `CV_8UC4`, but for `RGBA_8888` bitmaps you appear to copy channels as-is while the RGB565 path builds a BGRA-like layout (`b, g, r, 255`). If the rest of the pipeline assumes a specific ordering (commonly BGRA), the `RGBA_8888` path may produce swapped colors. Consider explicitly converting all inputs to a single, documented channel order (e.g., via `cv::cvtColor` or a manual channel swap) to keep behavior consistent across platforms.

Suggested implementation:

```cpp
bool AndroidControlUnitMgr::screencap(/*out*/ cv::Mat& image)
{
    // NOTE: All outputs from this function are expected to be BGRA (CV_8UC4).
    // Be sure to convert any other input formats (e.g. RGBA_8888) accordingly.
    JNIEnv* env = ensure_env();
    if (!env) {
        LogError << "JNIEnv null";
        return false;
    }
    jclass cls = bridge_class();
    if (!cls) {
        return false;
    }

```

```cpp
        case ANDROID_BITMAP_FORMAT_RGBA_8888: {
            // Convert RGBA_8888 input into a BGRA CV_8UC4 Mat to keep a consistent channel order.
            cv::Mat rgba(height, width, CV_8UC4, bitmapPixels);
            cv::cvtColor(rgba, image, cv::COLOR_RGBA2BGRA);
            break;
        }

```

Because I only see the beginning of `screencap`, you’ll likely need to adapt the `SEARCH` blocks to the exact existing code:

1. Ensure that there is a `case ANDROID_BITMAP_FORMAT_RGBA_8888` (or equivalent conditional handling) where RGBA pixels are currently copied “as-is” into `image`. Replace that body with the version that:
   - Wraps `bitmapPixels` in a temporary `cv::Mat rgba(height, width, CV_8UC4, bitmapPixels);`
   - Calls `cv::cvtColor(rgba, image, cv::COLOR_RGBA2BGRA);`
2. If the RGBA path currently assigns `image = cv::Mat(height, width, CV_8UC4);` and then performs a manual copy into `image.data`, you should remove that manual copy in favor of the `cv::Mat rgba(...)` + `cvtColor` approach to avoid double allocations or mismatched strides.
3. Verify that the RGB565 path is indeed producing BGRA (`b, g, r, 255`) so that the documented “BGRA (CV_8UC4)” expectation is correct; if it is not, adjust the RGB565 conversion so that it also outputs BGRA, or change the comment and the `cvtColor` target to match the chosen canonical order.
4. Make sure `<opencv2/imgproc.hpp>` (or the appropriate OpenCV header) is included in this translation unit so that `cv::cvtColor` and `cv::COLOR_RGBA2BGRA` are available.
</issue_to_address>

### Comment 3
<location> `docs/en_us/2.4-ControlMethods.md:57` </location>
<code_context>
+
+| Name | Value | Description |
+| --- | --- | --- |
+| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on current focused input node. |
+
+### Android Screencap
</code_context>

<issue_to_address>
**issue (typo):** Use "currently focused" instead of "current focused" for correct grammar.

Change "on current focused input node" to "on the currently focused input node" for correct grammar and clarity.

```suggestion
| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on the currently focused input node. |
```
</issue_to_address>

### Comment 4
<location> `docs/en_us/2.4-ControlMethods.md:63` </location>
<code_context>
+
+> Reference: `MaaAndroidScreencapMethod`.
+
+Combine the selected methods below using **bitwise OR**. Framework will choose available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.
+
+| Name | Value | Description |
</code_context>

<issue_to_address>
**suggestion (typo):** Consider adding an article to "Framework will choose available ones" for smoother phrasing.

You could rephrase to: "The framework will choose the available ones, preferring MediaProjection …" for more natural, grammatically complete wording.

```suggestion
Combine the selected methods below using **bitwise OR**. The framework will choose the available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +269 to +271
typedef uint64_t MaaAndroidInputMethod;
#define MaaAndroidInputMethod_None 0ULL
#define MaaAndroidInputMethod_Accessibility 1ULL
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (bug_risk): 定义 MaaAndroidInputMethod_All 和 MaaAndroidInputMethod_Default 以与模块导出和用法保持一致

MaaFramework.cppm 导出了 _MaaAndroidInputMethod_All / _MaaAndroidInputMethod_Default,并引用了 MaaAndroidInputMethod_All / MaaAndroidInputMethod_Default,但这些宏在此处没有定义。这会导致编译或链接错误。请在这里定义它们(例如:

#define MaaAndroidInputMethod_All (~MaaAndroidInputMethod_None)
#define MaaAndroidInputMethod_Default MaaAndroidInputMethod_Accessibility

或者使用你偏好的语义),以保证 C API 与模块导出保持一致。

Original comment in English

issue (bug_risk): Define MaaAndroidInputMethod_All and MaaAndroidInputMethod_Default to match module exports and usage

MaaFramework.cppm exports _MaaAndroidInputMethod_All / _MaaAndroidInputMethod_Default and refers to MaaAndroidInputMethod_All / MaaAndroidInputMethod_Default, but these macros are not defined here. That will cause compile or link errors. Please define them (e.g.

#define MaaAndroidInputMethod_All (~MaaAndroidInputMethod_None)
#define MaaAndroidInputMethod_Default MaaAndroidInputMethod_Accessibility

or whatever semantics you prefer) so the C API and module exports remain consistent.

Comment on lines +180 to +189
bool AndroidControlUnitMgr::screencap(/*out*/ cv::Mat& image)
{
JNIEnv* env = ensure_env();
if (!env) {
LogError << "JNIEnv null";
return false;
}
jclass cls = bridge_class();
if (!cls) {
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): 明确或调整像素格式处理,以匹配预期的 OpenCV 通道顺序

image 始终以 CV_8UC4 创建,但对于 RGBA_8888 位图,你似乎是按原样复制通道,而 RGB565 路径则构造了类似 BGRA 的布局(b, g, r, 255)。如果后续处理流水线假定特定的通道顺序(通常是 BGRA),那么 RGBA_8888 路径可能会导致颜色通道对调。建议显式地将所有输入转换为一个统一且有文档说明的通道顺序(例如通过 cv::cvtColor 或手动通道交换),以保证跨平台行为一致。

建议实现:

bool AndroidControlUnitMgr::screencap(/*out*/ cv::Mat& image)
{
    // NOTE: All outputs from this function are expected to be BGRA (CV_8UC4).
    // Be sure to convert any other input formats (e.g. RGBA_8888) accordingly.
    JNIEnv* env = ensure_env();
    if (!env) {
        LogError << "JNIEnv null";
        return false;
    }
    jclass cls = bridge_class();
    if (!cls) {
        return false;
    }
        case ANDROID_BITMAP_FORMAT_RGBA_8888: {
            // Convert RGBA_8888 input into a BGRA CV_8UC4 Mat to keep a consistent channel order.
            cv::Mat rgba(height, width, CV_8UC4, bitmapPixels);
            cv::cvtColor(rgba, image, cv::COLOR_RGBA2BGRA);
            break;
        }

由于我目前只能看到 screencap 的开头部分,你可能需要根据现有代码对 SEARCH 区块进行适配:

  1. 确保存在一个 case ANDROID_BITMAP_FORMAT_RGBA_8888(或等价的条件分支)来处理当前“按原样”复制 RGBA 像素到 image 的逻辑。将该分支体替换为:
    • 使用临时 cv::Mat rgba(height, width, CV_8UC4, bitmapPixels); 包装 bitmapPixels
    • 调用 cv::cvtColor(rgba, image, cv::COLOR_RGBA2BGRA);
  2. 如果 RGBA 路径当前是 image = cv::Mat(height, width, CV_8UC4); 然后再手动拷贝到 image.data,请移除该手动拷贝,改用 cv::Mat rgba(...) + cvtColor 的方式,以避免重复分配或步幅不匹配。
  3. 验证 RGB565 路径是否确实生成了 BGRA(b, g, r, 255),以确保“BGRA (CV_8UC4)” 这一预期是正确的;如果不是,要么调整 RGB565 转换也输出 BGRA,要么修改注释和 cvtColor 的目标格式以匹配你选择的规范通道顺序。
  4. 确保在该翻译单元中包含 <opencv2/imgproc.hpp>(或相应的 OpenCV 头文件),以便可以使用 cv::cvtColorcv::COLOR_RGBA2BGRA
Original comment in English

suggestion (bug_risk): Clarify or adjust pixel format handling to match the expected OpenCV channel order

image is always created as CV_8UC4, but for RGBA_8888 bitmaps you appear to copy channels as-is while the RGB565 path builds a BGRA-like layout (b, g, r, 255). If the rest of the pipeline assumes a specific ordering (commonly BGRA), the RGBA_8888 path may produce swapped colors. Consider explicitly converting all inputs to a single, documented channel order (e.g., via cv::cvtColor or a manual channel swap) to keep behavior consistent across platforms.

Suggested implementation:

bool AndroidControlUnitMgr::screencap(/*out*/ cv::Mat& image)
{
    // NOTE: All outputs from this function are expected to be BGRA (CV_8UC4).
    // Be sure to convert any other input formats (e.g. RGBA_8888) accordingly.
    JNIEnv* env = ensure_env();
    if (!env) {
        LogError << "JNIEnv null";
        return false;
    }
    jclass cls = bridge_class();
    if (!cls) {
        return false;
    }
        case ANDROID_BITMAP_FORMAT_RGBA_8888: {
            // Convert RGBA_8888 input into a BGRA CV_8UC4 Mat to keep a consistent channel order.
            cv::Mat rgba(height, width, CV_8UC4, bitmapPixels);
            cv::cvtColor(rgba, image, cv::COLOR_RGBA2BGRA);
            break;
        }

Because I only see the beginning of screencap, you’ll likely need to adapt the SEARCH blocks to the exact existing code:

  1. Ensure that there is a case ANDROID_BITMAP_FORMAT_RGBA_8888 (or equivalent conditional handling) where RGBA pixels are currently copied “as-is” into image. Replace that body with the version that:
    • Wraps bitmapPixels in a temporary cv::Mat rgba(height, width, CV_8UC4, bitmapPixels);
    • Calls cv::cvtColor(rgba, image, cv::COLOR_RGBA2BGRA);
  2. If the RGBA path currently assigns image = cv::Mat(height, width, CV_8UC4); and then performs a manual copy into image.data, you should remove that manual copy in favor of the cv::Mat rgba(...) + cvtColor approach to avoid double allocations or mismatched strides.
  3. Verify that the RGB565 path is indeed producing BGRA (b, g, r, 255) so that the documented “BGRA (CV_8UC4)” expectation is correct; if it is not, adjust the RGB565 conversion so that it also outputs BGRA, or change the comment and the cvtColor target to match the chosen canonical order.
  4. Make sure <opencv2/imgproc.hpp> (or the appropriate OpenCV header) is included in this translation unit so that cv::cvtColor and cv::COLOR_RGBA2BGRA are available.

| Name | Value | Description |
| --- | --- | --- |
| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on current focused input node. |
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (typo): 将 "current focused" 改为 "currently focused" 以符合正确的语法

将 "on current focused input node" 修改为 "on the currently focused input node",以保证语法正确且表达更清晰。

Suggested change
| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on current focused input node. |
| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on the currently focused input node. |
Original comment in English

issue (typo): Use "currently focused" instead of "current focused" for correct grammar.

Change "on current focused input node" to "on the currently focused input node" for correct grammar and clarity.

Suggested change
| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on current focused input node. |
| Accessibility | `1` | Uses accessibility service for control, including `dispatchGesture` for tap/swipe/scroll, global actions for key events (back/home/recents), and `ACTION_SET_TEXT` on the currently focused input node. |


> Reference: `MaaAndroidScreencapMethod`.
Combine the selected methods below using **bitwise OR**. Framework will choose available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (typo): 建议在 "Framework will choose available ones" 中补充冠词,使语句更顺畅

可以改成这样以使表达更自然且语法完整:"The framework will choose the available ones, preferring MediaProjection …"。

Suggested change
Combine the selected methods below using **bitwise OR**. Framework will choose available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.
Combine the selected methods below using **bitwise OR**. The framework will choose the available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.
Original comment in English

suggestion (typo): Consider adding an article to "Framework will choose available ones" for smoother phrasing.

You could rephrase to: "The framework will choose the available ones, preferring MediaProjection …" for more natural, grammatically complete wording.

Suggested change
Combine the selected methods below using **bitwise OR**. Framework will choose available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.
Combine the selected methods below using **bitwise OR**. The framework will choose the available ones, preferring MediaProjection (faster and more stable), then AccessibilityScreenshot.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request adds native Android controller support to the MaaFramework, enabling direct device control through Android Accessibility Services and MediaProjection APIs. This is a significant addition that allows the framework to run natively on Android devices without requiring ADB connections.

  • Implements a new AndroidControlUnitAPI with JNI bridge to Kotlin/Java accessibility and media projection services
  • Adds Android-specific screencap methods (AccessibilityScreenshot for API 33+ and MediaProjection) and Accessibility input method
  • Integrates the new controller into the CLI, CMake build system, and GitHub Actions workflow

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
source/modules/MaaFramework.cppm Exports new Android controller types and method constants
source/include/ProjectInterface/Types.h Adds Android controller type and AndroidParam configuration
source/include/LibraryHolder/ControlUnit.h Declares Android control unit library holder
source/include/ControlUnit/ControlUnitAPI.h Defines AndroidControlUnitAPI interface and adds <vector> include
source/include/ControlUnit/AndroidControlUnitAPI.h New header declaring Android control unit C API functions
source/MaaPiCli/Impl/Runner.cpp Integrates Android controller creation in CLI runner
source/MaaPiCli/Impl/Configurator.cpp Adds Android controller configuration generation
source/MaaPiCli/CLI/interactor.cpp Adds Android controller selection and display in CLI
source/MaaFramework/CMakeLists.txt Adds Android control unit build dependency
source/MaaFramework/API/MaaFramework.cpp Implements MaaAndroidControllerCreate with Android platform check
source/MaaAndroidControlUnit/java/settings.gradle.kts New Gradle settings for Android library
source/MaaAndroidControlUnit/java/res/xml/accessibility_service_config.xml Configures accessibility service capabilities
source/MaaAndroidControlUnit/java/gradle.properties Gradle properties for AndroidX and Kotlin
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/NativeBridge.kt JNI bridge singleton coordinating native and Java/Kotlin layers
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MediaProjectionHolder.kt Manages MediaProjection for screen capture with virtual display
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MediaProjectionActivity.kt Transparent activity requesting MediaProjection permission
source/MaaAndroidControlUnit/java/com/maa/framework/nativectrl/MaaAccessibilityService.kt Accessibility service implementing gesture dispatch, screenshot, and input
source/MaaAndroidControlUnit/java/build.gradle.kts Gradle build configuration for Android library module
source/MaaAndroidControlUnit/java/AndroidManifest.xml Declares accessibility service and MediaProjection activity
source/MaaAndroidControlUnit/Manager/AndroidControlUnitMgr.h Header for Android control unit manager with JNI integration
source/MaaAndroidControlUnit/Manager/AndroidControlUnitMgr.cpp Implements JNI calls to Kotlin bridge for all controller operations
source/MaaAndroidControlUnit/CMakeLists.txt CMake configuration building Android control unit native library
source/MaaAndroidControlUnit/API/AndroidControlUnitAPI.cpp Implements C API for creating/destroying Android control units
source/LibraryHolder/ControlUnit/ControlUnit.cpp Adds Android control unit library loading and instantiation
source/LibraryHolder/CMakeLists.txt Adds Android control unit dependency
source/CMakeLists.txt Adds Android control unit subdirectory with platform check
include/MaaFramework/MaaDef.h Defines Android screencap and input method types and constants
include/MaaFramework/Instance/MaaController.h Declares MaaAndroidControllerCreate function
docs/en_us/2.4-ControlMethods.md Documents Android input and screencap methods
docs/en_us/2.2-IntegratedInterfaceOverview.md Documents MaaAndroidControllerCreate API
CMakeLists.txt Adds WITH_ANDROID_CONTROLLER build option
.github/workflows/build.yml Adds JDK, Gradle setup and Kotlin library build for Android workflow

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// 检查是否有待处理的回调,如果没有则直接关闭
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Comment contains Chinese text. Please translate to English for consistency: "// 检查是否有待处理的回调,如果没有则直接关闭" should be "// Check if there's a pending callback, if not, close immediately"

Suggested change
// 检查是否有待处理的回调,如果没有则直接关闭
// Check if there's a pending callback, if not, close immediately

Copilot uses AI. Check for mistakes.

override fun onDestroy() {
super.onDestroy()
// 确保在 Activity 销毁时清理回调
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Comment contains Chinese text. Please translate to English for consistency: "// 确保在 Activity 销毁时清理回调" should be "// Ensure callback is cleared when Activity is destroyed"

Suggested change
// 确保在 Activity 销毁时清理回调
// Ensure callback is cleared when Activity is destroyed

Copilot uses AI. Check for mistakes.
*/
typedef uint64_t MaaAndroidInputMethod;
#define MaaAndroidInputMethod_None 0ULL
#define MaaAndroidInputMethod_Accessibility 1ULL
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Missing MaaAndroidInputMethod_All and MaaAndroidInputMethod_Default macros. For consistency with other input method definitions (like MaaAdbInputMethod), these should be defined. Suggested additions:

#define MaaAndroidInputMethod_All (~MaaAndroidInputMethod_None)
#define MaaAndroidInputMethod_Default MaaAndroidInputMethod_Accessibility
Suggested change
#define MaaAndroidInputMethod_Accessibility 1ULL
#define MaaAndroidInputMethod_Accessibility 1ULL
#define MaaAndroidInputMethod_All (~MaaAndroidInputMethod_None)
#define MaaAndroidInputMethod_Default MaaAndroidInputMethod_Accessibility

Copilot uses AI. Check for mistakes.

- `screencap_methods`: bitmask of `MaaAndroidScreencapMethod` (AccessibilityScreenshot or MediaProjection)
- `input_methods`: use `MaaAndroidInputMethod_Accessibility` (control via accessibility service)
- `config`: extra config (reserved, can be empty)
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

The documentation mentions a config parameter that doesn't exist in the actual API signature. The MaaAndroidControllerCreate function only takes screencap_methods and input_methods parameters, not a config parameter.

Suggested change
- `config`: extra config (reserved, can be empty)

Copilot uses AI. Check for mistakes.
context.startActivity(intent)
}

// 清理静态回调,防止内存泄漏
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Comment contains Chinese text. Please translate to English for consistency: "// 清理静态回调,防止内存泄漏" should be "// Clear static callback to prevent memory leak"

Suggested change
// 清理静态回调,防止内存泄漏
// Clear static callback to prevent memory leak

Copilot uses AI. Check for mistakes.

if (requestCode == REQUEST_CODE_CAPTURE) {
val callback = pendingCallback
pendingCallback = null // 立即清理回调引用
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Comment contains Chinese text. Please translate to English for consistency: "// 立即清理回调引用" should be "// Clear callback reference immediately"

Suggested change
pendingCallback = null // 立即清理回调引用
pendingCallback = null // Clear callback reference immediately

Copilot uses AI. Check for mistakes.
fun capture(): Bitmap? {
val reader = imageReader ?: return null

// 首先尝试获取已有的图像
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Comment contains Chinese text. Please translate to English for consistency with the codebase: "// 首先尝试获取已有的图像" should be "// First try to get existing image"

Suggested change
// 首先尝试获取已有的图像
// First try to get existing image

Copilot uses AI. Check for mistakes.
null
}

// 如果没有现成的图像,等待下一帧
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Comment contains Chinese text. Please translate to English for consistency with the codebase: "// 如果没有现成的图像,等待下一帧" should be "// If no image is available, wait for the next frame"

Suggested change
// 如果没有现成的图像,等待下一帧
// If no image is available, wait for the next frame

Copilot uses AI. Check for mistakes.
// Crop if needed
return if (rowPadding > 0) {
val cropped = Bitmap.createBitmap(bitmap, 0, 0, image.width, image.height)
bitmap.recycle() // 回收原始 bitmap 避免内存泄漏
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

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

Comment contains Chinese text. Please translate to English for consistency: "// 回收原始 bitmap 避免内存泄漏" should be "// Recycle original bitmap to avoid memory leak"

Suggested change
bitmap.recycle() // 回收原始 bitmap 避免内存泄漏
bitmap.recycle() // Recycle original bitmap to avoid memory leak

Copilot uses AI. Check for mistakes.
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