31 · 连接与自动连接策略

核心文件:settingslib CachedBluetoothDevice.java(三 flavor)、BluetoothBondedDevicesPrefController.javaConnectionUtils.ktMiBluetoothPairingRequest.ktBluetoothDeviceService.ktMiBluetoothExt.kt 适用范围:连接与回连策略(配对后 profile 自动连接、用户连接/断开按钮、回连 last device、CarPlay 黑名单与 2956 排查、双蓝牙互斥) 三 flavor:dcddif/xcddif/f3dif(A17 主线=f3dif) 关联:02-配对流程03-设备详情21-settingslib

本文专注”配对≠连接”中的”连接”侧:链路建立之后到 profile 连上、断开、回连。

1. 配对成功后谁触发 profile 连接(三 flavor connect 链)

触发点在 App 侧 settingslib,非纯系统行为。但”连哪些 profile”由框架按 isAutoConnectable 元数据决定。

步骤dcddifxcddiff3dif(A17主线)
onBondingStateChanged:839(单参):1050(单参):1147(双参)
黑名单早退 isCloseAutoConnectDevice:853:1079不存在
connect()(public):360:429:503
mConnectAttempted 赋值:365:434onAclStateChanged:1227
connectDevice()/connectAllEnabledProfiles:387(adapter:402):521(mDevice:544):596(mDevice:611)
  • xcddif/f3difmDevice.connect()(BluetoothDevice 隐藏 API),框架按 priority 一次连所有 profile。
  • dcddifmActiveAdapter.connectAllEnabledProfiles(mDevice),框架统一发起。
  • mProfiles.isEmpty() 时先退,等 ACTION_UUID

onUuidChanged 旁路(三 flavor 一致:都不查黑名单)

onUuidChanged()(xcddif:998/dcddif:788/f3dif:1109) 在 mConnectAttempted + timeout 窗口内直接 connectDevice()(xcddif:1025)/connectAllEnabledProfiles()(dcddif:813)/connectDevice()(f3dif:1141),全程无黑名单检查。timeout:xcddif/dcddif 翻倍(X+X);f3dif 不翻倍但常量大(MAX_UUID 35000 vs 5000)。

自动连接时序

sequenceDiagram
    participant FW as 蓝牙框架
    participant CDC as CachedBluetoothDevice
    participant Dev as BluetoothDevice.connect()
    FW->>CDC: ACTION_BOND_STATE_CHANGED(BOND_BONDED)
    Note over CDC: dcddif/xcddif: 查 isCloseAutoConnectDevice<br/>f3dif: 无黑名单,跳过
    alt dcddif/xcddif 命中黑名单(CarPlay)
        CDC->>CDC: 早退 return
    else isBondingInitiatedLocally(车机发起)
        CDC->>CDC: connect()→connectDevice()/AllEnabled
        alt mProfiles 空
            FW->>CDC: ACTION_UUID→onUuidChanged 旁路(不查黑名单)
        else
            CDC->>Dev: mDevice.connect()/connectAllEnabledProfiles
            Dev->>FW: 连 HFP/A2DP/PBAP
        end
    else 对端发起
        Note over CDC: settingslib 不主动 connect
    end

2. 用户”连接/断开”按钮路径

宿主 BluetoothBondedDevicesPrefController,子 Preference MiCarBluetoothBondedDevicePreference

  • 点击/右文字handlePreferClick(:167-179):未连接 connectMis(:170)→connectDevice(:172,:246-301);已连接 disconnectMis(:176)→showDisConnectConfirmDialog(:177,:221-244)
  • 右箭头→详情页

断开二次确认(:221-244) 走 AlertDialog.MiCarBuilder 内联(非已废的 BluetoothDisconnectConfirmDialogFragment)。确认后 performDisConnect(MiBluetoothExt.kt:32-48):disconnect() + 发 DEVICE_DISCONNECT 广播 + updateLastConnectedDevice

3. 回连策略:last device 给谁用

ConnectionUtils.recordLastConnectedDevice(:143-167) 写 Settings.Global,key=last_active_bluetooth_device(ConnectionConstants.kt:34,注意是 ConnectionConstants 非 ConnectionUtils),仅 phone 设备(isPhoneDevice() 卡控 :149),值变化才写盘。

BluetoothDeviceService.kt(IntentService :15) 由 updateLastConnectedBtDevice(:459-483) 拉起:断开后从已连 phone 设备挑一个顶上 last 槽位;仅当被断设备==当前 last 才更新(:466-472),防副设备断开误改主 last。

回连主体不是 App:自动回连由系统蓝牙栈(PhonePolicy)负责,Settings App 不主动发起回连——所有”主动 connect”都是用户点按钮(§2)或配对成功(§1)。recordLastConnectedDevice 只记账给蓝牙音乐 App 读(决定播哪台),与系统回连无直接耦合。

