fe6390337f
com.xiaomi.smarthome posts both generic status updates (title=米家, content=设备状态) and concrete events (e.g. 智能门锁开锁). Silence only the generic "设备状态" ones; door-lock/security alerts still forward. Filtered silently before the blacklist. CLAUDE.md synced. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9.4 KiB
9.4 KiB
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 调用前显式导出:
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),不要提交。
构建与安装
# 编译 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 签名,产出可直接安装):
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)
├─ 在线:BarkClient.push(msg) → OkHttp POST;失败入 pending → RetryWorker (15min 周期)
└─ 离线:紧要通知(验证码/来电) → SmsFallbackForwarder 走短信兜底
→ LogStore.addLog(...)(silent 的静默过滤不写日志)
旁路(不经通知监听器):
PhoneCallMonitor 轮询 getCallState() → 来电/未接构造 Decision 推送(绕过 MIUI 限制)
UpdateChecker 查 GitHub Releases → 新版本横幅 → 应用内下载 + 系统安装器
包结构 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;对 connection closed / 超时 / TLS 中断等瞬时 IOException 自动重试 3 次(退避 600/1200ms),HTTP 4xx/5xx 不重试。 |
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<Boolean>,供离线兜底判断。 |
core/CarrierDetector.kt |
SIM MCC+MNC 映射到运营商(移动/联通/电信/广电),供短信兜底发免费查询码。 |
core/CarrierBalanceQuery.kt |
向运营商服务号发查询短信、解析回执余量、余额≤5 自动挂起。 |
core/SmsFallbackForwarder.kt |
断网时把紧要通知(验证码/来电)用短信发到 iPhone,5 分钟限频 + 余额检查。 |
core/UpdateChecker.kt |
查 GitHub Releases 最新版(24h 节流)→ 语义比较 → 下载 APK → FileProvider 调起系统安装器。 |
store/SettingsStore.kt |
Bark 服务器列表、全局开关、广告关键词等。MutableStateFlow + DataStore。 |
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中):- 全局开关 → 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 = 9、versionName = "1.5.1"(位于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、截图与临时文件,不要提交它们。 - 发版流程:
- 递增
versionCode/versionName→./gradlew assembleRelease(产物app/build/outputs/apk/release/app-release.apk) git tag -a v{versionName} -m "..."→git push origin v{versionName}- 本机无
ghCLI:用 python(urllib) 调 GitHub API 创建 Release + 上传 APK。token 放仓库根gh-token.txt(已 gitignore),脚本里open('gh-token.txt')读取,勿把 token 写进命令参数。注意 Git Bash 的/tmp与 Windows python 路径不一致,脚本内一律用项目相对路径。
- 递增
- UI 现状:统一品牌科技感主题(固定品牌色 + 自定义 Typography + 共享组件层),深浅色双方案,关闭动态取色;新组件统一通过
ui/Components.kt复用,避免各页面各拼一套样式。