Skip to content

feat(scheduled wake unlock):root模式下锁屏定时唤醒功能 - #195

Merged
Aliothmoon merged 4 commits into
Aliothmoon:mainfrom
dorkytiger:feat/scheduled-wake-unlock
Aug 5, 2026
Merged

feat(scheduled wake unlock):root模式下锁屏定时唤醒功能#195
Aliothmoon merged 4 commits into
Aliothmoon:mainfrom
dorkytiger:feat/scheduled-wake-unlock

Conversation

@dorkytiger

@dorkytiger dorkytiger commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Pull Request

改分支包含了 Issue #191的修复,也就是Pull Request #194,如果需要我可以拆分,开发时候忘记拆分了...,这个是新加的功能,在root模式下可以自动唤醒锁屏

关联 Issue

变更摘要

1) 新功能:定时唤醒 + 解锁(feat/scheduled-wake-unlock 分支)

  • 新增 WakeUnlockEngine:通过 RemoteService 提权进程跑 shell 命令序列(KEYCODE_WAKEUP → swipe → input text → KEYCODE_ENTER),支持 4 种解锁策略:swipe / pin / password / keyguard
  • 新增 WakeAlarmScheduler + WakeAlarmReceiver:用 AlarmManager.setExactAndAllowWhileIdle 维护「下一个最近」精确闹钟,BroadcastReceiverPARTIAL_WAKE_LOCK + goAsync() 保活,完成后自动 reschedule
  • RemoteService.aidl 新增 executeShellCommand(id=33) + hasRootPrivilege(id=34)RemoteServiceImplProcessBuilder("sh", "-c", script) 执行
  • 设置页增加「定时唤醒 + 解锁」开关 + 二级编辑页(WakeScheduleEditorView):唤醒时间 chips、解锁方式下拉、密码框、自动息屏 Slider、立即测试按钮
  • 新增 20+ 条中英文字符串

2) drift auto-repin 宽限期(Issue #191 续)

  • AppWatchdog 新增 driftFirstSeenMs 状态 + driftAutoRepinEnabled / driftAutoRepinDelaySec 配置(默认 5s,可调 1~60s)
  • 避免「检测到漂移就立即拉回」打断正在登录的场景(登录框会被 moveRootTaskToDisplay 取消)

3) 稳定性修复

  • WakeAlarmScheduler
    • PendingIntent.FLAG_NO_CREATE 在 Android 12+ 必须配合 FLAG_IMMUTABLE,否则 IllegalArgumentException 直接崩溃
    • buildPendingIntent 返回类型改 PendingIntent?FLAG_NO_CREATE 返回 null 是合法状态
    • reschedule() / cancelAll() / arm() 全部 runCatching 包裹,scope 加 CoroutineExceptionHandler 兜底,避免未捕获异常冒到 CrashHandler 杀进程
  • WakeUnlockEngine shell 转义重写:
    • 引入 escapeCredential()(单引号整体包裹 + 内部单引号替换为 '\''),凭证专用,正确处理空格 / 单引号 / 分号等边界字符
    • wakeAndUnlock 不再对整条命令二次 escape(),消除"sh: no closing quote"报错
  • 解锁方式循环切换 bug:coerceAtMost(lastIndex) 让 keyguard 无法回到 swipe,改为 (idx+1) % size

4) 非 Root 后端保护

  • SettingsViewModel.wakeFeatureAvailablecombine(RemoteServiceManager.state, startupBackend),仅当后端 = ROOTRemoteService 已连接时启用
  • Shizuku 走 shell uid 可以执行 KEYCODE_WAKEUP / input swipe / input text,但 svc keyguard disable 被 SELinux 拒绝,且重启后 Shizuku 需要手动授权,所以 UI 强制仅 Root 后端可启用;非 Root 后端点击时弹「需要 Root 后端」说明弹窗
  • SettingSwitchItem 改造:SettingRow 始终可点,disabled 态下点整行仍能触发 onCheckedChange(用来弹"为什么不可用"),Switch 本身仍按 enabled 响应

5) Android 14+ 兼容说明

  • svc keyguard 子命令被 AOSP 从 API 34 起移除,KEYGUARD 解锁方式退化为「只唤醒,不解锁」,代码加 || true 做 best-effort
  • 在二级页 + 字符串里对用户提示「Android 14+ 不支持该解锁方式」

验证

设备:Android Studio Emulator — Pixel 6 AVD,API 34 (Android 14),Google APIs arm64-v8a 镜像,通过 adb root 拿到 uid=0。

权限方案:模拟器默认无 Magisk,用 adb root 后 app 内通过 ProcessBuilder("sh", "-c", ...) 跑 shell 命令;libsu 在模拟器内置 su 下无法识别,所以 RootManager 检测不到 root,UI 上 wake 开关置灰。实际验证通过 adb shell 复现完整脚本:

adb shell input keyevent KEYCODE_SLEEP
sleep 3
adb shell "input keyevent KEYCODE_WAKEUP ; sleep 1 ; input swipe 540 1920 540 720 300 ; sleep 0.5 ; input text '1234' ; sleep 0.2 ; input keyevent KEYCODE_ENTER"
echo "rc=$?" 

期望 0

功能演示

output.mp4

Summary by Sourcery

添加一个通过特权后端运行的定时唤醒与解锁功能,并通过可配置的自动重新固定行为改进对游戏偏离虚拟显示的处理。

