23 · BluetoothCallback 契约与接入指南

定位:21(架构)与 51 §4(回调↔广播三 flavor 总表)的操作手册——怎么接入、怎么扩展、哪里会踩坑。回调全表本文不重复,查 51。 基线:dev 分支 2026-09-06(对抗记录见 _对抗审查记录.md 第四轮)。

0. 一句话

BluetoothCallback 是 settingslib 事件总线到 UI 的观察者契约BluetoothEventManager 收到系统广播后遍历 mCallbacksonXxx,UI 类实现接口、按需覆写。

1. 设计语言(读 AOSP 的通用功)

  1. default 空实现(Java 8+):全部方法空体,实现类只覆写关心的——接口膨胀不惩罚实现者。方法数三 flavor:dcddif 10 / xcddif 15 / f3dif 11(对照表见 51 §4)。
  2. @IntDef 编译期约束(xcddif 版):@interface ConnectionState(:175)、@interface AdapterState(:184) 把 int 参数锁到常量集——枚举的安全性 + int 的零开销,AOSP 惯用法。dcddif 版无任何注解(裸 int);f3dif 版有注解但 Nullable 来自 androidx 而非 android.annotation。

2. 事件五跳(谁在调你的回调)

蓝牙协议栈 → 系统广播 → BluetoothEventManager 的 Handler 解析
  → 遍历 mCallbacks(:71,声明 Collection<BluetoothCallback>,实例化 CopyOnWriteArrayList)
  → callback.onXxx(...)   ← 你的代码

锚点(xcddif BluetoothEventManager.java):registerCallback :204(add :205)/ unregisterCallback :209(remove :210);dispatch 例:onDeviceAdded :265、onProfileConnectionStateChanged :278、onBluetoothStateChanged :447。

  • 注册/注销必须配对(CopyOnWriteArrayList 只增不减 → 泄漏)。注册时机:蓝牙主页 Fragment 的 onStart/onStop、各 Controller 的 onStartInternal/onStopInternal(具体行号见 51 §4,以现场 grep 为准)。
  • 所有回调在主线程触发,可直接刷 UI。

3. 实现者全景(2026-09-06 全仓核实,共 6 个)

⚠️ 纠正一个易错认知:实现者不止 BluetoothPreferenceController 一个。

实现者源集位置覆写要点
BluetoothPreferenceControllermain(单份)settingsPage/micarConnectionSettings/.../controller/BluetoothPreferenceController.java:508 个回调(§4)
BluetoothMainEntrySettingsFragmentmain(单份)settingsPage/micarConnectionSettings/.../view/BluetoothMainEntrySettingsFragment.java:56蓝牙主页 Fragment
BluetoothEventManagerExt.kt仅 f3dif.../settingslib/bluetooth/BluetoothEventManagerExt.kt:28object : 写法;onProfileConnectionStateChanged(A17 事件增强)
LocalBluetoothManagerExt.kt仅 f3dif.../settingslib/bluetooth/LocalBluetoothManagerExt.kt:28object : 写法;onAudioModeChanged
PresetUiController.kt仅 f3dif.../hearingdevices/ui/PresetUiController.kt:66助听设备预设 UI(字段持有匿名实现)
AmbientVolumeUiController.java仅 f3dif.../hearingdevices/ui/AmbientVolumeUiController.java:63助听设备环境音量(多接口 implements 列表中的一员)

找实现者的正确姿势:别只 grep implements BluetoothCallback——Kotlin object : BluetoothCallbackimplements A, B, BluetoothCallback 多接口列表、字段类型声明 BluetoothCallback cb = ... 都会漏。至少组合搜 implements BluetoothCallbackobject : BluetoothCallbackBluetoothCallback> 三种模式(第四轮对抗审查正是用单一模式漏掉了 5 个实现者)。

4. BluetoothPreferenceController 覆写清单(8/15,行号 2026-09-06)

onBluetoothStateChanged:137 / onScanningStateChanged:142 / onDeviceAdded:146 / onDeviceDeleted:150 / onDeviceBondStateChanged:154 / onConnectionStateChanged:158 / onActiveDeviceChanged:162 / onAudioModeChanged:166。均为一行委托刷新,模式统一,可作新 Controller 的模板。

5. 扩展三步(新接一个事件的标准路径)

  1. BluetoothEventManager 注册广播 + Handler 里 dispatch callback.onXxx(...)
  2. BluetoothCallbackdefault void onXxx(...) {}
  3. UI 实现类覆写 + 注册/注销配对。

flavor 同步矩阵:接口与 dispatch 在三 flavor 是独立拷贝,成对增删。反面教材即正面教材:f3dif 移除 5 个回调(A2dpCodec/CSIP 组×2/Auracast 广播×2)时,接口与 EventManager 分发一起删(分发零命中),同时新增 onAutoOnStateChanged(接口 :174、dispatch BluetoothEventManager.java:640)——接口有方法而无人 dispatch = 永不触发的死回调;有 dispatch 而接口无方法 = 编译不过。改一处漏 flavor = 编译不过或行为不一致;dcd(dcddif 10 法)最小可作最小集参照。

6. 陷阱清单

  1. 两套 STATE 数字体系BluetoothAdapter.STATE_CONNECTED(本接口 :19-26 静态导入,@ConnectionState 用这套)vs BluetoothProfile.STATE_CONNECTED(profile 层)。数字可能相同,语义来源不同——比较/存库时别混。
  2. bond ≠ connect:配对持久(重启还在)、连接临时。见 1061
  3. 三层连接态onConnectionStateChanged(整机有无连接的边界)/ onProfileConnectionStateChanged(某设备某 profile)/ onAclConnectionStateChanged(ACL 物理链路)。车机”连接成功”UI 通常看 profile 层。深讲见 61
  4. BONDED 广播被 SDP 延迟最多 3s20 §2)——onDeviceBondStateChanged 不是实时信号,判态用 getBondState()
  5. f3dif 特有onAutoOnStateChanged(:174) 为 A17 独有;Auracast/codec/CSIP 回调整体移除,事件走别的机制(Auracast QR 侧见 80 系列)。

时效声明

行号 2026-09-06 于 dev 分支核对(主控 + 红队交叉,见 _对抗审查记录.md 第四轮)。行号漂移是常态,引用前 grep -n 复核,必标 flavor。