8.8 KiB
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: 验证码解析采用多阶段规则
解析规则必须保守,避免把手机号、金额、日期误识别为验证码。
建议顺序:
- 优先匹配包含关键词的模式:
验证码、校验码、动态码、code、verification、OTP附近的 4-8 位数字或字母数字。 - 次级匹配短信中独立出现的 4-8 位数字,排除明显日期、手机号片段、金额和订单号。
- 对带空格或短横线的验证码做归一化,例如
12 34 56、123-456。 - 若多个候选值并存,选择距离关键词最近、长度在 4-6 位优先、出现位置更靠前的候选。
- 解析失败时保留失败原因,不展示完整正文。
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
本项目当前没有既有代码,迁移计划等同于实施顺序:
- 完成本次 OpenSpec 评审。
- 参考 Weather 项目创建或复制最小 Android 构建骨架。
- 实现权限和诊断 UI。
- 实现系统短信广播主路径。
- 实现验证码解析器和单元测试。
- 在小米 12S 上跑真机验证。
- 根据真机结果决定是否补
SMS User Consent API或后台稳定性处理。
Rollback 策略:
- 如果系统广播路径被目标设备限制,保留解析器和 UI,降级为
SMS User Consent API前台读取验证。 - 如果 Google API 不可用,不影响系统广播主路径。
Validation Strategy
上下文验证:
- 确认 Weather 项目可作为构建环境参考。
- 确认
SmsReceiveOpenSpec 通过 CLI validate。 - 确认 spec 任务列表不包含重装 Android Studio、Gradle、JDK。
代码验证:
- 验证码解析器单元测试覆盖中文、英文、空格、短横线、多候选、无验证码样本。
- receiver 解析逻辑可通过构造 Intent/PDU 或抽象 message input 测试核心逻辑。
- 权限状态和诊断状态可通过 ViewModel/unit test 验证。
真机验证:
- 前台打开应用后,向目标号码发送测试短信:
【测试】验证码 123456,5 分钟内有效。 - 应用退到后台后,重复发送不同验证码。
- 锁屏状态下发送短信,解锁后检查最近结果。
- 若可以控制短信格式,发送带 app hash 的 SMS Retriever 测试短信。
- 若广播路径失败,打开前台 consent flow 再发送短信,观察是否弹出授权并读取正文。
Open Questions
- 目标小米 12S 当前是否安装并启用了 Google Play services。
- 用户是否接受为了后台稳定性关闭 HyperOS 对该 app 的省电限制。
- 首版是否需要常驻通知显示最近验证码,还是只在 app 内展示。
- 是否需要支持验证码自动复制到剪贴板;这会带来额外隐私和系统提示问题,建议先不做。