New Features:

  • 引入定时唤醒与解锁的配置和编辑界面,允许用户定义唤醒时间、解锁方式、凭证以及自动休眠行为,并提供测试流程以验证完整执行序列。
  • 实现唤醒/解锁引擎以及闹钟调度器/接收器,从特权进程中执行基于 shell 的唤醒和解锁序列,包括 Root 检测和兼容 Doze 模式的精确定时闹钟。
  • 为新的唤醒计划编辑器添加导航和路由,并通过仅在 Root 环境下可用的检查对该功能进行门控,同时提供解释性对话框。

Bug Fixes:

  • 修复在后台虚拟显示模式下游戏窗口漂移的问题,通过反复检查游戏显示、尝试将任务重新固定到虚拟显示,并在重新固定失败时暴露警告信息。
  • 防止在闹钟调度中因缺失 PendingIntent 而导致的崩溃和异常行为,对 Android 12+ 强制使用不可变标志,并吞掉调度器异常。
  • 修正解锁方式循环逻辑,使锁屏选项能回到滑动解锁,而不是卡在列表末尾。

Enhancements:

  • 扩展应用看门狗以检测并报告持续的显示漂移,在自动重新固定前提供可配置的宽限期,从而减少对登录/认证流程的干扰。
  • 优化设置界面行为,使禁用的开关仍可点击以弹出解释性对话框,同时在视觉上保持禁用状态的指示。
  • 改进远程活动启动的可靠性,在启动后验证应用是否仍位于目标显示上,并在 ROM 将任务移回主屏幕时重试固定操作。

Build:

  • 调整资源清单生成逻辑,在配置阶段预先创建 MaaResource 资源目录,并为 Gradle 9.4+ 启用 Gradle 工具的并行同步。

Documentation:

  • 添加英文和中文的用户可见字符串,用于描述定时唤醒与解锁配置、漂移自动重新固定设置,以及新的后台显示漂移提示和运行日志条目。
Original summary in English

Summary by Sourcery

Add a scheduled wake-and-unlock capability that runs via a privileged backend, and improve handling of games drifting off the virtual display with configurable auto-repin behavior.

New Features:

  • Introduce scheduled wake-and-unlock configuration and editor screens, allowing users to define wake times, unlock methods, credentials, and auto-sleep behavior, with a test flow to validate the sequence.
  • Implement a wake/unlock engine and alarm scheduler/receiver that execute shell-based wake and unlock sequences from a privileged process, including root detection and Doze-aware exact alarms.
  • Add navigation and routing for the new wake schedule editor and gate the feature behind a Root-only availability check with explanatory dialogs.

Bug Fixes:

  • Fix game window drift in background virtual display mode by repeatedly checking the game display, attempting to repin tasks to the virtual display, and exposing warnings when repin fails.
  • Prevent crashes and misbehavior in alarm scheduling by handling missing PendingIntents, enforcing immutable flags on Android 12+, and swallowing scheduler exceptions.
  • Correct unlock method cycling so the keyguard option loops back to swipe instead of getting stuck at the end of the list.

Enhancements:

  • Extend app watchdog to detect and report sustained display drift with a configurable grace period before auto-repin, reducing interference with login/auth flows.
  • Refine settings UI behavior so disabled switches remain tappable to surface explanatory dialogs while visually indicating the disabled state.
  • Improve remote activity startup reliability by validating that the app remains on the target display after launch and retrying pinning when ROMs move tasks back to the main screen.

Build:

  • Adjust asset manifest generation to precreate the MaaResource assets directory at configuration time and enable Gradle tooling parallel sync for Gradle 9.4+.

Documentation:

  • Add English and Chinese user-facing strings describing scheduled wake-and-unlock configuration, drift auto-repin settings, and new background display drift messages and runlog entries.

@sourcery-ai

sourcery-ai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

实现仅 root 可用的定时唤醒 + 解锁功能,涵盖 UI、存储、闹钟调度和 shell 执行;新增“漂移自动重新固定”的宽限期控制;加固虚拟显示固定与唤醒闹钟的鲁棒性;并更新 Gradle 与本地化文案。

定时唤醒与解锁流程的时序图

sequenceDiagram
    participant WakeAlarmScheduler
    participant AlarmManager
    participant WakeAlarmReceiver
    participant WakeUnlockEngine
    participant RemoteServiceManager
    participant RemoteServiceImpl

    WakeAlarmScheduler->>WakeAlarmScheduler: reschedule()
    WakeAlarmScheduler->>AlarmManager: setExactAndAllowWhileIdle(time, PendingIntent)

    AlarmManager-->>WakeAlarmReceiver: onReceive(ACTION_WAKE)
    WakeAlarmReceiver->>WakeAlarmReceiver: acquire PARTIAL_WAKE_LOCK
    WakeAlarmReceiver->>WakeUnlockEngine: wakeAndUnlock(WakeConfig)

    WakeUnlockEngine->>RemoteServiceManager: useRemoteService(timeoutMs)
    RemoteServiceManager->>RemoteServiceImpl: executeShellCommand(script)
    RemoteServiceImpl-->>RemoteServiceManager: exitCode
    RemoteServiceManager-->>WakeUnlockEngine: success/failure

    WakeUnlockEngine-->>WakeAlarmReceiver: result
    WakeAlarmReceiver->>WakeAlarmScheduler: reschedule()
    WakeAlarmReceiver->>WakeAlarmReceiver: release PARTIAL_WAKE_LOCK
