66 · bond 状态与自动回连 — 状态机、耐心窗口与黑名单

源码基线 f3dif。交叉引用:11-配对与SSP安全机制(配对协议细节)、21-settingslib架构与事件流 §自动连接31-连接与自动连接策略68 §8 2846 本篇讲清:配对状态机的三个分支各自做什么、自动回连的时间窗怎么算、以及 A17 上黑名单为何失效。

0. 状态机鸟瞰

                createBond()                     配对流程出错/用户移除
   ┌─────────┐ ─────────────→ ┌─────────────┐ ─────────────→ ┌────────────┐
   │BOND_NONE│                │BOND_BONDING │                │ 回到 NONE   │
   │ (无关系) │ ←───────────── │ (握手中)     │                └────────────┘
   └─────────┘   握手失败      └──────┬──────┘
        ↑                             │ 握手成功(拿到 Link Key)
        │  removeBond()/unpair()      ↓
        │                       ┌───────────┐
        └────────────────────── │BOND_BONDED│ (关系存续,无需 ACL 在线)
                                └───────────┘

广播入口ACTION_BOND_STATE_CHANGED(BEM:127 注册)→ BondStateChangedHandler(BEM:407-509)→ cachedDevice.onBondingStateChanged(bondState, prevBondState)(:450 → CBD:1147-1210)。注意 f3dif 的这个方法带 prevBondState 参数(老版本不带——三 flavor 签名对照见 21 篇 §8)。

1. BondStateChangedHandler:进 CBD 前的六道关卡(BEM:407-509)

按执行顺序,每一道都是真实 bug 的现场:

#代码作用关联案例
1:409-412device null 防御
2:416 mDeviceManager.onBondStateChangedIfProcess(CBDM:567)CSIP 组成员的 bond 变化不单独刷 UI(组内代管)
3:421-432小米补丁 MANNPROB-2291:BOND_NONE 且 EXTRA_PAIRING_CONTEXT==PAIRING_CONTEXT_REPAIRING 直接 return——autonomous repair 的中间态 BOND_NONE 不当失败显示(常量是手抄的平台 @hide 值,:59-66)2414 同机制
4:434-439findDevice 为 null → addDevice 补建档案(诞生入口之一,63 §2.13492
5:441-443BONDED 时按 identity address 去重(LE 隐私地址场景)
6:445-447 → :450回调 onDeviceBondStateChanged(BluetoothCallback)→ cachedDevice.onBondingStateChanged(...)

尾部 :466-470 → showUnbondMessage(:480-508):BOND_NONE 且非诊断模式时按 EXTRA_UNBOND_REASON 映射失败 toast。

2. onBondingStateChanged 三个分支逐行(CBD:1147-1210)

2.1 BOND_NONE(:1148-1183)— 大扫除

synchronized (mProfileLock) { mProfiles.clear(); }   // :1149-1151 ⚠️ mRemovedProfiles 不清!
// MIUI MOD: BT_MIUIBluetoothFrame(:1152-1182,后台线程):
//   phonebook/message/SIM 三权限重置 ACCESS_UNKNOWN(:1159 同时 mBondTimestamp = null)
//   prevBondState==BOND_BONDING 且诊断flag → mBondFailureTimeMillis 记时(:1163)
//   → 调度 CAN_NOT_PAIR_TIME_OUT_MILLS 延迟 refresh(:1170-1175)

三个教学点:

  • mProfiles.clear()mRemovedProfiles 不清——残留的 removed 列表是日志噪音源,也是”明明解绑了还有 profile 痕迹”的困惑来源。
  • 三权限重置在后台线程做(权限重置含 IO,不占主线程)——f3dif 无 MICAR-PORTING 标记但有 MIUI MOD 标记(小米改动换了标记体系)。
  • mBondFailureTimeMillis 只在 BONDING→NONE(握手失败)时记录,供”配对失败”延迟提示节流。

2.2 BOND_BONDING — 无操作

状态机里没有独立分支(不进任何 if)——握手期间一切交给 framework。

2.3 BOND_BONDED(:1189-1205)— 记账 + 自动连接

mBondTimestamp = new Timestamp(...);            // :1190 配对成功时刻
if (isBondingInitiatedLocally()) {              // :1192 ★只有"本端发起"的配对
    connect();                                  // :1194 才自动连接
}
HearingDeviceStatsLogUtils.addToJustBonded(...); // :1198 助听指标
mBondFailureTimeMillis = -1;                     // :1200-1204 复位(诊断flag下)

isBondingInitiatedLocally() 是自动连接的第一道闸门:本端用户点”配对”→ 成功即连(用户体验闭环);对端发起的配对(比如从手机侧发起)成功后自动连——尊重本端用户的选择权。

⚠️ f3dif 与 dcddif/xcddif 的关键差异:老 flavor 在 :1192-1194 这里还有第二道闸门——isCloseAutoConnectDevice() 黑名单(DC:852-858 / XC:1078-1084,读 Settings.Secure bluetooth_close_autoconnect,CarPlay 配对后写入以禁止自动连 HFP/A2DP)。f3dif 没有这个方法也没有这个判断——黑名单写入仍在(disableAutoConnect 所有 flavor 都写),但 f3dif 没人读,A17 上 CarPlay 黑名单失效(知识库金标准”假设 H6”,详见 31 §4)。

3. 自动回连的第二机制:UUID 时间窗(ACL 记账 + 耐心窗口)

§2.3 的自动连接只覆盖”配对刚成功”。另一场景是:已配对设备回连时,ACL 先通、UUID(SDP 结果)后到——连接发起时 mProfiles 可能还是空的(64 §2.2 直接放弃)。谁来回连?答案是一条”账本 + 窗口”机制:

3.1 记账:onAclStateChanged(CBD:1212-1262)

入口:ACTION_ACL_CONNECTED/DISCONNECTED(BEM:149-150 注册,AclStateChangedHandler :566-613)
       读 EXTRA_TRANSPORT(默认 BREDR,:608-609)
写入(CBD.onAclStateChanged):
  首个 ACL 连接(BR/EDR 与 LE 都未连时):
      mConnectAttempted = SystemClock.elapsedRealtime()   // :1226-1240 记账★
  LE/BREDR 双通道标志分别更新                            // :1243-1247
  双通道都断开:mConnectAttempted = -1                    // :1249-1261 销账

⚠️ 字段注释与实现不符62 §6 详述):注释说”上次自动连接尝试时间”,实际记的是 ACL 链路建立时刻。半断(一条传输还连着)不清账;换壳时账本随迁(:2580)。dcddif 版还漏了 -1 守卫(判语句 DC:811)——f3dif 补上了。

