- 删除 VerificationCodeParser 及相关测试,短信捕获和推送不再解析验证码 - 飞书推送改为只发送短信原文,时间戳格式化为可读日期 - 移除主界面"只推送验证码"开关和"调试时上传完整短信正文"选项 - 移除"保存轮询间隔"按钮,开启轮询时自动保存间隔(未输入默认1秒) - 按钮文字从"开始1秒轮询验证码"改为"开始短信轮询" - 删除"打印最近30条短信"功能及相关 SmsInboxReader.logRecentMessages - SmsInboxReader 用 RecentSmsResult 替换 RecentCodeResult - FeishuWebhookConfigStore.Config 移除 filterVerificationCode/sendFullBodyDebug - 修复代码缩进不一致问题
155 lines
8.5 KiB
Markdown
155 lines
8.5 KiB
Markdown
## 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. 优先保证 PDU 合并后的短信正文完整。
|
||
2. 记录 sender、timestamp、source 和 body 摘要。
|
||
3. 读取失败时保留失败原因,例如无权限、未收到广播、正文为空。
|
||
4. 默认展示最近一条短信原文,不额外推断正文中的业务字段。
|
||
|
||
### 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 验证。
|
||
|
||
真机验证:
|
||
|
||
- 前台打开应用后,向目标号码发送测试短信:`【测试】这是一条短信原文样本。`
|
||
- 应用退到后台后,重复发送不同短信正文。
|
||
- 锁屏状态下发送短信,解锁后检查最近结果。
|
||
- 若可以控制短信格式,发送带 app hash 的 SMS Retriever 测试短信。
|
||
- 若广播路径失败,打开前台 consent flow 再发送短信,观察是否弹出授权并读取正文。
|
||
|
||
## Open Questions
|
||
|
||
- 目标小米 12S 当前是否安装并启用了 Google Play services。
|
||
- 用户是否接受为了后台稳定性关闭 HyperOS 对该 app 的省电限制。
|
||
- 首版是否需要常驻通知显示最近短信结果,还是只在 app 内展示。
|
||
- 是否需要支持短信原文快捷复制;这会带来额外隐私和系统提示问题,建议先不做。
|