Loading

漂移自动重新固定宽限期处理的时序图

sequenceDiagram
    participant AppWatchdog
    participant AppSettingsManager
    participant AppAliveChecker
    participant RemoteServiceManager
    participant RemoteServiceImpl

    AppWatchdog->>AppAliveChecker: isAppOnBackgroundDisplay(packageName)
    AppAliveChecker-->>AppWatchdog: onDisplay?

    alt app on virtual display
        AppWatchdog->>AppWatchdog: reset driftFirstSeenMs, driftNotified
    else app drifted
        AppWatchdog->>AppSettingsManager: driftAutoRepinEnabled / driftAutoRepinDelaySec
        AppSettingsManager-->>AppWatchdog: enabled, delaySec
        AppWatchdog->>AppWatchdog: record driftFirstSeenMs
        AppWatchdog->>AppWatchdog: wait until now - driftFirstSeenMs >= delayMs
        AppWatchdog->>AppAliveChecker: moveAppToBackgroundDisplay(packageName)
        AppAliveChecker->>RemoteServiceManager: moveAppToVirtualDisplay(packageName)
        RemoteServiceManager->>RemoteServiceImpl: moveAppToVirtualDisplay(packageName)
        RemoteServiceImpl-->>RemoteServiceManager: success?
        RemoteServiceManager-->>AppAliveChecker: success?
        AppAliveChecker-->>AppWatchdog: success?
        alt move failed and !driftNotified
            AppWatchdog->>AppWatchdog: set driftNotified = true
            AppWatchdog->>AppWatchdog: _displayDriftEvent.tryEmit(packageName)
        end
    end
Loading

文件级变更

Change Details Files
添加由 root shell 命令、闹钟和新的设置界面支撑的“定时唤醒 + 解锁”功能。
  • 引入 WakeUnlockEngine,通过 RemoteService 运行唤醒/解锁的 shell 序列,包括“先锁屏再唤醒”的测试辅助方法以及凭据转义处理。
  • 引入 WakeAlarmScheduler 和 WakeAlarmReceiver,从 HH:mm CSV 计算下一次唤醒时间,设置精准且适配 Doze 的闹钟,持有唤醒锁,并在触发后重新调度。
  • 扩展 RemoteService AIDL/RemoteServiceImpl,新增 moveAppToVirtualDisplay、executeShellCommand 和 hasRootPrivilege,经由 ProcessBuilder 执行。
  • 在 AppSettings/AppSettingsManager 中持久化唤醒计划配置(启用标记、时间 CSV、解锁类型、凭据、自动休眠延迟),并做校验与边界收缩。
  • 将唤醒功能接入 DI(Koin)、导航(新增 WAKE_SCHEDULE_EDITOR 路由)以及清单中的广播接收器注册。
  • 新增 SettingsView 与 WakeScheduleEditorView UI,用于切换功能开关、编辑时间、解锁类型/密码、自动休眠滑杆,并支持运行测试与结果 Toast;通过 wakeFeatureAvailable 和 Root 后端进行功能门控,提供说明弹窗并调整 SettingSwitchItem 行为。
app/src/main/java/com/aliothmoon/maameow/domain/service/WakeUnlockEngine.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/WakeAlarmScheduler.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/WakeAlarmReceiver.kt
app/src/main/java/com/aliothmoon/maameow/remote/RemoteServiceImpl.kt
app/src/main/aidl/com/aliothmoon/maameow/RemoteService.aidl
app/src/main/java/com/aliothmoon/maameow/domain/models/AppSettings.kt
app/src/main/java/com/aliothmoon/maameow/data/preferences/AppSettingsManager.kt
app/src/main/java/com/aliothmoon/maameow/koin/AppModule.kt
app/src/main/java/com/aliothmoon/maameow/presentation/navigation/AppNavigation.kt
app/src/main/java/com/aliothmoon/maameow/constant/Routes.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/WakeScheduleEditorView.kt
app/src/main/res/values/strings.xml
app/src/main/res/values-en/strings.xml
app/src/main/AndroidManifest.xml
实现“漂移自动重新固定”的宽限期配置,并改进虚拟显示固定与漂移处理。
  • 在 AppSettings/AppSettingsManager 中新增 driftAutoRepinEnabled 与 driftAutoRepinDelaySec,提供默认值与范围校验。
  • 扩展 AppWatchdog,跟踪 driftFirstSeenMs,在重新固定前执行可配置延迟,并在自动重新固定失败时发出 displayDriftEvent;当应用回到目标显示时重置状态。
  • 扩展 AppAliveChecker/RemoteAppAliveChecker 和 RemoteServiceImpl,新增 moveAppToVirtualDisplay,委托给 ActivityUtils.repinAppToDisplay。
  • 在 ActivityUtils 中新增 ensureAppOnDisplay 与 repinAppToDisplay,利用隐藏的 IActivityTaskManager 反射以及 am display move-stack 兜底;新增从最近任务获取显示 ID 的 getAppDisplayId 逻辑,以及与漂移相关的常量。
  • 更新 DriverClass.startApp,在启动后调用 ensureAppOnDisplay,如固定失败则及早失败。
  • 将 displayDriftEvent 接入 MaaCompositionService 的运行日志以及 BackgroundTaskView 的 Toast,使用新的本地化文案解释“漂移”现象并建议使用 FULLSCREEN。
  • 在 SettingsView 中添加配置项以启用漂移自动重新固定,并通过带数字过滤与校验的对话框编辑延迟。
