diff --git a/.gitignore b/.gitignore index 502b15c..0c316b2 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ test.json # Local assistant session helpers .claude/ do.ps1 +CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 9bd6b7c..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,124 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## 项目概述 - -**a2i** — 安卓通知转发到 iOS Bark。监听 Android 系统通知(短信、验证码、聊天等),通过 Bark Server 推送到 iOS 设备。iOS 端点击通知可跳转到对应 App(微信、QQ、Telegram)。 - -## 环境配置(本机) - -环境变量需要在每次 shell 调用前显式导出: - -```bash -export JAVA_HOME="D:/Dev/JDK-17" -export ANDROID_HOME="D:/Dev/Android/SDK" -``` - -- JDK 17: `D:\Dev\JDK-17`(项目使用 `sourceCompatibility = VERSION_17`) -- Android SDK: `D:\Dev\Android\SDK` -- Gradle 8.11.1(wrapper 内置) -- AGP 8.9.1 / Kotlin 2.1.0 - -`local.properties` 已在项目根(`sdk.dir=D:\Dev\Android\SDK`),**不要提交**。 - -## 构建与安装 - -```bash -# 编译 debug APK -export JAVA_HOME="D:/Dev/JDK-17" ANDROID_HOME="D:/Dev/Android/SDK" -./gradlew assembleDebug - -# 产物位置 -app/build/outputs/apk/debug/app-debug.apk - -# 安装到已连接的设备 -"D:/Dev/Android/SDK/platform-tools/adb.exe" install -r app/build/outputs/apk/debug/app-debug.apk -``` - -无 lint / 测试任务配置(项目未启用)。改完代码直接 `assembleDebug` 验证即可。 - -构建可分发的 release APK(已绑定 debug 签名,产出可直接安装): - -```bash -export JAVA_HOME="D:/Dev/JDK-17" ANDROID_HOME="D:/Dev/Android/SDK" -./gradlew assembleRelease -# 产物:app/build/outputs/apk/release/app-release.apk -adb install -r app/build/outputs/apk/release/app-release.apk -``` - -> `release` 复用 `debug` 签名配置(`signingConfigs.getByName("debug")`),适合个人发布到 GitHub Release;正式商店上架前需替换为独立的 release keystore。 - -## 架构 - -### 核心数据流 - -``` -NotifyListenerService (系统通知入口) - → NotificationProcessor.process(sbn) → Decision(message?, reason, code, silent) - ├─ 在线:BarkSender.pushToFirst(enabledServers, msg) 按序依次试,首个成功止;全失败入 pending → RetryWorker (15min 周期) - └─ 离线:紧要通知(验证码/来电) → SmsFallbackForwarder 走短信兜底 - → LogStore.addLog(...)(silent 的静默过滤不写日志) - -旁路(不经通知监听器): - PhoneCallMonitor 轮询 getCallState() → 来电/未接构造 Decision 推送(绕过 MIUI 限制) - UpdateChecker 查 Gitea Releases → 新版本横幅 → 应用内下载 + 系统安装器 - EmailSender 并联电邮转发(SMTP,fire-and-forget) -``` - -### 包结构 `com.a2i.forwarder` - -| 文件 | 职责 | -|---|---| -| `A2iApp.kt` | `Application` 单例;持有全局 `appScope`(`SupervisorJob+Dispatchers.Default`)与全局组件:三个 Store(`settings`/`appRules`/`logStore`)+ 监听类(`phoneCallMonitor`/`connectivityMonitor`/`carrierBalanceQuery`/`smsForwarder`/`updateChecker`)。全部经 `A2iApp.instance.xxx` 访问,`onCreate` 构造并 `start()`。UI 通过 `collectAsState()` 订阅 Store 的 `StateFlow`。 | -| `service/NotifyListenerService.kt` | `NotificationListenerService` 实现;每条通知调 `processor.process()`,过滤后推送。 | -| `core/NotificationProcessor.kt` | **核心决策点**。包含 LRU 去重缓存(5s)、脱敏检测(`*****` → 回退到 `tickerText`)、持续性通知 + VPN/代理客户端/设备互联等常驻通知(`silentPackages`)静默过滤、米家「设备状态」泛化通知静默(保留门锁告警)、系统状态消息过滤、广告过滤、验证码提取、特殊 App 解析。返回 `Decision(message?, reason, appLabel, code, silent)`。 | -| `core/CodeExtractor.kt` | 三段正则:关键词+数字、数字+关键词、纯数字 4-8 位。检测到全 `*****` 时直接返回 null。 | -| `core/AdFilter.kt` | 内置营销关键词 + 用户自定义关键词(`settings.adKeywords`)。 | -| `core/AppParsers.kt` | 微信/QQ/Telegram 的 `clickUrl` 映射 + 重要消息关键词(@我、验证码、通话等)。 | -| `core/BarkClient.kt` | OkHttp POST 到 `${server}/push`,超时 10/10/15s;body 用 `msg.copy(deviceKey=server.deviceKey)`;对 connection closed / 超时 / TLS 中断等瞬时 IOException 自动重试 3 次(退避 600/1200ms),HTTP 4xx/5xx 不重试。 | -| `core/BarkSender.kt` | 多服务器容错推送:按列表顺序依次 `BarkClient.push`,首个成功即止(`pushToFirst`);NotifyListenerService / PhoneCallMonitor / RetryWorker 共用。 | -| `core/EmailSender.kt` | SMTP 直连发送纯文本邮件(`send`)支持 465 SSL / 587 STARTTLS + AUTH LOGIN;`trySend` 便捷方法读取 SettingsStore 异步发(fire-and-forget)。 | -| `core/RetryWorker.kt` | WorkManager 周期任务,每 15 分钟重发 `pending` 队列中的失败消息。 | -| `core/LogStore.kt` | 最近 300 条日志 + 失败重发队列(≤100)。DataStore 持久化。 | -| `core/IconExporter.kt` | 一键导出所有已安装 App 图标到用户选择的 SAF 目录(用于部署到 iOS Bark 图床)。 | -| `core/PhoneCallMonitor.kt` | 轮询 `TelephonyManager.getCallState()`(800ms)感知来电/未接来电,构造 Decision 推送。绕过 MIUI 不向第三方监听器分发系统电话通知的限制。 | -| `core/ConnectivityMonitor.kt` | `ConnectivityManager` + `NetworkCallback`,暴露 `isOnline: StateFlow`,供离线兜底判断。 | -| `core/CarrierDetector.kt` | SIM MCC+MNC 映射到运营商(移动/联通/电信/广电),供短信兜底发免费查询码。 | -| `core/CarrierBalanceQuery.kt` | 向运营商服务号发查询短信、解析回执余量、余额≤5 自动挂起。 | -| `core/SmsFallbackForwarder.kt` | 断网时把紧要通知(验证码/来电)用短信发到 iPhone,5 分钟限频 + 余额检查。 | -| `core/UpdateChecker.kt` | 查 Gitea Releases 最新版(`UPDATE_SOURCE_HIDDEN`,国内可达,24h 节流)→ 语义比较 → 下载 APK → FileProvider 调起系统安装器。 | -| `store/SettingsStore.kt` | Bark 服务器列表(有序,`BarkServer.enabled` 勾选参与转发)、全局开关、广告关键词、图标前缀、电邮转发 SMTP 设置等。`MutableStateFlow` + DataStore。`moveServer` 调序、`setServerEnabled` 启停;转发按序依次试(见 `BarkSender`)。图标前缀默认 github raw,国内不稳时可手填自建 Gitea。 | -| `store/AppRulesStore.kt` | 黑/白名单模式 + 默认黑名单(系统 App + 输入法 + 三星剪贴板/键盘 + GMS + 录音机/相册/查找/密码管理 + 通话管理/AI通话);默认黑名单带版本号 `bl_version`,升级时把新增噪音包合并进老用户黑名单(只增不删)。 | -| `ui/nav/AppNav.kt` | 4 个底部 Tab + 日志页。 | -| `ui/theme/*` | 固定品牌配色(`Color.kt`)、自定义 `Typography`(`Type.kt`),`A2iTheme` 关闭动态取色、统一深浅色方案(`Theme.kt`)。 | -| `ui/screens/*.kt` | HomeScreen(品牌横幅 + 运行状态 + 统计 + 更新横幅 + 短信挂起提醒)、AppManageScreen、FilterScreen、SettingsScreen(Bark 服务 / 应用图标 / 断网短信兜底 / 关于与更新)、LogScreen、UpdateDialog(应用内更新弹窗)。 | -| `ui/Components.kt` | 共享 UI 组件层:`ScreenTitle`、`SectionCard`、`SwitchRow`、`ClickRow`、`IconBox`、`StatusBadge`、`MetricBlock`,以及 `appVersionName(context)`、`drawableToImageBitmap(d)`、`isNotificationListenerEnabled(context)` 工具函数。 | - -### 重要约定 - -- **Store 全局单例**:所有 Store 通过 `A2iApp.instance.xxx` 访问。**不要**在 Compose 中创建新的 Store 实例。 -- **协程作用域**:跨屏调用用 `app.appScope.launch { ... }`(Application 级别的 `SupervisorJob + Dispatchers.Default`)。 -- **过滤流水线**(顺序很重要,在 `NotificationProcessor.process` 中): - 1. 全局开关 → 2. ongoing 过滤(静默)→ 2b. VPN/代理常驻通知静默过滤(silentPackages)→ 2c. 米家「设备状态」静默 → 3. App 黑/白名单 → 4. 空内容/脱敏 → 5. 噪音包名 → 6. 系统状态消息 → 7. 近空内容 → 8. 广告过滤 → 9. 已发短信(仅收不发)→ 10. 运营商余额回执拦截 → **11. 去重检查(5s 窗口)** → 12. 验证码提取 → 13. 特殊 App 解析 → 14. 构造 BarkMessage -- **Bark API** 字段映射在 `BarkMessage.kt`(`@SerialName`):`device_key`/`autoCopy`/`isArchive` 用下划线/驼峰别名,其他用蛇形。 -- **版本号**:`app/build.gradle.kts` 的 `versionCode`/`versionName`,运行时通过 `appVersionName(context)` 读取,首页底部居中显示 `v{version}`。**改完代码同步递增 versionCode/versionName,并更新本文档「版本与发布」一节的当前版本。** - -### 通知监听授权 - -监听服务依赖 `Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS` 授权。`HomeScreen` 检测授权状态,未授权时显示跳转按钮。授权是**一次性**的,卸载后需重新授权。 - -### 调试日志 - -`NotifyListenerService.handle()` 每次都会写一条 `ForwardLog` 到 `LogStore`(无论转发/过滤/失败),可在「日志」页查看最近 300 条。失败会自动加入 `pending` 队列,由 `RetryWorker` 后续重发。 - -## 版本与发布 - -- 当前版本:`versionCode = 12`、`versionName = "1.6.2"`(位于 `app/build.gradle.kts`)。**每次改动后同步递增 versionCode/versionName,并同步更新本节。** -- 远端仓库:`https://github.com/lsxf/a2i.git`,默认分支 `main`。 -- 提交策略:`.gitignore` 已排除 `app/build`、`.gradle`、`.kotlin`、`local.properties`、`.claude/`、`do.ps1`、`gh-token.txt`(发版用 token)、`*.token`、截图与临时文件,**不要**提交它们。 -- 发版流程(github + gitea 双发;UpdateChecker 查 Gitea 源,故 Gitea 必发): - 1. 递增 `versionCode`/`versionName` → `./gradlew assembleRelease`(产物 `app/build/outputs/apk/release/app-release.apk`) - 2. `git tag -a v{versionName} -m "..."` → `git push origin v{versionName}`(origin 推 github + gitea) - 3. 本机无 `gh` CLI:用 python(urllib) 调 API 创建 Release + 上传 APK。token 分别放 `gh-token.txt`(GitHub)和 `gitea-token.txt`(Gitea,均 gitignore),脚本 `open(...)` 读取,**勿**把 token 写进命令参数。脚本内一律用项目相对路径(Git Bash `/tmp` 与 Windows python 不一致)。 -- UI 现状:统一品牌科技感主题(固定品牌色 + 自定义 Typography + 共享组件层),深浅色双方案,关闭动态取色;新组件统一通过 `ui/Components.kt` 复用,避免各页面各拼一套样式。