2026-05-18 11:10:52 +08:00

157 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Context
当前 `SmsReceive` 目录几乎为空,只有 macOS 生成的 `.DS_Store`,没有 Android 工程和既有 OpenSpec 目录。用户要求先生成完整 spec 方案,再开始编码;同时明确 Android Studio、Gradle、JDK 等环境不在本次方案范围内,后续实现要参考 `Weather reference project` 的构建环境。
目标设备是小米 12S、澎湃 OS 3、Android 15。需求本质是个人自用工具收到手机短信验证码后应用读取短信正文、提取验证码并展示。由于不是 Play Store 上架应用,方案可以直接使用短信权限,但仍要面对 Android 运行时权限、Android 15 受限权限策略、厂商后台管理和短信广播分发行为。
官方 API 判断如下:
- Android `Telephony.Sms.Intents.SMS_RECEIVED_ACTION` 是收到文本短信的系统广播,需要 `RECEIVE_SMS` 权限。它是读取任意短信验证码最直接的路径。
- Google `SMS Retriever API` 不需要 `READ_SMS``RECEIVE_SMS`,但短信必须包含 app hash适合服务端短信模板可控的手机号验证不适合读取所有第三方验证码。
- Google `SMS User Consent API` 可以请求用户授权读取单条包含验证码的短信,不要求 app hash但需要弹出用户确认适合作为受限权限或广播异常时的对比验证路径。
## Goals / Non-Goals
**Goals:**
- 先形成可执行 spec不直接写业务代码。
- 建立 Android 15 上读取验证码短信的主路径和备选路径。
- 明确验证码解析、权限状态、诊断状态和真机验证标准。
- 后续实现应保持最小化:一个主界面、一个接收链路、一组诊断信息,不做复杂产品化。
- 保持短信内容本地处理,默认只展示验证码、来源、时间和短诊断摘要。
**Non-Goals:**
- 不做完整短信客户端,不替代系统短信 App。
- 不实现发送短信、删除短信、读取历史短信库或同步短信到云端。
- 不处理 Android Studio、Gradle、JDK 的重新安装和环境拉取。
- 不以 Google Play 上架合规作为约束目标。
- 不保证所有银行、平台、运营商验证码都能被无条件读取;必须通过真机验证确认。
## Decisions
### Decision 1: 主路径使用 `SMS_RECEIVED_ACTION` + `RECEIVE_SMS`
主路径选择系统短信广播。原因是目标是读取“我自己的手机收到的验证码”,短信来源不可控,很多验证码短信不会带当前 app 的 hash`SMS Retriever API` 无法覆盖任意验证码。系统广播能拿到完整 PDU再通过 `Telephony.Sms.Intents.getMessagesFromIntent(Intent)` 合并为正文,是最符合目标的能力。
实现要求:
- Manifest 声明 `android.permission.RECEIVE_SMS`
- 对 Android 6.0+ 执行运行时权限申请。
- 注册接收 `android.provider.Telephony.SMS_RECEIVED` 的 receiver。
- receiver 内只做轻量解析和状态分发,避免长耗时。
- 记录最近一次接收时间、sender、body 摘要、提取结果和失败原因。
备选方案:
- `READ_SMS` 可读取短信数据库,但需求是监听新验证码,不需要读取历史短信;默认不纳入主路径。
- 默认短信应用角色权限更强,但目标不是做短信客户端;不作为一期要求。
### Decision 2: 备选验证路径引入 `SMS User Consent API`
`SMS User Consent API` 用于验证两类问题:
- 当系统广播路径在 HyperOS 上被后台策略影响时,前台触发 consent flow 是否能拿到单条短信。
- 当用户不愿或系统不允许直接授予短信权限时,是否仍能通过一次性确认读取验证码。
限制:
- 它不是静默读取,需要用户确认。
- 它适合前台验证流程,不适合后台长期监听所有验证码。
- 它依赖 Google Play services国内 ROM 环境下需要确认设备实际可用性。
### Decision 3: `SMS Retriever API` 只作为受控短信模板能力
`SMS Retriever API` 的优点是无需短信权限,体验干净;但它要求短信包含 app hash且通常需要服务端发送符合格式的短信。对于读取第三方平台验证码它大概率不适用。因此一期只实现或预留为“自发测试短信/未来自控服务端验证码”的能力,不作为读取任意验证码的主线。
### Decision 4: 验证码解析采用多阶段规则
解析规则必须保守,避免把手机号、金额、日期误识别为验证码。
建议顺序:
1. 优先匹配包含关键词的模式:`验证码``校验码``动态码``code``verification``OTP` 附近的 4-8 位数字或字母数字。
2. 次级匹配短信中独立出现的 4-8 位数字,排除明显日期、手机号片段、金额和订单号。
3. 对带空格或短横线的验证码做归一化,例如 `12 34 56``123-456`
4. 若多个候选值并存,选择距离关键词最近、长度在 4-6 位优先、出现位置更靠前的候选。
5. 解析失败时保留失败原因,不展示完整正文。
### Decision 5: UI 首版只做诊断型工具界面
首版 UI 应该服务验证,而不是做复杂产品。建议显示:
- 当前短信权限状态。
- 主路径 receiver 状态。
- Google Play services / SMS User Consent 可用性。
- 最近一次收到短信的时间、发送方、验证码、解析策略命中类型。
- 最近失败原因例如无权限、未收到广播、正文为空、未找到验证码、API timeout。
- 手动清空最近结果按钮。
### Decision 6: 本地隐私边界
即使是自用 app也不应默认保存完整短信正文。建议
- 内存中可短暂保留最近一条完整正文用于调试开关。
- 默认持久化只保存验证码、时间、sender 摘要和解析状态。
- 不做网络上传。
- 日志避免输出完整短信正文debug 模式如需输出,必须集中开关控制。
## Android 15 And HyperOS Risk Analysis
- [Risk] `RECEIVE_SMS` 在 Android 15 或厂商系统上被标记为高风险/受限权限,安装来源和系统设置可能影响授权。
→ Mitigation: 首次启动展示权限状态;如果权限申请失败,引导到应用详情页检查“受限权限/权限管理”;同时使用 consent API 做对比验证。
- [Risk] 后台接收被 HyperOS 省电策略限制。
→ Mitigation: 首轮验证覆盖前台、后台、锁屏三种状态;如后台不稳定,增加前台服务或引导关闭省电限制作为后续任务。
- [Risk] Google Play services 在目标设备上不可用或版本不满足。
→ Mitigation: Google API 作为备选路径,主路径不依赖它;诊断页显示可用性。
- [Risk] 双卡、国际短信、长短信 PDU 合并导致 sender 或正文异常。
→ Mitigation: 使用官方 `getMessagesFromIntent` 解析,按 message body 拼接,记录 subscription id 如可用。
- [Risk] 正则误识别。
→ Mitigation: 解析结果带命中策略和置信度;测试用例覆盖误判样本。
## Migration Plan
本项目当前没有既有代码,迁移计划等同于实施顺序:
1. 完成本次 OpenSpec 评审。
2. 参考 Weather 项目创建或复制最小 Android 构建骨架。
3. 实现权限和诊断 UI。
4. 实现系统短信广播主路径。
5. 实现验证码解析器和单元测试。
6. 在小米 12S 上跑真机验证。
7. 根据真机结果决定是否补 `SMS User Consent API` 或后台稳定性处理。
Rollback 策略:
- 如果系统广播路径被目标设备限制,保留解析器和 UI降级为 `SMS User Consent API` 前台读取验证。
- 如果 Google API 不可用,不影响系统广播主路径。
## Validation Strategy
上下文验证:
- 确认 Weather 项目可作为构建环境参考。
- 确认 `SmsReceive` OpenSpec 通过 CLI validate。
- 确认 spec 任务列表不包含重装 Android Studio、Gradle、JDK。
代码验证:
- 验证码解析器单元测试覆盖中文、英文、空格、短横线、多候选、无验证码样本。
- receiver 解析逻辑可通过构造 Intent/PDU 或抽象 message input 测试核心逻辑。
- 权限状态和诊断状态可通过 ViewModel/unit test 验证。
真机验证:
- 前台打开应用后,向目标号码发送测试短信:`【测试】验证码 1234565 分钟内有效。`
- 应用退到后台后,重复发送不同验证码。
- 锁屏状态下发送短信,解锁后检查最近结果。
- 若可以控制短信格式,发送带 app hash 的 SMS Retriever 测试短信。
- 若广播路径失败,打开前台 consent flow 再发送短信,观察是否弹出授权并读取正文。
## Open Questions
- 目标小米 12S 当前是否安装并启用了 Google Play services。
- 用户是否接受为了后台稳定性关闭 HyperOS 对该 app 的省电限制。
- 首版是否需要常驻通知显示最近验证码,还是只在 app 内展示。
- 是否需要支持验证码自动复制到剪贴板;这会带来额外隐私和系统提示问题,建议先不做。