33 · CarPlay 特殊链路
核心文件:
ConnectionUtils.kt(UUID/黑名单/能力判定)、MiBluetoothPairingRequest.kt(disableAutoConnect 写入点)、view/MiBluetoothPairingDialogActivity.kt(dismiss→拉起 CarPlay 弹窗)、view/MiCarplayConnectTypeChooseDialogActivity.kt+Fragment(蓝牙 vs CarPlay 二选一)、view/MiCarBluetoothDeviceDetailFragment.java(MisComplexSdk 监听分发) 适用范围:CarPlay 特殊链路(识别→配对→二次弹窗→跳板→WiFi 数据流→disableAutoConnect 黑名单) 关联:12-Profile §3 CarPlay跳板、02-配对流程 §6、31-连接策略 假设H6
1. CarPlay 设备识别:扫描期 EIR 私有 UUID
苹果 CarPlay 不在蓝牙 SIG 16-bit UUID 空间注册,用 128-bit 私有标识。MiCar 在扫描发现阶段直接从广播 EIR 包读出,不走 SDP(无需建链即可识别)。
- 常量:
ConnectionUtils.kt:30-31CARPLAY_128UUID = ParcelUuid.fromString("2d8d2466-e14d-451c-88bc-7301abea291a") - 设备级判定:
isCarPlaySupportDevice(device):101-107用device.getEirDataUuids()?.find { it == CARPLAY_128UUID } - 车机级 ROM 判定:
isCarPlaySupported():117-119← 字段mIsCarPlaySupportedRom(:41-47,by lazy查com.mi.car.carplay或com.desaysv.carplay两包名)
两层必须同时为真:iPhone 即使广播了 CarPlay UUID,车机 ROM 没预装 CarPlay app 也无法走该链路。
2. 配对入口:disableAutoConnect 提前写入黑名单
MiBluetoothPairingRequest.onReceive:50 处理 ACTION_PAIRING_REQUEST,分流前在 :103-107 专为 CarPlay 设备打”禁自动连”标记:
} else if (ConnectionUtils.isCarPlaySupported() && isCarplaySupportDevice) {
ConnectionUtils.disableAutoConnect(context, device.address)
}disableAutoConnect(ConnectionUtils.kt:87-97) 写 Settings.Secure bluetooth_close_autoconnect(key:32),单槽位。目的:阻止 settingslib 在 onBondingStateChanged→BOND_BONDED 时自动拉起 HFP/A2DP 抢占通道(CarPlay 数据要走 WiFi,见 §6)。
🚨 f3dif(A17 主线)黑名单完全失效:f3dif
CachedBluetoothDevice.java:1147onBondingStateChanged(bondState, prevBondState)在 BOND_BONDED 分支直接if(mDevice.isBondingInitiatedLocally()) connect();(:1192),无isCloseAutoConnectDevice早退(grep 0,MICAR-PORTING 全回归纯 AOSP)。对照 xcddif 同文件 :1079-1084 有早退。即 A17”写得到、没人读”——CarPlay 设备配对后仍自动连 HFP/A2DP。详见 §7 与 31 假设 H6。
3. 配对成功→dismiss→CarPlay 弹窗(150ms 延迟)
MiBluetoothPairingDialogActivity.mReceiver(:33-64) 在 ACTION_BOND_STATE_CHANGED→BOND_BONDED 时调 dismiss(oldState==BOND_BONDING),isPairSuccess=true(:52-54)。dismiss():190-221 的 CarPlay 拉起分支:
val isMisPair = MisComplexSdk.getInstance().isBluetoothBondingByMe(mPairingDevice.address)
if (isPairSuccess && isPairingCarplayDevice() && !isMisPair) {
BluetoothScanManager.setCanExecuteScan(false, "pairing_carplay_device")
window?.decorView?.postDelayed({ startActivity(chooseConnection) }, CARPLAY_CONNECT_DIALOG_START_DELAY_MS) // 150L :75
}isPairingCarplayDevice():186-188 = isCarPlaySupportDevice && isCarPlaySupported()。三条件:配对成功 + CarPlay 设备 + 非 MIS 自发起(!isBluetoothBondingByMe,避免互联 app 自己 createBond 时被劫持)。
150ms 设计意图(taskAffinity 顶替):三对话框 Activity 共用 taskAffinity="car.settings.bluetooth"+singleTask(manifest:43/56/69)→落在同一任务栈互相顶替。配对弹窗 dismiss 有 ~150ms 消失动画,若同步 startActivity 会两 Activity 短暂共存→动画卡顿/dim 层闪烁。150ms 等配对弹窗彻底结束再启 CarPlay 弹窗。startActivity 失败兜底 setCanExecuteScan(true,"dismiss_pair_fail")(:212)。
4. MiCarplayConnectTypeChooseDialog:两分支
Activity onCreate(:25-55) 核心:window.decorView.postDelayed({ BluetoothScanManager.setCanExecuteScan(true,"connect_type_choose_inflated") }, SCAN_RESUME_DELAY_MS)(:52-54, 200L)。
200ms 设计意图(扫描恢复):配对链路在 handlePairingIntent:130-134 与 dismiss:197 两次 setCanExecuteScan(false,"pairing_carplay_device") 阻断扫描。CarPlay 弹窗完全 inflate 后再延迟 200ms 恢复扫描——既给新弹窗动画/measure 留时间,又防扫描恢复过快触发列表抖动。
| 分支 | 触发 | 链路 |
|---|---|---|
选蓝牙 connection_bt(:95-119) | clearDisableAutoConnect(:97) 解黑名单 | 非手机类(!hasHfpOrA2dp)直连;手机类已连<2 直连;已连=2→displayBluetoothDevicePicker(:111) 冲替 |
选 CarPlay connection_carplay(:121-128) | clearDisableAutoConnect(:123)+setLastConnectTypeCarplay(:124, 写 Settings.Secure last_complex_connect_type=2) | requestConnectedCarplay(btMac):137-150 发 deeplink |
CarPlay deeplink URI(纠正笔误):实际拼出 mis://complex/connect?action=carplay_connect&bt_mac=AA:BB:CC:DD:EE:FF(CONNECTION_URI_PREFIX+CONNECTION_CARPLAY="carplay_connect",ConnectionConstants.kt:5-6,Fragment:140),由外部 com.mi.car.carplay/com.desaysv.carplay 捕获。注意 scheme 是 mis://、action 是 carplay_connect,非 connection://carplay。ActivityNotFoundException 只打日志(:147-149),无 fallback UI。
Activity onDestroy():64-68 兜底 clearDisableAutoConnect——用户直接关弹窗不选,黑名单也清掉防后续蓝牙操作被永久静默。
5. CarPlay 与双蓝牙上限交互
flowchart TD A["配对成功 dismiss:194"] --> B["拉起 ChooseDialog(150ms延迟)"] B --> C{"用户选择"} C -->|connection_bt :95| D["clearDisableAutoConnect:97"] C -->|connection_carplay :121| E["clearDisableAutoConnect:123+setLastConnectTypeCarplay+requestConnectedCarplay"] D --> F{"hasHfpOrA2dp? :99"} F -->|否 非手机| G["直接performConnect:100"] F -->|是 手机| H{"已连接数 vs LIMIT=2 :104"} H -->|空或<2| G H -->|>=2| I["displayBluetoothDevicePicker 冲替:111"] G --> J["dismissDialog+onDestroy clearDisableAutoConnect兜底:66"] I --> J E --> J
CarPlay 链路的双蓝牙上限拦截只在选蓝牙分支生效(:104-117),且仅手机类(hasHfpOrA2dp=true)计数。CarPlay 走 WiFi 不占蓝牙槽位——选 CarPlay 永不触发 Picker。
6. 蓝牙跳板→WiFi 数据流(核心:CarPlay 数据不走蓝牙)
sequenceDiagram participant U as 👤用户 participant B as 车机蓝牙栈 participant Settings as MiCarSettings participant CP as 车机CarPlay App participant W as 车机WiFi participant iPhone as 📱iPhone U->>B: 触发扫描 B->>iPhone: Inquiry(EIR含128UUID) Note over Settings: isCarPlaySupportDevice=true U->>Settings: 点击配对 Settings->>iPhone: createBond Settings->>Settings: disableAutoConnect 写黑名单(:106) iPhone-->>B: BOND_BONDED Settings->>Settings: dismiss 延迟150ms启CarPlay弹窗 U->>Settings: 选connection_carplay(:121) Settings->>Settings: clearDisableAutoConnect(:123)+setLastConnectTypeCarplay Settings->>CP: startActivity mis://complex/connect?action=carplay_connect&bt_mac=...(:140) CP->>iPhone: CarPlay握手(经蓝牙control channel) CP->>W: 启动WiFi P2P/STA W-->>iPhone: WiFi直连或同AP Note over CP,iPhone: 🎵音视频/触控全走WiFi<br/>蓝牙仅低带宽控制通道
关键事实(纠正误解):所谓”CarPlay 走蓝牙”是错误表述。蓝牙只承担:① 扫描期识别(EIR UUID)、② 配对建立链路信任、③ CarPlay 协议握手初期低带宽控制通道。音视频/触控数据全走 WiFi(本链路为无线 CarPlay:蓝牙握手→WiFi;有线 USB CarPlay 数据走 USB,不在此讨论)。disableAutoConnect 黑名单的设计目的就是让 HFP/A2DP 不抢占蓝牙通道,把数据通路让给 WiFi。
7. disableAutoConnect 写入/清除时序
sequenceDiagram participant Req as MiBluetoothPairingRequest participant CU as ConnectionUtils participant SS as Settings.Secure bluetooth_close_autoconnect participant CBD as CachedBluetoothDevice participant Cho as ChooseDialog participant Sw as SwitchController Note over SS: 单槽位 只存一个MAC Req->>CU: disableAutoConnect(:106) CU->>SS: putString(mac)(:89-91) Note over CBD: xcddif onBondingStateChanged:1079 早退不connect<br/>🚨f3dif:1192 无早退直接connect 黑名单失效 alt 选蓝牙 connection_bt Cho->>CU: clearDisableAutoConnect(Fragment:97) Cho->>CBD: performConnect 手动连HFP/A2DP else 选CarPlay Cho->>CU: clearDisableAutoConnect(:123) Note over Cho: 不连HFP/A2DP 交CarPlay app经WiFi else 用户关弹窗 Cho->>CU: Activity.onDestroy clearDisableAutoConnect(:66) else 切蓝牙开关 Sw->>CU: clearDisableAutoConnect(:186) end
清除五路:①选bt(Fragment:97) ②选CarPlay(Fragment:123) ③ChooseDialog onDestroy兜底(Activity:66) ④切开关(SwitchController:186) ⑤蓝牙列表点未配对设备发起新配对(UnbondedDevicesPrefController:517,先清黑名单再 pair)。写入仅一路:MiBluetoothPairingRequest.kt:106。
f3dif 真实风险:A17 写得到、读不到。iPhone 配对后选”蓝牙”前,HFP/A2DP 可能已被 settingslib 自动拉起(黑名单早退不存在)——
clearDisableAutoConnect已来不及。用户体验:刚配完的 iPhone 立刻被抓去做 HFP Sink,CarPlay 弹窗”选 CarPlay”分支变多余。A17 相对 xcddif/dcddif 的回归,见 31 H6。
8. MisComplexSdk 在详情页的监听
MisComplexSdk 是外部 aar(com.xiaomi.phonecarlink:complex:0.1.40,app/micarConnectionSettings build.gradle 均依赖),对 IMisComplex AIDL service 的封装,实际连接握手/设备发现都在外部 app 进程(com.mi.car.carplay 等)。Settings 仅作 listener/caller。
详情页装配(MiCarBluetoothDeviceDetailFragment.java):
parseComplexListeners():59-66(onCreate:76 调):遍历mPreferenceControllers,缓存所有MisComplexSdk.IComplexConnectListener进mComplexListenersonViewCreated:97-131:new 总 listener,四方法(onServiceDisconnected/onBinderDied/onComplexConnected/onComplexDisconnect)都view.post{ mComplexListeners.forEach{it.xxx()} }fan-out 到子 controller;:131registComplexConnectListeneronDestroyView:136:unRegistComplexConnectListener
设计原因:CarPlay/CarLink/AndroidAuto/Mirror 任一互联连上/断开都影响”对蓝牙设备操作是否需二次确认”(03 §MIS联动:confirmConnectWhenMisExist 等)。统一订阅一次再 fan-out,避免每 controller 各自订阅(aar listener 列表可能有限、跨进程回调有成本)。view.post 切 UI 线程(IPC 回调在 binder 线程)。aar 内部实现待验证。
9. 设计要点与坑
- 识别在扫描期、不走 SDP:128UUID 在 EIR 随广播带出,
getEirDataUuids()精确匹配,无需建链。 - 黑名单是 App 层补丁、非 framework API:framework 无”按设备抑制自动连接”API,MiCar 用
Settings.Secure单槽位 + settingslib patch(MICAR-PORTING) 实现。脆弱:单槽位多设备覆盖、依赖 settingslib patch(f3dif 已回归)。 - 150ms/200ms 双延迟:150ms 等 dismiss 动画避 taskAffinity 顶替闪烁;200ms 等 CarPlay 弹窗 inflate 再恢复扫描避列表抖动。
- MIS 自发起配对不被劫持:
!isBluetoothBondingByMe(PairingDialogActivity:193)。 - 黑名单清除五路兜底(选bt/选CarPlay/ChooseDialog onDestroy/切开关/点未配对设备发起配对):任一路径都能清,最坏切开关/重启恢复。
- 上限拦截只针对手机类 + 只在选 bt 分支:选 CarPlay 永不触发 Picker。
- CarPlay app 双实现:小米自研
com.mi.car.carplay∪ 德赛西威com.desaysv.carplay,任一预装即支持(ConnectionUtils.kt:41-47)。两者能力差异待验证。 - deeplink URI:
mis://complex/connect?action=carplay_connect&bt_mac=...(非 connection://)。 - f3dif 回归跟踪:A17 黑名单失效是 31 H6,合入 settingslib patch 前重点关注 iPhone 配对后 HFP/A2DP 抢占。