## Context 当前 app 已有短信验证码接收实现:Manifest 声明了 `RECEIVE_SMS`、`READ_SMS`,静态 `SmsReceiver` 监听 `android.provider.Telephony.SMS_RECEIVED`,`MainActivity` 提供权限申请、最近结果展示、`READ_SMS` 最新短信读取、ContentObserver 和短时轮询诊断。用户反馈“逻辑试过了能用”,但不确定为什么某些场景 `onReceive` 收不到。 目标设备是小米 12S、澎湃 OS 3、Android 15。根据 Android 官方文档,`RECEIVE_SMS` 允许应用接收 SMS,但它是 dangerous 且 hard restricted 权限,是否能真正持有可能受安装来源/安装器 allowlist 影响。Android 15 又新增了 `BOOT_COMPLETED` 启动部分前台服务类型的限制;Doze/App Standby 白名单也不是无限制后台执行。小米官方支持文档说明 HyperOS/小米系统存在“Background autostart”用户开关,路径为 Settings > Apps > Permissions > Background autostart。 参考资料: - Android 15 前台服务类型变更:https://developer.android.com/about/versions/15/changes/foreground-service-types - Android 15 行为变更:https://developer.android.com/about/versions/15/behavior-changes-15 - Doze/App Standby 与电池优化:https://developer.android.com/training/monitoring-device-state/doze-standby - `RECEIVE_SMS` 权限:https://developer.android.com/reference/android/Manifest.permission#RECEIVE_SMS - 小米后台自启动设置:https://www.mi.com/global/support/faq/details/KA-507608/ ## Goals / Non-Goals **Goals:** - 先形成完整 spec,不直接写代码。 - 在当前可用短信接收 app 上补齐后台保活和开机恢复方案。 - 让用户能手动完成小米设置,并在 app 内看到哪些设置已完成、哪些只能人工确认。 - 让 `onReceive` 收不到时有明确诊断:权限、安装来源、force-stop、系统广播、HyperOS 后台策略、短信是否进入收件箱。 - 后续实现保持可解释、可关闭、可验证。 **Non-Goals:** - 不做无通知、不可感知、规避系统策略的隐藏保活。 - 不承诺杀进程、force-stop、清后台、系统级省电清理后仍 100% 存活。 - 不把 C++ fork/native daemon 当成可靠能力;Android 应用沙箱和系统进程管理不会因为 native 代码而失效。 - 不把 app 做成默认短信客户端。 - 不要求本轮编译。 ## API Strategy ### 1. 系统短信广播仍是主路径 短信进入设备时,主路径仍是 `SMS_RECEIVED_ACTION`。这是最直接的验证码捕获路径,但它依赖: - 应用真正持有 `RECEIVE_SMS`。 - 应用未被用户 force-stop。 - 系统或厂商策略允许静态 receiver 在当前状态下被拉起。 - 短信确实由 Android Telephony 入库/分发,而不是被系统短信 app 或安全策略特殊处理。 现有 `SmsReceiver` 的 manifest 写法包含 `android:permission="android.permission.BROADCAST_SMS"`,该写法用于限制只有持有系统广播权限的发送方能投递该 receiver;系统短信广播通常满足此条件。后续诊断要验证它不是问题根因,但不建议先移除。 ### 2. 前台服务用于“可见后台运行”,不用于读取短信 新增 `SmsKeepAliveService`: - app 前台点击“开启常驻保活”后调用 `startForegroundService`。 - 服务在 5 秒内调用 `startForeground`,展示低打扰常驻通知。 - 通知内容显示“短信监听运行中”、最近一次心跳、最近一次短信来源。 - 服务只做状态心跳、通知刷新、诊断状态保存。 - 短信接收仍由 `SmsReceiver` 和收件箱兜底路径处理。 这样做的原因是前台服务可以提升后台进程可见性,但不能替代短信广播,也不能保证被厂商永不杀。它的价值是让系统和用户明确知道该 app 在后台运行,并给 HyperOS “省电无限制 + 自启动”一个更稳定的承载对象。 ### 3. 开机自启动采用轻量 BootReceiver 新增 `BootReceiver` 监听: - `android.intent.action.BOOT_COMPLETED` - `android.intent.action.LOCKED_BOOT_COMPLETED` - `android.intent.action.MY_PACKAGE_REPLACED` 收到后: - 记录 boot 事件和时间。 - 检查用户是否曾开启“常驻保活”。 - 如果已开启,则尝试启动 `SmsKeepAliveService`。 - 捕获 `ForegroundServiceStartNotAllowedException` 等异常,写入诊断,不崩溃。 Android 15 对 `BOOT_COMPLETED` 启动前台服务的限制集中在 dataSync、camera、mediaPlayback、phoneCall、mediaProjection、microphone 等类型。当前保活服务不应声明这些类型;如果后续升级 targetSdk 到 35,仍必须真机验证 BootReceiver 启动服务是否被 HyperOS 额外限制。 ### 4. 电池优化与 HyperOS 设置采用“检测 + 引导” Android 标准能力: - 用 `PowerManager.isIgnoringBatteryOptimizations(packageName)` 展示是否在电池优化白名单。 - 提供 `Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS` 打开白名单设置。 - 个人自用场景可考虑 `ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` 直接请求,但 UI 必须说明它仍不等于无限后台。 小米/HyperOS 能力: - 提供“打开小米自启动设置”按钮,优先尝试常见 MIUI/HyperOS 安全中心组件。 - 如果显式组件不可用,回退 `ACTION_APPLICATION_DETAILS_SETTINGS` 和通用系统设置。 - UI 显示人工清单: - 权限:短信权限已授权。 - 自启动:用户已在 HyperOS 开启。 - 省电策略:用户已设为无限制。 - 通知:常驻通知未被关闭。 - 保活服务:正在运行。 自启动和省电无限制通常没有稳定公开 API 可直接读取真实状态,因此必须允许用户手动勾选“我已完成设置”,并把这部分标记为人工确认。 ### 5. Native/C++ 方案只做实验诊断 可以加入一个可选 native heartbeat: - Java 服务加载 native library。 - native 层定期写入一个本地 heartbeat 文件或通过 JNI 回调返回时间戳。 - 用于确认进程活着、JNI 正常、服务被杀后心跳停止。 不建议实现 native fork daemon: - Android 应用进程被系统管理,子进程同属应用 UID/cgroup,厂商清理通常会一起处理。 - 后台私自 fork 常驻进程会带来电量、兼容性和安全风险。 - 对短信广播恢复没有直接帮助。 如果用户坚持尝试,必须放在“实验开关”下,并以诊断结论交付,不作为保活主链路。 ## Why `onReceive` May Not Fire 后续 UI 和 logcat 诊断必须覆盖以下根因: - `RECEIVE_SMS` 未授权,或因 hard restricted 机制安装后实际不可持有。 - app 被 force-stop;Android 不会为 force-stopped app 投递大多数隐式广播,直到用户再次打开。 - 小米自启动未开启,系统不允许后台拉起静态 receiver 或服务。 - 省电策略不是无限制,后台/锁屏/待机时进程或广播处理被延迟/限制。 - app 刚安装后从未启动,部分系统不会让 receiver 正常进入用户期望状态。 - 短信由运营商/RCS/系统短信能力特殊处理,未走普通 SMS_RECEIVED。 - 双卡或短信格式异常导致 PDU 解析失败,但此时 receiver 应该已有日志。 - Manifest receiver 被禁用、包名不匹配、构建安装的不是当前调试版本。 - 通知权限关闭不应直接阻止短信广播,但可能影响前台服务可见性和用户判断。 诊断顺序建议: 1. 看 UI 权限状态和最近 boot/service 心跳。 2. 发送短信时抓 logcat `SmsReceive`。 3. 如果 receiver 无日志,点“读取最新短信”确认短信是否入库。 4. 如果短信已入库但 receiver 无日志,重点排查权限、force-stop、HyperOS 自启动和省电。 5. 如果 receiver 有日志但无验证码,排查 PDU/body/parser。 ## Data Model 新增 `KeepAliveState` 本地状态: - `enabledByUser`: 用户是否开启常驻保活。 - `serviceRunning`: 最近一次服务 onCreate/onStartCommand/onDestroy 状态。 - `lastHeartbeatMillis`: 服务最近心跳。 - `lastBootEvent`: 最近一次 boot/package replaced 事件。 - `lastServiceStartFailure`: 最近一次服务启动失败原因。 - `batteryOptimizationIgnored`: 标准 Android 电池优化白名单状态。 - `manualAutostartConfirmed`: 用户手动确认已开启小米自启动。 - `manualBatteryUnrestrictedConfirmed`: 用户手动确认已设置省电无限制。 - `notificationEnabledHint`: 通知是否可能可见。 保存方式优先使用 `SharedPreferences`,与现有 `SmsCaptureStore` 保持简单一致。 ## UI Strategy 首版继续使用现有 Java View UI,不引入新框架: - 增加“后台保活状态”区域: - 保活开关。 - 服务运行状态。 - 最近心跳。 - 最近开机事件。 - 最近启动失败原因。 - 增加“系统设置”区域: - 打开应用详情。 - 打开电池优化设置。 - 请求忽略电池优化。 - 打开小米自启动设置。 - 人工确认自启动已开启。 - 人工确认省电无限制已开启。 - 增加“短信广播诊断”区域: - 最近 `system_sms_broadcast` 时间。 - 最近 `sms_inbox_*` 时间。 - 当收件箱有新短信但广播未到时提示“疑似广播未投递”。 ## Test Strategy 单元/本地测试: - `KeepAliveStateStore` 读写测试。 - `BootReceiver` 在不同 action 下生成正确状态。 - `SmsKeepAliveService` 的状态更新逻辑抽出为纯 Java 方法测试。 - 设置 intent builder 测试:小米组件不可用时能 fallback。 ADB/真机验证: - `adb shell am broadcast -a android.intent.action.BOOT_COMPLETED -n com.smsreceive.app/.BootReceiver` - 重启手机后验证保活服务是否恢复。 - 开启保活通知后,后台 30 分钟、锁屏 30 分钟分别发送短信。 - 开启/关闭小米自启动、开启/关闭省电无限制,对比短信广播和收件箱兜底结果。 - 手动 force-stop 后发送短信,确认不承诺接收,并在再次打开 app 后展示诊断。 - Android Doze 测试可参考官方命令 `dumpsys deviceidle force-idle`、`am set-inactive`。 ## Rollout Plan 1. 完成本 OpenSpec 并 validate。 2. 先实现 Java 层 BootReceiver、Foreground Service、通知和状态 UI。 3. 再补小米设置入口和人工确认状态。 4. 最后根据真机结果决定是否加入 native heartbeat 实验。 5. 如果前台服务/开机恢复在 HyperOS 上仍不稳定,交付时明确记录限制和必须手动设置项。 ## Open Questions - 当前安装方式是 Android Studio/adb 直装,还是包管理器/文件管理器安装 APK;这会影响 hard restricted SMS 权限表现。 - 用户是否接受常驻通知一直显示。 - 是否希望 app 开机后自动开启保活,还是必须用户先在 UI 中开启一次后才持久化。 - 是否需要 native heartbeat 实验;建议第一轮 Java 方案真机验证后再决定。