31 · 连接与自动连接策略
核心文件:settingslib
CachedBluetoothDevice.java(三 flavor)、BluetoothBondedDevicesPrefController.java、ConnectionUtils.kt、MiBluetoothPairingRequest.kt、BluetoothDeviceService.kt、MiBluetoothExt.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元数据决定。
| 步骤 | dcddif | xcddif | f3dif(A17主线) |
|---|---|---|---|
onBondingStateChanged | :839(单参) | :1050(单参) | :1147(双参) |
黑名单早退 isCloseAutoConnectDevice | :853 | :1079 | 不存在 |
→ connect()(public) | :360 | :429 | :503 |
mConnectAttempted 赋值 | :365 | :434 | onAclStateChanged:1227 |
→ connectDevice()/connectAllEnabledProfiles | :387(adapter:402) | :521(mDevice:544) | :596(mDevice:611) |
- xcddif/f3dif:
mDevice.connect()(BluetoothDevice 隐藏 API),框架按 priority 一次连所有 profile。 - dcddif:
mActiveAdapter.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 都写。 - 读取/拦截(
isCloseAutoConnectDevicexcddif: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 | 证据/验证 |
|---|---|---|---|
| H6 | f3dif 完全无黑名单,disableAutoConnect 静默失效 | f3dif(A17) | grep 0 命中;onBondingStateChanged:1192 直 connect。A17 最硬 |
| H2 | Settings.Secure 单槽位覆盖(多 CarPlay 设备互相覆盖) | 全 | BLUETOOTH_CLOSE_AUTO_CONNECT 单 string(ConnectionUtils.kt:32) |
| H1 | 写入时机竞态(putString 异步 vs onBondingStateChanged) | dcddif/xcddif | logcat disableAutoConnect ret vs get close auto connect device '' 时序 |
| H3 | MAC 格式不匹配(大小写) | dcddif/xcddif | dumpsys settings vs logcat 比对 |
| H5 | clearDisableAutoConnect 误触发(开关蓝牙/主动pair/CarPlay选蓝牙清空) | 全 | grep clearDisableAutoConnect 调用点 |
| H4 | onUuidChanged 旁路不查黑名单 | 仅 dcddif/xcddif | onUuidChanged: triggering connectDevice logcat |
优先级:H6 > H2 > H1 > H3 > H5 > H4(仅历史 flavor)。A17 主线直接查 H6;A14(dcddif/xcddif) 才需查 H4 旁路。
4.4 ⚠️ 2956 配对取消仍成功——当前未修复
对抗审查+主控二次核实(2026-07-23):修复 commit
7239803be在dev_a17_0610为悬空提交(git merge-base --is-ancestor返回非祖先、git fsckdangling),已被 reset,未合入,bug 仍在。实测onCancel(:283) 仍是旧版(仅cancelBondProcess),DialogActivity无mDismissed。详见 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. 设计要点与坑
- 配对≠连接:createBond 建 BOND_BONDED(密钥),
mDevice.connect()才拉 profile。 - 黑名单是单点且 A17 失效:
bluetooth_close_autoconnect单 string;f3dif 完全无读取逻辑(H6)。 - onUuidChanged 不查黑名单(H4,仅 dcddif/xcddif)。
- 回连非 App 职责:
recordLastConnectedDevice只写 Global 给音乐 App 读。 - last device 仅 phone:严卡
isPhoneDevice()。 - BluetoothDeviceService 防误改:仅断==当前 last 才更新。
- 2956 未修复:onCancel 仍旧版,修复待合入。
- 三 flavor 行号差异大:改 settingslib 必须三栏对照(见 21 §8)。