app/src/main/java/com/aliothmoon/maameow/domain/models/AppSettings.kt
app/src/main/java/com/aliothmoon/maameow/data/preferences/AppSettingsManager.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/AppWatchdog.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/AppAliveChecker.kt
app/src/main/java/com/aliothmoon/maameow/remote/RemoteServiceImpl.kt
app/src/main/java/com/aliothmoon/maameow/remote/internal/ActivityUtils.kt
app/src/main/java/com/aliothmoon/maameow/maa/DriverClass.java
app/src/main/java/com/aliothmoon/maameow/domain/service/MaaCompositionService.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/background/BackgroundTaskView.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt
app/src/main/res/values/strings.xml
app/src/main/res/values-en/strings.xml
优化设置页行为,增加唤醒功能可用性门控,并调整在禁用状态下的开关交互。
  • 在 SettingsViewModel 中添加 wakeFeatureAvailable、wakeScheduleEnabled、唤醒计划状态流、唤醒测试结果以及漂移自动重新固定相关流,并与 AppSettingsManager 和 WakeAlarmScheduler/WakeUnlockEngine 进行绑定。
  • 在 SettingsView 中收集新流,展示漂移延迟对话框和“唤醒需要 root”的提示对话框,插入漂移自动重新固定开关/可点击的延迟项,并新增唤醒计划分区行,点击时导航至编辑器或展示 root 要求。
  • 重写 SettingSwitchItem,在视觉上禁用时仍保持整行可点击,调整标题/描述的颜色,并通过 onClick 翻转选中状态,而 Switch 本身仍遵守 enabled 标记。
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt
app/src/main/java/com/aliothmoon/maameow/presentation/viewmodel/SettingsViewModel.kt
app/src/main/java/com/aliothmoon/maameow/data/preferences/AppSettingsManager.kt
调整 Gradle 资源清单任务以兼容 Gradle 9,并启用并行 tooling。
  • 修改 asset-manifest 任务,在配置期预创建 MaaSync/MaaResource 目录并复用一个 File 引用;保留 doFirst 作为运行期的安全兜底。
  • 将 sourceDir 设置为具体的 maaResourceDir,而非由路径字符串派生的 DirectoryProperty。
  • 在 gradle.properties 中启用 org.gradle.tooling.parallel,并添加关于 Gradle 9.4+ 的说明注释。
app/asset-manifest.gradle.kts
gradle.properties

与关联 Issue 的符合度评估

Issue Objective Addressed Explanation
#191 防止或自动纠正游戏窗口从虚拟(后台)显示漂移到前台主显示,从而确保后台模式任务可以持续运行。

可能关联的 Issue

  • #(建议)能否添加自动解锁模块:Issue 期望实现“定时自动唤醒并解锁手机”;本 PR 正好新增了“定时唤醒 + 解锁”功能以满足该需求。

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 摘要。也可以在 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 来触发新的审查!

Customizing Your Experience

打开你的 dashboard 以:

  • 启用或禁用审查功能,例如 Sourcery 自动生成的 Pull Request 摘要、审查者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查说明。
  • 调整其他审查相关设置。

Getting Help

Original review guide in English

Reviewer's Guide

Implements a root-only scheduled wake + unlock feature with UI, storage, alarm scheduling and shell execution; adds drift auto-repin grace-period controls; hardens virtual-display pinning and wake alarm robustness; and updates Gradle and localization strings.

Sequence diagram for scheduled wake and unlock flow

sequenceDiagram
    participant WakeAlarmScheduler
    participant AlarmManager
    participant WakeAlarmReceiver
    participant WakeUnlockEngine
    participant RemoteServiceManager
    participant RemoteServiceImpl

    WakeAlarmScheduler->>WakeAlarmScheduler: reschedule()
    WakeAlarmScheduler->>AlarmManager: setExactAndAllowWhileIdle(time, PendingIntent)

    AlarmManager-->>WakeAlarmReceiver: onReceive(ACTION_WAKE)
    WakeAlarmReceiver->>WakeAlarmReceiver: acquire PARTIAL_WAKE_LOCK
    WakeAlarmReceiver->>WakeUnlockEngine: wakeAndUnlock(WakeConfig)

    WakeUnlockEngine->>RemoteServiceManager: useRemoteService(timeoutMs)
    RemoteServiceManager->>RemoteServiceImpl: executeShellCommand(script)
    RemoteServiceImpl-->>RemoteServiceManager: exitCode
    RemoteServiceManager-->>WakeUnlockEngine: success/failure

    WakeUnlockEngine-->>WakeAlarmReceiver: result
    WakeAlarmReceiver->>WakeAlarmScheduler: reschedule()
    WakeAlarmReceiver->>WakeAlarmReceiver: release PARTIAL_WAKE_LOCK
Loading

Sequence diagram for drift auto-repin grace-period handling

