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-配对流程 §631-连接策略 假设H6

1. CarPlay 设备识别:扫描期 EIR 私有 UUID

苹果 CarPlay 不在蓝牙 SIG 16-bit UUID 空间注册,用 128-bit 私有标识。MiCar 在扫描发现阶段直接从广播 EIR 包读出,不走 SDP(无需建链即可识别)。

  • 常量:ConnectionUtils.kt:30-31 CARPLAY_128UUID = ParcelUuid.fromString("2d8d2466-e14d-451c-88bc-7301abea291a")
  • 设备级判定:isCarPlaySupportDevice(device):101-107device.getEirDataUuids()?.find { it == CARPLAY_128UUID }
  • 车机级 ROM 判定:isCarPlaySupported():117-119 ← 字段 mIsCarPlaySupportedRom(:41-47,by lazycom.mi.car.carplaycom.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:1147 onBondingStateChanged(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-134dismiss: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:FFCONNECTION_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://carplayActivityNotFoundException 只打日志(: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外部 aarcom.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.IComplexConnectListenermComplexListeners
  • onViewCreated:97-131:new 总 listener,四方法(onServiceDisconnected/onBinderDied/onComplexConnected/onComplexDisconnect)都 view.post{ mComplexListeners.forEach{it.xxx()} } fan-out 到子 controller;:131 registComplexConnectListener
  • onDestroyView:136unRegistComplexConnectListener

设计原因:CarPlay/CarLink/AndroidAuto/Mirror 任一互联连上/断开都影响”对蓝牙设备操作是否需二次确认”(03 §MIS联动:confirmConnectWhenMisExist 等)。统一订阅一次再 fan-out,避免每 controller 各自订阅(aar listener 列表可能有限、跨进程回调有成本)。view.post 切 UI 线程(IPC 回调在 binder 线程)。aar 内部实现待验证

9. 设计要点与坑

  1. 识别在扫描期、不走 SDP:128UUID 在 EIR 随广播带出,getEirDataUuids() 精确匹配,无需建链。
  2. 黑名单是 App 层补丁、非 framework API:framework 无”按设备抑制自动连接”API,MiCar 用 Settings.Secure 单槽位 + settingslib patch(MICAR-PORTING) 实现。脆弱:单槽位多设备覆盖、依赖 settingslib patch(f3dif 已回归)。
  3. 150ms/200ms 双延迟:150ms 等 dismiss 动画避 taskAffinity 顶替闪烁;200ms 等 CarPlay 弹窗 inflate 再恢复扫描避列表抖动。
  4. MIS 自发起配对不被劫持!isBluetoothBondingByMe(PairingDialogActivity:193)。
  5. 黑名单清除五路兜底(选bt/选CarPlay/ChooseDialog onDestroy/切开关/点未配对设备发起配对):任一路径都能清,最坏切开关/重启恢复。
  6. 上限拦截只针对手机类 + 只在选 bt 分支:选 CarPlay 永不触发 Picker。
  7. CarPlay app 双实现:小米自研 com.mi.car.carplay ∪ 德赛西威 com.desaysv.carplay,任一预装即支持(ConnectionUtils.kt:41-47)。两者能力差异待验证
  8. deeplink URImis://complex/connect?action=carplay_connect&bt_mac=...(非 connection://)。
  9. f3dif 回归跟踪:A17 黑名单失效是 31 H6,合入 settingslib patch 前重点关注 iPhone 配对后 HFP/A2DP 抢占。