3.2 消费:onUuidChanged(CBD:1109-1145)

入口:ACTION_UUID → UuidChangedHandler(BEM:520-526);PbapClientProfile.java:86 也直调
步骤:
  1. updateProfiles()(:1056)—— 用新 UUID 重建 mProfiles(LBPM:634 干活)
  2. 选窗口(if-else 链,:1113-1120):
       含 HOGP       → 60_000   // :102
       含 HEARING_AID → 45_000   // :101(注释:第二只耳做服务发现更慢)
       含 LE_AUDIO    → 60_000   // :103
       默认           → 35_000   // :99
  3. 决策(:1138-1142):
       if (mConnectAttempted != -1
            && mConnectAttempted + timeout > SystemClock.elapsedRealtime()) {
           connectDevice();      // ★窗口内 UUID 到达 → 补发连接
       }
  4. dispatchAttributesChanged()(:1144)

时间线图解

时刻0        时刻0+2s            时刻0+?              窗口边界(35s默认)
 │ ACL连上     │发起连接:            │UUID终于到达          │
 ▼            ▼mProfiles空,放弃     ▼                     ▼
 ●────────────●─────────────────────●━━━━━━━━━━━━━━━━━━━╳
 mConnectAttempted                 落在窗口内?→connectDevice()
 = now(:1227)                       落在窗口外?→什么都不做

窗口选择顺序敏感(HOGP > HEARING_AID > LE_AUDIO):同时含助听器与 LE Audio UUID 的设备拿 45s 而非 60s——if-else 链重排即改行为,改这段代码前先想清楚。

金标准背书:f3dif 21 处(三 flavor 全仓 60 处)isAutoConnectable 命中全是定义与覆写、零调用点——“自动连接”的实际机制就是本节的 isBondingInitiatedLocally + 时间窗两套,与接口上的语义标记无关(21 篇 已核实)。

4. unpair:取消配对的三件事(CBD:656-690)

public void unpair() {
    mUnpairing = true;                    // :666 单向旗(永不复位,[62 §9])
    // BOND_BONDING → cancelBondProcess();BONDED → removeBond()
    // (两者选择逻辑见 [11 篇 §cancel 语义](../10-基础概念篇/11-配对与SSP安全机制.md)——取消握手 ≠ 删除关系)
    LeAudioSharing → removeBroadcastSource(组成员集合)   // 调用点 :667-676(方法定义 :691-708,@WorkerThread 删广播源)
    releaseLruCache();                    // :679 图标缓存清空
}

UI 侧入口:详情页 BluetoothOperationsController Unpair → MIS 确认/DIALOG_DEVICE_UNPAIRperformUnpair(MiBluetoothExt.kt:55-62,:57unpair())。unpair 后 CBD 不立即出表——残留到下次 clearNonBondedDevices63 §5.368 §4 3492 的温床)。

5. 广播时序的两条金标准(排障必背)

  1. BONDED 广播被 SDP 延迟(最多 ~3s)ACTION_BOND_STATE_CHANGED(BONDED) 要等 SDP Discovery 结构完成才发出(68 §8 2846 实测 enq 在 SDP 完成后 4ms)。判断当前 bond 状态以 getBondState() 为权威(读服务侧实时态),别信”广播还没到 = 还没配对上”。
  2. 配对前 CoD 可能未知:出局式(手动发起)配对时,PAIRING_REQUEST 到达时刻对端设备类(CoD)还没学到——把”未知(0x1F00)“当”不支持”过滤掉是 2846 偶现的直接原因。

6. 自测

  • BOND_NONE 分支清了哪些、没清哪些?(清 mProfiles;不清 mRemovedProfiles)
  • 自动连接的两道闸门?(isBondingInitiatedLocally;f3dif 上第二道黑名单闸门缺失
  • mConnectAttempted 记的是什么时刻?什么条件下销账?(首个 ACL 建立;双传输全断才 -1)
  • 窗口选择的优先级顺序?为什么助听器反而比 LE Audio 短?(HOGP>HA>LE_AUDIO;AOSP 原序如此——HA 45s 是”第二只耳服务发现慢”的补偿,LE Audio 60s 是另一档)
  • isAutoConnectable 参与自动连接决策吗?(不参与,零调用点)
  • 判断 bond 状态用什么方法最权威?为什么?(getBondState()——BONDED 广播被 SDP 延迟最多 3s)

下一篇 67-active设备与多设备:多台设备并存时的”当前输出”、CSIP 组与电量。