sequenceDiagram
    participant AppWatchdog
    participant AppSettingsManager
    participant AppAliveChecker
    participant RemoteServiceManager
    participant RemoteServiceImpl

    AppWatchdog->>AppAliveChecker: isAppOnBackgroundDisplay(packageName)
    AppAliveChecker-->>AppWatchdog: onDisplay?

    alt app on virtual display
        AppWatchdog->>AppWatchdog: reset driftFirstSeenMs, driftNotified
    else app drifted
        AppWatchdog->>AppSettingsManager: driftAutoRepinEnabled / driftAutoRepinDelaySec
        AppSettingsManager-->>AppWatchdog: enabled, delaySec
        AppWatchdog->>AppWatchdog: record driftFirstSeenMs
        AppWatchdog->>AppWatchdog: wait until now - driftFirstSeenMs >= delayMs
        AppWatchdog->>AppAliveChecker: moveAppToBackgroundDisplay(packageName)
        AppAliveChecker->>RemoteServiceManager: moveAppToVirtualDisplay(packageName)
        RemoteServiceManager->>RemoteServiceImpl: moveAppToVirtualDisplay(packageName)
        RemoteServiceImpl-->>RemoteServiceManager: success?
        RemoteServiceManager-->>AppAliveChecker: success?
        AppAliveChecker-->>AppWatchdog: success?
        alt move failed and !driftNotified
            AppWatchdog->>AppWatchdog: set driftNotified = true
            AppWatchdog->>AppWatchdog: _displayDriftEvent.tryEmit(packageName)
        end
    end
Loading

File-Level Changes

Change Details Files
Add scheduled wake + unlock feature backed by root shell commands, alarms, and new settings UI.
  • Introduce WakeUnlockEngine to run wake/unlock shell sequences via RemoteService, including lock-then-wake test helpers and credential escaping.
  • Introduce WakeAlarmScheduler and WakeAlarmReceiver to compute next wake time from HH:mm CSV, arm exact Doze-friendly alarms, hold wake locks, and reschedule after firing.
  • Extend RemoteService AIDL/RemoteServiceImpl with moveAppToVirtualDisplay, executeShellCommand, and hasRootPrivilege, executed via ProcessBuilder.
  • Persist wake schedule configuration (enable flag, times CSV, unlock type, credential, auto-sleep delay) in AppSettings/AppSettingsManager with validation and clamping.
  • Wire wake feature into DI (Koin), navigation (new WAKE_SCHEDULE_EDITOR route), and manifest receiver registration.
  • Add SettingsView and new WakeScheduleEditorView UI for toggling the feature, editing times, unlock type/password, auto sleep slider, and running tests with result toasts; guard feature by wakeFeatureAvailable and Root backend, with explanatory dialog and modified SettingSwitchItem behavior.
app/src/main/java/com/aliothmoon/maameow/domain/service/WakeUnlockEngine.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/WakeAlarmScheduler.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/WakeAlarmReceiver.kt
app/src/main/java/com/aliothmoon/maameow/remote/RemoteServiceImpl.kt
app/src/main/aidl/com/aliothmoon/maameow/RemoteService.aidl
app/src/main/java/com/aliothmoon/maameow/domain/models/AppSettings.kt
app/src/main/java/com/aliothmoon/maameow/data/preferences/AppSettingsManager.kt
app/src/main/java/com/aliothmoon/maameow/koin/AppModule.kt
app/src/main/java/com/aliothmoon/maameow/presentation/navigation/AppNavigation.kt
app/src/main/java/com/aliothmoon/maameow/constant/Routes.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/WakeScheduleEditorView.kt
app/src/main/res/values/strings.xml
app/src/main/res/values-en/strings.xml
app/src/main/AndroidManifest.xml
Implement drift auto-repin grace period configuration and improved virtual-display pinning and drift handling.
  • Add driftAutoRepinEnabled and driftAutoRepinDelaySec to AppSettings/AppSettingsManager, with defaults and range validation.
  • Extend AppWatchdog to track driftFirstSeenMs, enforce configurable delay before repin, and emit displayDriftEvent when auto-repin fails; reset state when app returns to display.
  • Extend AppAliveChecker/RemoteAppAliveChecker and RemoteServiceImpl with moveAppToVirtualDisplay, delegating to ActivityUtils.repinAppToDisplay.
  • In ActivityUtils, add ensureAppOnDisplay and repinAppToDisplay with hidden IActivityTaskManager reflection and am display move-stack fallback; add getAppDisplayId logic for recent tasks, plus drift-related constants.
  • Update DriverClass.startApp to call ensureAppOnDisplay after launch and fail early if pinning fails.
  • Wire displayDriftEvent into MaaCompositionService runlog and BackgroundTaskView toast with new localized strings explaining drift and suggesting FULLSCREEN.
  • Add UI controls in SettingsView for enabling drift auto-repin and editing the delay via dialog with numeric filter and validation.
app/src/main/java/com/aliothmoon/maameow/domain/models/AppSettings.kt
app/src/main/java/com/aliothmoon/maameow/data/preferences/AppSettingsManager.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/AppWatchdog.kt
app/src/main/java/com/aliothmoon/maameow/domain/service/AppAliveChecker.kt
app/src/main/java/com/aliothmoon/maameow/remote/RemoteServiceImpl.kt
app/src/main/java/com/aliothmoon/maameow/remote/internal/ActivityUtils.kt
app/src/main/java/com/aliothmoon/maameow/maa/DriverClass.java
app/src/main/java/com/aliothmoon/maameow/domain/service/MaaCompositionService.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/background/BackgroundTaskView.kt
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt
app/src/main/res/values/strings.xml
app/src/main/res/values-en/strings.xml
Refine settings view behavior, add wake feature availability gating, and adjust switch interactions for disabled states.
  • Add wakeFeatureAvailable, wakeScheduleEnabled, wake schedule state flows, wake test result, and drift auto-repin flows to SettingsViewModel, wiring to AppSettingsManager and WakeAlarmScheduler/WakeUnlockEngine.
  • In SettingsView, collect new flows, show drift delay dialog and wake-need-root dialog, insert drift auto-repin switch/clickable delay item, and add the wake schedule section row that navigates to editor or shows root requirement.
  • Override SettingSwitchItem to keep row clickable when visually disabled, adjusting title/description colors and onClick to flip checked state, while the Switch itself respects enabled flag.