4. CarPlay disableAutoConnect 与 2956 排查(开放问题)

4.1 已知机制(仅 dcddif/xcddif 完整)

  • 写入(MiBluetoothPairingRequest.kt:106):识别为 CarPlay 设备且车机支持时,调 ConnectionUtils.disableAutoConnect(ConnectionUtils.kt:88-95) 写 Settings.Secure bluetooth_close_autoconnect(key :32)=MAC(单槽位)。所有 flavor 都写
  • 读取/拦截(isCloseAutoConnectDevice xcddif:1032-1046/dcddif:821-836):Settings.Secure.getStringForUser + getAddress().equals(devicesStr)仅 dcddif/xcddif 读

4.2 🚨 假设 H6(A17 主线最硬,对抗审查+主控核实)

f3dif(A17 主线)完全缺失 isCloseAutoConnectDevice(grep 0 命中,MICAR-PORTING 标记全回归纯 AOSP)。onBondingStateChanged(:1147) 直接 connect()(:1192)。即 disableAutoConnect 在 A17”写得到、没人读”,CarPlay 设备配对后仍自动连 HFP/A2DP,黑名单策略 100% 失效。A17 上排查”CarPlay 配对后蓝牙抢占”根本不需要看旁路,正门就不存在

4.3 六个排查假设(优先级排序)

#假设适用 flavor证据/验证
H6f3dif 完全无黑名单,disableAutoConnect 静默失效f3dif(A17)grep 0 命中;onBondingStateChanged:1192 直 connect。A17 最硬
H2Settings.Secure 单槽位覆盖(多 CarPlay 设备互相覆盖)BLUETOOTH_CLOSE_AUTO_CONNECT 单 string(ConnectionUtils.kt:32)
H1写入时机竞态(putString 异步 vs onBondingStateChanged)dcddif/xcddiflogcat disableAutoConnect ret vs get close auto connect device '' 时序
H3MAC 格式不匹配(大小写)dcddif/xcddifdumpsys settings vs logcat 比对
H5clearDisableAutoConnect 误触发(开关蓝牙/主动pair/CarPlay选蓝牙清空)grep clearDisableAutoConnect 调用点
H4onUuidChanged 旁路不查黑名单仅 dcddif/xcddifonUuidChanged: triggering connectDevice logcat

优先级:H6 > H2 > H1 > H3 > H5 > H4(仅历史 flavor)。A17 主线直接查 H6;A14(dcddif/xcddif) 才需查 H4 旁路。

4.4 ⚠️ 2956 配对取消仍成功——当前未修复

对抗审查+主控二次核实(2026-07-23):修复 commit 7239803bedev_a17_0610悬空提交git merge-base --is-ancestor 返回非祖先、git fsck dangling),已被 reset,未合入,bug 仍在。实测 onCancel(:283) 仍是旧版(仅 cancelBondProcess),DialogActivitymDismissed。详见 11-配对与SSP §6。

5. 双蓝牙互斥与抢占

DOUBLE_BLUETOOTH_DEVICE_LIMIT=2(ConnectionConstants.kt:19),口径=已连 HFP/A2DP 数。connectDevice(:246-301) 四分支:①!hasHfpOrA2dp非手机直连(:258) ②size<LIMIT直连(:273) ③!isSupportedPickerDevice直连(:282) ④否则弹 Picker(:294)。Picker 冲替三入口详见 32

stateDiagram-v2
    direction LR
    [*] --> Empty
    Empty --> One : 连A
    One --> Two : 连B(size2)
    Two --> Two_Full : 再连C
    state Two_Full {
    [*] --> Picker : displayBluetoothDevicePicker
        Picker --> 冲替 : 选被替换者→断旧→延时500ms连新
        }
    Two_Full --> Two : 冲替完成
    Two --> One : 断其一
    note right of Two: 上限=2 仅计 hasHfpOrA2dp

6. 设计要点与坑

  1. 配对≠连接:createBond 建 BOND_BONDED(密钥),mDevice.connect() 才拉 profile。
  2. 黑名单是单点且 A17 失效bluetooth_close_autoconnect 单 string;f3dif 完全无读取逻辑(H6)。
  3. onUuidChanged 不查黑名单(H4,仅 dcddif/xcddif)。
  4. 回连非 App 职责recordLastConnectedDevice 只写 Global 给音乐 App 读。
  5. last device 仅 phone:严卡 isPhoneDevice()
  6. BluetoothDeviceService 防误改:仅断==当前 last 才更新。
  7. 2956 未修复:onCancel 仍旧版,修复待合入。
  8. 三 flavor 行号差异大:改 settingslib 必须三栏对照(见 21 §8)。