[update] init

This commit is contained in:
邹超
2026-05-18 11:10:52 +08:00
commit 790afd679e
54 changed files with 5015 additions and 0 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-16
@@ -0,0 +1,156 @@
## 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 验证。
真机验证:
- 前台打开应用后,向目标号码发送测试短信:`【测试】验证码 123456,5 分钟内有效。`
- 应用退到后台后,重复发送不同验证码。
- 锁屏状态下发送短信,解锁后检查最近结果。
- 若可以控制短信格式,发送带 app hash 的 SMS Retriever 测试短信。
- 若广播路径失败,打开前台 consent flow 再发送短信,观察是否弹出授权并读取正文。
## Open Questions
- 目标小米 12S 当前是否安装并启用了 Google Play services。
- 用户是否接受为了后台稳定性关闭 HyperOS 对该 app 的省电限制。
- 首版是否需要常驻通知显示最近验证码,还是只在 app 内展示。
- 是否需要支持验证码自动复制到剪贴板;这会带来额外隐私和系统提示问题,建议先不做。
@@ -0,0 +1,53 @@
## Why
你想做的不是完整短信客户端,而是一个只服务自己手机的验证码接收工具:在小米 12S、澎湃 OS 3、Android 15 上尽可能可靠地读取新收到短信里的验证码,并把关键结果快速展示出来。
这类需求的关键不在 UI,而在 Android 15 与厂商系统对短信权限、广播分发、后台限制和验证码短信格式的真实行为。因此必须先把可用 API、兜底路径和真机验证方案写清楚,再进入编码。
## What Changes
- 新建一个 Android App 规格方案,目标能力限定为“接收短信验证码、解析验证码、展示最近结果、输出诊断状态”。
- 明确三条可用 SMS 获取路径,并按优先级落地:
- `Telephony.Sms.Intents.SMS_RECEIVED_ACTION` + `RECEIVE_SMS`:主路径,适合个人自用、非 Play 上架场景,直接读取收到短信内容。
- `SMS User Consent API`:备选路径,不要求短信带 app hash,但需要用户对单条短信授权,适合验证系统广播被限制时的可行性。
- `SMS Retriever API`:受控格式路径,不需要短信权限,但要求验证码短信包含当前 app 的 hash,更适合服务端可控验证码,不适合作为读取任意平台验证码的主路径。
- 明确不把 Android Studio、Gradle、JDK 环境初始化纳入本次工作,后续实现时参考已有 `Weather` 项目的构建环境。
- 设计验证码解析策略,支持常见 4-8 位数字码、中文验证码文案、带空格/短横线的验证码,以及短信多段 PDU 合并后的正文。
- 设计真机验证路径,重点验证 Android 15 + HyperOS 3 上:
- 运行时申请 `RECEIVE_SMS` 是否成功。
- 应用在前台/后台时 `SMS_RECEIVED_ACTION` 是否触发。
- 短信正文是否能被解析为完整 message body。
- 常见短信验证码是否能稳定提取。
- 建立诊断能力,暴露权限状态、API 路径状态、最近一次广播时间、最近一次解析结果和失败原因。
## Capabilities
### New Capabilities
- `sms-code-capture`: 定义应用通过系统短信广播、Google SMS 验证 API 备选路径接收短信正文并抽取验证码的行为要求。
- `sms-permission-diagnostics`: 定义应用对短信权限、接收状态、API 可用性和解析失败原因的诊断展示要求。
- `sms-code-validation-workflow`: 定义在小米 12S、澎湃 OS 3、Android 15 真机上的验证流程和测试通过标准。
### Modified Capabilities
- 无。当前 `SmsReceive` 目录没有既有 OpenSpec 能力规格。
## Impact
- 预期后续会创建一个最小 Android 工程或复用 Weather 工程环境生成同类 Android 工程配置。
- 预期会影响 Android Manifest、运行时权限申请、BroadcastReceiver、短信 PDU 解析、验证码正则解析、前台诊断 UI 和测试用例。
- 需要引入或使用的主要 Android/Google API
- `android.provider.Telephony.Sms.Intents.SMS_RECEIVED_ACTION`
- `android.permission.RECEIVE_SMS`
- `Telephony.Sms.Intents.getMessagesFromIntent(Intent)`
- `com.google.android.gms.auth.api.phone.SmsRetriever`
- `SMS User Consent API`
- 不以 Play Store 上架合规为目标,因此可以使用短信权限;但实现仍必须本地化处理短信内容,不上传、不持久化完整短信正文,减少隐私风险。
## Validation
- OpenSpec 文档结构完整,至少包含 `proposal.md``design.md``tasks.md` 和三个 capability spec。
- API 方案必须明确主路径、备选路径、适用条件和限制,不写成泛泛而谈的短信读取方案。
- 设计必须说明 Android 15、HyperOS 3、个人自用 sideload/debug 场景下的权限与后台行为风险。
- 后续实现前必须能从任务列表直接进入编码,不再需要重新讨论核心架构。
- 后续实现完成后,必须在目标真机上完成至少一轮短信接收、解析和诊断验证。
@@ -0,0 +1,57 @@
## ADDED Requirements
### Requirement: Receive new SMS messages through the system broadcast path
The app SHALL use Android's new SMS received broadcast path as the primary mechanism for capturing verification SMS messages on the user's own device.
#### Scenario: SMS broadcast is received with permission granted
- **WHEN** the app has `RECEIVE_SMS` permission and the device receives a text SMS
- **THEN** the app MUST process `Telephony.Sms.Intents.SMS_RECEIVED_ACTION` and extract message objects from the received intent
#### Scenario: SMS broadcast is unavailable because permission is missing
- **WHEN** the app does not have `RECEIVE_SMS` permission
- **THEN** the app MUST report that the primary SMS capture path is blocked by missing permission
### Requirement: Parse complete SMS message bodies
The app SHALL parse SMS bodies using Android SMS message APIs rather than ad hoc PDU handling in business logic.
#### Scenario: Multi-part SMS is received
- **WHEN** the received intent contains multiple SMS message segments
- **THEN** the app MUST combine the message bodies in received order before verification code extraction
#### Scenario: Sender and timestamp are available
- **WHEN** Android exposes sender address or timestamp for the SMS message
- **THEN** the app MUST attach those values to the capture result for diagnostics and display
### Requirement: Extract verification code candidates
The app SHALL extract verification code candidates from SMS bodies with a conservative parser optimized for common Chinese and English verification messages.
#### Scenario: Chinese verification keyword is present
- **WHEN** the SMS body contains a keyword such as `验证码``校验码` or `动态码` near a 4-8 character code
- **THEN** the app MUST extract the nearby code as the preferred verification code candidate
#### Scenario: English verification keyword is present
- **WHEN** the SMS body contains a keyword such as `code`, `verification` or `OTP` near a 4-8 character code
- **THEN** the app MUST extract the nearby code as the preferred verification code candidate
#### Scenario: Code contains spaces or hyphens
- **WHEN** the SMS body contains a verification code formatted with spaces or hyphens
- **THEN** the app MUST normalize the code before displaying it
#### Scenario: No reliable code candidate exists
- **WHEN** the SMS body does not contain a reliable verification code candidate
- **THEN** the app MUST return a structured parse failure instead of displaying a guessed code
### Requirement: Support optional Google SMS verification APIs
The app SHALL support Google SMS verification APIs only as optional paths and MUST NOT depend on them for the primary capture behavior.
#### Scenario: SMS User Consent path is available
- **WHEN** Google Play services supports SMS User Consent and the user authorizes reading a single SMS
- **THEN** the app MUST parse that SMS body through the same verification code parser used by the primary path
#### Scenario: SMS Retriever path is used with app hash
- **WHEN** a controlled test SMS includes the app hash required by SMS Retriever
- **THEN** the app MUST accept the retrieved message and parse the verification code
#### Scenario: Google SMS API is unavailable
- **WHEN** Google Play services is missing, disabled, incompatible, times out, or the user declines consent
- **THEN** the app MUST keep the system SMS broadcast path usable and report the optional path failure separately
@@ -0,0 +1,45 @@
## ADDED Requirements
### Requirement: Validate on the target Xiaomi Android 15 device
The app SHALL be validated on the user's Xiaomi 12S running HyperOS 3 and Android 15 before the SMS capture behavior is considered complete.
#### Scenario: Foreground validation
- **WHEN** the app is open in the foreground and a test SMS containing `验证码 123456` is received
- **THEN** the app MUST show `123456` as the latest parsed verification code
#### Scenario: Background validation
- **WHEN** the app has been moved to the background and a test SMS containing a new verification code is received
- **THEN** the app MUST either show the new code after returning to the app or report that background delivery was blocked
#### Scenario: Lock screen validation
- **WHEN** the device is locked and a test SMS is received
- **THEN** the app MUST show the received result after unlock or report that delivery was blocked under lock screen conditions
### Requirement: Validate parser behavior with representative samples
The verification parser SHALL be validated with representative verification SMS examples and negative examples.
#### Scenario: Common valid samples
- **WHEN** parser tests include Chinese verification codes, English OTP messages, space-separated codes, hyphen-separated codes, and multiple candidates
- **THEN** all expected verification codes MUST be extracted with the correct normalized value
#### Scenario: Negative samples
- **WHEN** parser tests include messages with phone numbers, dates, money amounts, tracking numbers, or no verification code
- **THEN** the parser MUST avoid returning a false verification code unless a stronger keyword-nearby rule applies
### Requirement: Validate optional API assumptions separately
The app SHALL validate Google SMS APIs independently from the primary system broadcast path.
#### Scenario: Google Play services is available
- **WHEN** Google Play services is installed and supports SMS User Consent or SMS Retriever
- **THEN** the app MUST run an explicit optional-path test and record the result separately from system broadcast validation
#### Scenario: Google Play services is unavailable
- **WHEN** Google Play services is unavailable or incompatible on the target phone
- **THEN** the app MUST mark Google SMS API validation as skipped or unavailable without failing the primary SMS broadcast validation
### Requirement: Validate implementation without rebuilding development environment
The app SHALL reuse the existing local Android build environment reference and MUST NOT require reinstalling Android Studio, Gradle, or JDK as part of the implementation validation.
#### Scenario: Build configuration is prepared
- **WHEN** implementation begins after spec approval
- **THEN** the project MUST align its build setup with the existing Weather project environment or document any minimal project-specific difference
@@ -0,0 +1,45 @@
## ADDED Requirements
### Requirement: Display SMS permission state
The app SHALL display whether the SMS receive permission is granted, denied, or blocked by system settings.
#### Scenario: Permission is granted
- **WHEN** `RECEIVE_SMS` permission is granted
- **THEN** the app MUST show that the primary SMS capture path can be attempted
#### Scenario: Permission is denied
- **WHEN** `RECEIVE_SMS` permission is denied
- **THEN** the app MUST show that incoming SMS cannot be captured through the primary path until permission is granted
### Requirement: Explain capture path status
The app SHALL expose diagnostic state for each supported SMS capture path.
#### Scenario: Primary path receives an SMS
- **WHEN** the system broadcast path receives and parses an SMS
- **THEN** the app MUST show the latest receive time, source path, sender summary, parsed code, and parse strategy
#### Scenario: Primary path fails before parsing
- **WHEN** the app cannot receive or parse an SMS through the primary path
- **THEN** the app MUST show a specific reason such as missing permission, no broadcast received, empty body, or parser failure
#### Scenario: Optional Google API path fails
- **WHEN** SMS User Consent or SMS Retriever cannot complete
- **THEN** the app MUST show whether the failure came from unavailable Google Play services, timeout, user cancellation, or unmatched SMS format
### Requirement: Avoid unnecessary SMS content retention
The app SHALL minimize retention and logging of full SMS content.
#### Scenario: Verification code is parsed successfully
- **WHEN** the app extracts a verification code from an SMS
- **THEN** the app MUST display or retain the code, sender summary, timestamp, and parse metadata without requiring persistent storage of the full SMS body
#### Scenario: Debug body visibility is enabled
- **WHEN** a debug-only setting enables full body visibility
- **THEN** the app MUST keep that behavior local to the device and clearly separate it from normal display state
### Requirement: Provide recovery actions for permission problems
The app SHALL provide a clear recovery path when Android or HyperOS blocks SMS capture permissions.
#### Scenario: Permission cannot be granted in normal prompt
- **WHEN** the runtime permission prompt does not grant usable SMS access
- **THEN** the app MUST provide an action to open the system application details or permission settings page
@@ -0,0 +1,71 @@
## 1. Spec And Project Baseline
- [x] 1.1 评审本次 OpenSpec 的 proposal、design、specs、tasks 是否覆盖需求
- [x] 1.2 用 `npx openspec validate build-sms-code-receiver-app` 校验方案结构
- [x] 1.3 检查 Weather 项目的 Gradle、AGP、Kotlin/Java、compileSdk 配置,作为后续实现参考
- [x] 1.4 确认本次不处理 Android Studio、Gradle、JDK 重新安装
- [ ] 1.5 后续提交或 push 前检查 diff,避免引入非预期 EOF newline 变化
## 2. Android Skeleton
- [x] 2.1 基于 Weather 项目环境建立最小 Android app 工程骨架
- [x] 2.2 设置包名、minSdk、targetSdk、compileSdk,并保持与现有可用环境兼容
- [x] 2.3 创建单 Activity 工具型界面,用于权限申请、状态展示和最近验证码展示
- [x] 2.4 建立基础日志标签和 debug 开关
## 3. Permission And Diagnostics
- [x] 3.1 在 Manifest 声明 `android.permission.RECEIVE_SMS`
- [x] 3.2 实现 Android 运行时短信权限申请和权限状态刷新
- [x] 3.3 在 UI 展示权限状态、receiver 状态、最近接收时间和失败原因
- [x] 3.4 增加跳转应用详情页的入口,用于处理 HyperOS 权限或受限权限设置
- [x] 3.5 检测 Google Play services 可用性,为 SMS User Consent / Retriever 备选路径提供诊断
## 4. System SMS Broadcast Path
- [x] 4.1 实现 `SMS_RECEIVED_ACTION` BroadcastReceiver
- [x] 4.2 使用 `Telephony.Sms.Intents.getMessagesFromIntent(Intent)` 解析短信消息
- [x] 4.3 处理多段短信 body 合并、sender、timestamp 和 subscription id
- [x] 4.4 将 receiver 结果分发到应用状态层,避免在 receiver 内执行重任务
- [x] 4.5 在无权限、body 为空、解析失败时输出结构化失败原因
## 5. Verification Code Parser
- [x] 5.1 实现关键词邻近匹配,支持验证码、校验码、动态码、code、verification、OTP
- [x] 5.2 实现 4-8 位数字或字母数字候选提取
- [x] 5.3 支持空格和短横线归一化,例如 `12 34 56``123-456`
- [x] 5.4 增加误判排除规则,降低手机号、日期、金额、订单号误识别概率
- [x] 5.5 为多候选短信输出命中策略和置信度
## 6. Optional Google SMS APIs
- [x] 6.1 评估目标设备 Google Play services 是否可用
- [ ] 6.2 预留或实现 SMS User Consent API 前台读取单条短信路径
- [ ] 6.3 预留或实现 SMS Retriever API 获取 app hash 与受控短信模板验证
- [ ] 6.4 在 UI 中区分系统广播、User Consent、Retriever 三种来源
- [ ] 6.5 为 Google API 超时、不可用、用户拒绝授权输出明确诊断
## 7. Tests
- [x] 7.1 为验证码解析器添加中文验证码样本测试
- [x] 7.2 为验证码解析器添加英文 OTP/code 样本测试
- [x] 7.3 为验证码解析器添加空格、短横线、多候选样本测试
- [x] 7.4 为验证码解析器添加无验证码、手机号、日期、金额误判样本测试
- [ ] 7.5 为短信接收状态层添加权限状态和失败原因测试
## 8. Xiaomi 12S / HyperOS 3 Device Validation
- [ ] 8.1 前台打开应用时发送测试短信并验证接收结果
- [ ] 8.2 应用退到后台时发送测试短信并验证接收结果
- [ ] 8.3 锁屏状态发送测试短信,解锁后验证最近结果
- [ ] 8.4 验证权限被拒绝、权限重新授予后的状态恢复
- [ ] 8.5 如后台接收失败,记录 HyperOS 省电、后台运行、受限权限相关设置
- [ ] 8.6 如 Google Play services 可用,验证 SMS User Consent API 前台授权读取
## 9. Delivery Criteria
- [x] 9.1 相关上下文逻辑分析通过
- [x] 9.2 OpenSpec validate 通过
- [x] 9.3 单元测试通过
- [ ] 9.4 目标真机至少完成一条验证码短信接收和解析
- [x] 9.5 诊断 UI 能解释无权限、未收到广播、未解析到验证码三类失败