app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt
app/src/main/java/com/aliothmoon/maameow/presentation/viewmodel/SettingsViewModel.kt
app/src/main/java/com/aliothmoon/maameow/data/preferences/AppSettingsManager.kt
Adjust Gradle asset manifest task for Gradle 9 compatibility and enable parallel tooling.
  • Change asset-manifest task to pre-create MaaSync/MaaResource directory during configuration, and reuse a File reference; keep doFirst as a runtime safeguard.
  • Set sourceDir to the concrete maaResourceDir instead of a DirectoryProperty derived from path string.
  • Enable org.gradle.tooling.parallel in gradle.properties with comment about Gradle 9.4+.
app/asset-manifest.gradle.kts
gradle.properties

Assessment against linked issues

Issue Objective Addressed Explanation
#191 Prevent or automatically correct the game window drifting from the virtual (background) display to the main foreground display so that background mode tasks can continue running.

Possibly linked issues

  • #(建议)能否添加自动解锁模块: Issue要定时自动唤醒并解锁手机;PR正好新增“定时唤醒+解锁”功能满足该需求。

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

@dorkytiger dorkytiger changed the title Feat/scheduled wake unlock feat(scheduled wake unlock):root模式下锁屏定时唤醒功能 Jul 30, 2026

@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.

Hey - 我发现了两个问题,并给出了一些高层次的反馈:

  • wakeUnlockTypeLabel 辅助函数在 SettingsView.ktWakeScheduleEditorView.kt 中都被重复定义,并且直接写死了英文标签;建议将其集中到一个地方,并改用字符串资源,这样可以保持本地化的一致性。
  • 在多个地方,时间输入(HH:mm)会被静默验证,非法输入会直接被忽略;既然你已经添加了 settings_wake_invalid_time,可以在一个 token 被拒绝时向用户显示反馈,而不是静默失败。
  • 更新后的 SettingSwitchItem/SettingRow 现在总是报告 enabled = true,并在整行点击时切换状态,这可能会让无障碍语义和点击目标变得混乱;建议在支持不可用功能说明点击处理的同时,保留整行的禁用状态。
给 AI 代理的提示
Please address the comments from this code review:

## Overall Comments
- The `wakeUnlockTypeLabel` helper is duplicated in both `SettingsView.kt` and `WakeScheduleEditorView.kt` and hardcodes English labels; consider centralizing it and using string resources instead so localization stays consistent.
- In several places the time input (`HH:mm`) is silently validated and invalid entries are just ignored; since you added `settings_wake_invalid_time`, you could surface feedback to the user when a token is rejected instead of failing silently.
- The updated `SettingSwitchItem`/`SettingRow` now always reports `enabled = true` and toggles on row click, which may confuse accessibility semantics and click targets; consider preserving the row’s disabled state while still supporting a separate explanation click handler for unavailable features.

## Individual Comments

### Comment 1
<location path="app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt" line_range="1870-1874" />
<code_context>
+    }
+}
+
+private fun wakeUnlockTypeLabel(type: String): String = when (type) {
+    "swipe" -> "Swipe (no lock)"
+    "pin" -> "PIN"
+    "password" -> "Password"
+    "keyguard" -> "Force dismiss keyguard"
+    else -> type
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** Unlock type labels are hardcoded English strings instead of using localized resources.

This helper should use the existing localized wake unlock type strings (e.g., settings_wake_unlock_type_swipe, settings_wake_unlock_type_pin) instead of hardcoded English text. Otherwise, it bypasses localization and can drift from the canonical wording in strings.xml. Please resolve the label via resources, consistent with other setting labels.
</issue_to_address>

### Comment 2
<location path="app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/WakeScheduleEditorView.kt" line_range="229-238" />
<code_context>
+                        style = MaterialTheme.typography.bodySmall,
+                        color = MaterialTheme.colorScheme.onSurfaceVariant
+                    )
+                    OutlinedTextField(
+                        value = delayInput,
+                        onValueChange = { s ->
</code_context>
<issue_to_address>
**🚨 suggestion (security):** PIN/password credential field shows sensitive input as plain text instead of obscuring it.

The `OutlinedTextField` for `wakeCredential` in `UnlockConfigCard` currently shows the credential in clear text and uses standard keyboard options. For a field labeled as a credential, it should behave like a password input (e.g., `PasswordVisualTransformation` with `KeyboardType.Password`), or the UI should clearly indicate that the value is intentionally visible so users can make an informed choice.
</issue_to_address>

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

Hey - I've found 2 issues, and left some high level feedback:

  • The wakeUnlockTypeLabel helper is duplicated in both SettingsView.kt and WakeScheduleEditorView.kt and hardcodes English labels; consider centralizing it and using string resources instead so localization stays consistent.
  • In several places the time input (HH:mm) is silently validated and invalid entries are just ignored; since you added settings_wake_invalid_time, you could surface feedback to the user when a token is rejected instead of failing silently.
  • The updated SettingSwitchItem/SettingRow now always reports enabled = true and toggles on row click, which may confuse accessibility semantics and click targets; consider preserving the row’s disabled state while still supporting a separate explanation click handler for unavailable features.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `wakeUnlockTypeLabel` helper is duplicated in both `SettingsView.kt` and `WakeScheduleEditorView.kt` and hardcodes English labels; consider centralizing it and using string resources instead so localization stays consistent.
- In several places the time input (`HH:mm`) is silently validated and invalid entries are just ignored; since you added `settings_wake_invalid_time`, you could surface feedback to the user when a token is rejected instead of failing silently.
- The updated `SettingSwitchItem`/`SettingRow` now always reports `enabled = true` and toggles on row click, which may confuse accessibility semantics and click targets; consider preserving the row’s disabled state while still supporting a separate explanation click handler for unavailable features.

## Individual Comments

### Comment 1
<location path="app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/SettingsView.kt" line_range="1870-1874" />
<code_context>
+    }
+}
+
+private fun wakeUnlockTypeLabel(type: String): String = when (type) {
+    "swipe" -> "Swipe (no lock)"
+    "pin" -> "PIN"
+    "password" -> "Password"
+    "keyguard" -> "Force dismiss keyguard"
+    else -> type
+}
</code_context>
<issue_to_address>
**issue (bug_risk):** Unlock type labels are hardcoded English strings instead of using localized resources.

This helper should use the existing localized wake unlock type strings (e.g., settings_wake_unlock_type_swipe, settings_wake_unlock_type_pin) instead of hardcoded English text. Otherwise, it bypasses localization and can drift from the canonical wording in strings.xml. Please resolve the label via resources, consistent with other setting labels.
</issue_to_address>

### Comment 2
<location path="app/src/main/java/com/aliothmoon/maameow/presentation/view/settings/WakeScheduleEditorView.kt" line_range="229-238" />
<code_context>
+                        style = MaterialTheme.typography.bodySmall,
+                        color = MaterialTheme.colorScheme.onSurfaceVariant
+                    )
+                    OutlinedTextField(
+                        value = delayInput,
+                        onValueChange = { s ->
</code_context>
<issue_to_address>
**🚨 suggestion (security):** PIN/password credential field shows sensitive input as plain text instead of obscuring it.

The `OutlinedTextField` for `wakeCredential` in `UnlockConfigCard` currently shows the credential in clear text and uses standard keyboard options. For a field labeled as a credential, it should behave like a password input (e.g., `PasswordVisualTransformation` with `KeyboardType.Password`), or the UI should clearly indicate that the value is intentionally visible so users can make an informed choice.
</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.

@Aliothmoon

Copy link
Copy Markdown
Owner

这个风险挺高的说是,测试过其他国产ROM吗

@dorkytiger

Copy link
Copy Markdown
Contributor Author

这个风险挺高的说是,测试过其他国产ROM吗

额,做的时候想的是,既然只有root的才能使用,那说明已经存在风险了,而且现阶段国产rom要大部分root的难度有点大,但是你这么一说,确实默认承担风险也不好,我看看能不能找几台国产机搞搞吧,不行的话,我自己单开一个app就好了

@dorkytiger

dorkytiger commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

这个风险挺高的说是,测试过其他国产ROM吗

今天找群友测试了下,大概有5,6台左右,都是国产rom,小米,colors等,修复后跑了一下,都通过了,以下是改动内容

Pull Request: feat(wake-unlock): 动态分辨率 + 滑动校准 + 解锁校验重试

背景

定时唤醒+解锁功能(前序 PR 引入)在实际设备上暴露了几个兼容性问题:

  1. swipe 坐标硬编码 1080×2400:不同设备分辨率差异大(小米 1440×3200、一加 1200×2608 等),硬编码坐标在非 1080p 设备上经常滑不到 MIUI/ColorOS 锁屏手势区,导致"偶尔解锁失败"
  2. swipe 后等待时间固定 0.5s:MIUI/ColorOS 锁屏动画 + PIN 键盘弹出 + 密码框获焦需要更长时间,0.5s 不够导致 input text 注入到空气里
  3. 无解锁校验:shell rc=0 不代表真的解锁了,失败时无日志可查

UI改动

image image image

代码改动

1. AIDL 新增 executeShellCommandCaptureOutput

RemoteService.aidl + RemoteServiceImpl.kt 新增方法,返回 shell stdout(合并 stderr),用于需要解析命令输出的场景(wm size 解析分辨率、dumpsys window 校验解锁状态)。

2. 动态分辨率查询 + 缓存(WakeUnlockEngine

  • 首次 wakeAndUnlock 调用时通过 wm size 查询实际分辨率,缓存到 @Volatile 字段(同进程内分辨率不会变)
  • swipe 坐标基于实际分辨率动态计算:
    • 起点 X = 屏幕宽度 50%
    • 起点 Y = 屏幕高度 90%(MIUI/ColorOS/AOSP 手势区一般在底部 ~15%)
    • 终点 Y = 屏幕高度 30%
  • 查询失败回退默认 1080×2400

3. 解锁状态校验

  • 主序列执行后通过 dumpsys window 检查 mDreamingLockscreen=true / mShowingLockscreen=true
  • 三种状态:true=已解锁、false=仍锁屏、null=无法解析
  • 日志明确标记:unlock verified ✓ / still locked ✗ / unlock state unknown

4. 解锁失败自动重试

  • 当「shell 成功但仍锁屏」时,等待 1s 后重新执行整个解锁序列
  • 默认最多重试 2 次(共 3 次尝试),可配置
  • KEYGUARD 解锁方式不校验(直接相信 shell rc)

5. 滑动起点用户校准(方案 D:混合方案)

  • 默认:用 90% 高度(覆盖 80% 设备)
  • 踩坑时:用户可在「设置 → 锁屏解锁配置」点「校准滑动起点」,全屏黑色对话框触摸一次,记录相对屏幕百分比坐标
  • 校准数据存 DataStore(swipeStartXPercent / swipeStartYPercent,默认 -1.0 表示未校准)
  • 引擎优先用校准值,未校准回退默认
  • 提供「清除校准」按钮恢复默认

6. PIN 等待时间可配置

  • wakePinWaitSec 字段(默认 1.5s,范围 0.0–10.0s)
  • 设置页下拉选择:0.5 / 1.0 / 1.5 / 2.0 / 2.5 / 3.0 秒
  • MIUI/ColorOS 用户可调到 2.0s+,AOSP 用户可调到 0.8s

7. 重试次数可配置

  • wakeUnlockMaxRetries 字段(默认 2,范围 0–5)
  • 设置页下拉选择:0(不重试)/ 1 / 2(推荐)/ 3

8. 设置页 UI

「锁屏解锁配置」二级页新增两个卡片:

┌─ 滑动起点校准 ─────────────────────┐
│ 校准滑动起点    已校准:X=50%, Y=92% │
│ 清除校准        恢复为默认坐标       │
└────────────────────────────────────┘
┌─ 高级 ─────────────────────────────┐
│ PIN 等待时间    当前 1.5 秒          │
│ 解锁失败重试    当前 2 次            │
└────────────────────────────────────┘

9. 触发日志增强

定时任务触发时记录完整上下文:

使用校准滑动起点: (50%,92%)
PIN 等待: 1.5s, 最大重试: 2

logcat 详细记录每次尝试:

WakeUnlockEngine: screen=1200x2608, type=PIN, calibrated=(0.50,0.92), pinWait=1.5s, maxRetries=2
WakeUnlockEngine: executing 7 commands (attempt 1)
WakeUnlockEngine: shell rc=0
WakeUnlockEngine: still locked after attempt 1 ✗
WakeUnlockEngine: retry attempt 2/3
WakeUnlockEngine: executing 7 commands (attempt 2)
WakeUnlockEngine: unlock verified ✓ (attempt 2)

文件改动清单

文件 改动
RemoteService.aidl +4 行:新增 executeShellCommandCaptureOutput (=35)
RemoteServiceImpl.kt +21 行:实现该方法
WakeUnlockEngine.kt +100 行:动态分辨率 + 校验 + 重试 + 校准支持
AppSettings.kt +24 行:4 个新字段
AppSettingsManager.kt +58 行:StateFlow + setter
SettingsViewModel.kt +50 行:暴露 StateFlow + 读取校准
ScheduleExecutionService.kt +25 行:读取配置 + 触发日志
WakeAlarmReceiver.kt +17 行:读取配置 + logcat
WakeScheduleEditorView.kt +160 行:校准卡片 + 全屏对话框 + 高级设置
strings.xml / values-en/strings.xml +16 条新字符串

测试

  • ✅ AOSP 模拟器 API 34(1080×2400):默认坐标解锁成功
  • ✅ 用户真机小米(1440×3200 MIUI):动态分辨率 + 校准后解锁成功
  • ✅ 用户真机一加(1200×2608 MIUI):重试机制兜底成功
  • ✅ 单元测试:./gradlew :app:testDebugUnitTest 全绿

兼容性

  • 数据迁移:新字段均有默认值,老用户升级无需迁移
  • AIDL 兼容:新 AIDL 方法需要提权进程重启生效。若未更新,executeShellCommandCaptureOutputRemoteException,自动回退默认分辨率 1080×2400
  • 未校准用户:完全无感,行为和之前一致(90% 高度)
  • 已校准用户:换手机/ROM 重校一次即可

已知限制

  • dumpsys window 字段命名因 ROM 而异,当前仅识别 mDreamingLockscreen / mShowingLockscreen,极少数 ROM 可能返回 null(unknown)
  • 校准数据基于屏幕百分比,屏幕旋转后需要重校(锁屏场景一般不涉及旋转)
  • Android 14+ 设备 svc keyguard disable 已被 AOSP 删除,KEYGUARD 解锁方式退化为「只唤醒,不解锁」

dorkytiger and others added 4 commits August 5, 2026 20:54
1.优化了解析逻辑,实现动态计算和手动校准
2.能够设置解析pin的等待时间和重试次数
3.开启功能移入到定时任务
@Aliothmoon
Aliothmoon force-pushed the feat/scheduled-wake-unlock branch from f28eb15 to fc6bd93 Compare August 5, 2026 16:34
@Aliothmoon
Aliothmoon merged commit 48a7a99 into Aliothmoon:main Aug 5, 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.

[Bug] 后台模式明日方舟弹到前台

2 participants