12 · Profile 体系与车机角色

核心文件:ConnectionUtils.ktMiCarBluetoothDeviceDetailFragment、settingslib CachedBluetoothDevice.java/LocalBluetoothProfileManager.java/各 *Profile.java 适用范围:Profile 体系(HFP/A2DP/PBAP/MAP/HID/PAN/SAP/OPP/LeAudio/HearingAid/CarPlay) 三 flavor:settingslib 在 base/settingsLibAndroid/src/{dcddif,xcddif,f3dif}/java/com/android/settingslib/bluetooth/dcddif=骁龙8295 / xcddif=玄戒A14 / f3dif=玄戒A17主线(dev_a17_0610 编译 f3dif)。下表行号以 xcddif 为基准,差异处标注。 关联:03-设备详情21-settingslib架构

0. 一图看懂车机角色

措辞纠正(对抗审查):不是”车机永远是 Client/Sink/HF”,而是”默认配置下车机承担 Client/Sink/HF 角色”。实际能力由 BluetoothAdapter.getSupportedProfiles() 运行时返回的标志位决定,平台可通过该清单裁剪/扩展角色(例如车机对外接蓝牙耳机又可作 Source)。

flowchart LR
    subgraph Phone["📱 手机 (AG / Source / Server)"]
        PA2DP["A2DP Source 推音频"]
        PHFP["HFP AG 通话"]
        PPBAP["PBAP PSE 电话本服务端"]
    end
    subgraph Car["🚗 车机 (默认 HF / Sink / Client)"]
        CA2DP["A2dpSinkProfile 放音乐"]
        CHFP["HfpClientProfile 免提"]
        CPBAP["PbapClientProfile 读电话本"]
        CMAP["MapClientProfile 读短信"]
    end
    PA2DP -- "A2DP 音频流" --> CA2DP
    PHFP -- "SCO 通话+AT" --> CHFP
    PPBAP -- "PBAP OBEX" --> CPBAP
    PPBAP -. "MAP 短信 OBEX" .-> CMAP

1. 各 Profile 详解

isAutoConnectable 的真实位置(关键纠正):它是 LocalBluetoothProfile 接口方法(各 profile 子类实现),被框架层(BluetoothDevice.connect/connectAllEnabledProfiles 内部)用来判断”是否允许自动连该 profile”settingslib 不在 connect 链里主动调用它。下表”自动连接”列指”框架是否会自动连”,非 settingslib 显式过滤。

1.1 HFP — 车机 = HF

HfpClientProfile.java(xcddif 注释:38/class:40,dcddif 注释:39/class:41)注释明写 Handles the Handsfree HF roleSRC_UUIDS={HSP_AG,HFP_AG}(xcddif:48-51,dcddif:49-52)——意为”对端是 AG 时才连”。本仓用 HfpClientProfile,无独立开关,由框架统一拉起。同时存在的 HeadsetProfile(AG 角色)实例化 gate 在 LocalBluetoothProfileManager.java(xcddif:185-188/dcddif:159-162),通常因平台不上报 HEADSET 支持而不实例化(代码路径存在,非绝对)。

1.2 A2DP — 车机 = Sink

A2dpSinkProfile(Sink) 与 A2dpProfile(Source) 独立 gate(LBPM xcddif A2dpProfile:173-176 / A2dpSinkProfile:179-182),分别由 A2DP 和 A2DP_SINK 两个标志位独立判断,可同时实例化也可只一个——不是”二选一”。车机默认配置走 A2dpSinkProfile 放音乐,无独立开关

1.3 PBAP — 唯一有独立开关的 Profile

直读 BluetoothDeviceProfilesPreferenceController.java:字段 private PbapClientProfile mPbapClientProfile(:41) 钉死单一引用onCreateInternal(:49-56) 只取 getPbapClientProfile()不遍历 getProfiles()getPreferenceType()(:59-61) 锁死 MiCarConnectSwitchPreference(非 PreferenceGroup);XML 只一个槽位。updateState(:64-87) isShow = isPhoneDevice && !isTwoWayRadio。切换 handlePreferenceChanged:90-102 直接 mPbapClientProfile.setEnabled(device, newValue)唯一回滚是返回 false 时 framework 自动弹回 switch。

设计动机(推测):电话本同步涉隐私且非通话必需,给用户控股权;HFP/A2DP 是基础体验,统一拉起不让用户分心。

1.4 MAP / HID / PAN / SAP / OPP(isAutoConnectable 双 flavor 核实)

Profile车机角色isAutoConnectable行号 (xcddif / dcddif)自动连接
MapClientProfile读短信(Client)true:113 / :114
MapProfile读短信true:117 / :117
PanProfilePANUfalse ❗(常见误解为true):87 / :88(按需手动)
SapProfileSAP Clienttrue:112 / :113
HidProfileHost(键鼠/手柄)true:105 / :105是(手柄另走 PairingRequest 直通)
HidDeviceProfileHost Devicefalse:104 / :105
OppProfilefalse:39 / :39
PbapClientProfile读电话本(Client)true:117 / :118
PbapServerProfilePBAP Serverfalse:94 / :94否(车机一般不启用)

⚠️ PAN 硬错纠正:PAN isAutoConnectable=falsePanProfile.java:87-89 直读 return false),不自动连,需用户在详情页手动触发。早期草稿误记为 true,已纠正。

1.5 LeAudio / HearingAid / HapClient(BLE Audio 家族)— 仅 xcddif + f3dif

comm -13 实测:LeAudioProfile/HapClientProfile/CsipSetCoordinatorProfile/VolumeControlProfile/LocalBluetoothLeBroadcast*/HearingAidAudioRoutingHelperxcddif 与 f3dif 都有,dcddif 完全没有(dcddif 连 LeAudioProfile 都缺)。这是平台能力差异(骁龙8295 不支持 LE Audio/Auracast/助听)。f3dif 在此基础上多了 *Ext.kt Kotlin 扩展(A17 版本演进)。

车机用不用:LeAudio/助听是车机作为 Source 把音频推给 LE Audio 耳机的反向角色,当前 MiCar 主线(手机连车机)不使用;xcddif/f3dif 保留为海外高端/后排蓝牙耳机场景预留。待验证:是否真有车型在 BluetoothProfile.LE_AUDIO 进 supportedList。

2. Profile × 角色 × 开关 × 自动连接 总表

Profile车机角色settingslib 类独立开关框架自动连
HFPHF 免提HfpClientProfile
HFP-AGAG(备用)HeadsetProfile通常不实例化
A2DPSink 放音乐A2dpSinkProfile
A2DP-SourceSource(备用)A2dpProfile视 supportedList
PBAP-Client读电话本PbapClientProfile✅ 唯一开关
PBAP-ServerServer(备用)PbapServerProfile否(默认不启用)
MAP-Client读短信MapClientProfile
HIDHost(键鼠/手柄)HidProfile
PANPANUPanProfile(手动)
SAPSAP ClientSapProfile
OPPOppProfile
LeAudioLE Audio SourceLeAudioProfile(仅xcddif+f3dif)是(CSIP 组级联)
HearingAidHearing Aid SourceHearingAidProfile
HapClientHAP ClientHapClientProfile(仅xcddif+f3dif)
CarPlay私有UUID识别(无 Profile 类)❌(走WiFi)(见 §3)

3. CarPlay:蓝牙只是「握手跳板」,数据走 WiFi

ConnectionUtils.kt:30-31CARPLAY_128UUID = ParcelUuid.fromString("2d8d2466-e14d-451c-88bc-7301abea291a")isCarPlaySupportDevice(device)(:101-107) 用 device.getEirDataUuids() 在广播包 EIR UUID 列表精确匹配 128-bit——Apple 私有识别码,不公开注册于 SIG 16-bit UUID 空间,必须用 128-bit 字面值不走 SDP:EIR 在扫描阶段随广播带出,无需建链即可识别。

sequenceDiagram
    participant U as 👤用户
    participant B as 车机蓝牙栈
    participant I as iPhone
    participant App as 车机CarPlay App
    participant W as 车机WiFi
    U->>B: 触发扫描
    B->>I: Inquiry (EIR 含 CarPlay 128UUID)
    Note over B: isCarPlaySupportDevice=true
    B->>I: createBond 配对
    I-->>B: BOND_BONDED
    Note over B: MiBluetoothPairingRequest.kt:106<br/>disableAutoConnect 写黑名单<br/>蓝牙不自动连 HFP/A2DP
    B->>App: 拉起 CarPlay App
    App->>I: CarPlay 握手
    App->>W: 启动 WiFi P2P/STA
    W-->>I: WiFi 直连/同AP
    Note over App,I: 🎵 音视频/触控 全走 WiFi<br/>蓝牙仅维持控制通道

关键:配对成功后 MiBluetoothPairingRequest.kt:106ConnectionUtils.disableAutoConnect(context, device.address)Settings.Secure bluetooth_close_autoconnect(key 见 ConnectionUtils.kt:32),专门阻止 HFP/A2DP 自动连抢占通道——CarPlay 体验要走 WiFi。蓝牙完成”识别+配对+拉起 app”后退居二线。

4. 配对成功后的自动连接(双 flavor 链路)

重要:profile 连接不是纯系统行为,触发点在 App 侧 settingslib CachedBluetoothDevice.onBondingStateChanged→connect()。但”连哪些 profile”由框架决定(isAutoConnectable 元数据),settingslib 不显式过滤。

sequenceDiagram
    participant FW as 蓝牙框架
    participant CBD as CachedBluetoothDevice
    participant LBPM as LocalBluetoothProfileManager
    participant CTRL as 车机Controller
    FW->>CBD: ACTION_BOND_STATE_CHANGED(BOND_BONDED)
    Note over CBD: xcddif onBondingStateChanged:1050<br/>dcddif :839<br/>f3dif :1147(双参+prevBondState)
    alt 在 disableAutoConnect 黑名单
        CBD->>CBD: isCloseAutoConnectDevice=true 早退<br/>(xcddif:1079 / dcddif:853)
        Note over CBD: CarPlay 场景:HFP/A2DP 不自动连
    else isBondingInitiatedLocally=true(车机发起)
        CBD->>CBD: connect()
        alt xcddif/f3dif
            CBD->>CBD: connectDevice()<br/>mDevice.connect() 隐藏API<br/>(xcddif:544) 框架按priority连所有profile
        else dcddif
            CBD->>CBD: connectAllEnabledProfiles()<br/>(dcddif:387/402) 框架统一发起
        end
        FW-->>CBD: 各 profile STATE_CONNECTED
        CBD->>CTRL: dispatchAttributesChanged → UI 刷新
    else 对端发起(isBondingInitiatedLocally=false)
        Note over CBD: settingslib 不主动 connect
    end

isAutoConnectable 不在 connect 链:xcddif/f3dif 走 mDevice.connect()、dcddif 走 connectAllEnabledProfiles(),都把”连哪些 profile”交给框架;isAutoConnectable 是框架内部用的 profile 元数据,settingslib 不主动调用。详见 21-settingslib架构 §3.2。

5. 对抗自查

5.1「profile 连接由系统自动完成」——细化

错误表述。触发点是 App 侧 settingslibCachedBluetoothDevice.onBondingStateChanged(xcddif:1050/dcddif:839/f3dif:1147) 接收广播 → 在 connect 分支调 connect()(xcddif:1091) → connectDevice()(:521) → mDevice.connect()(:544 隐藏API,xcddif/f3dif) 或 connectAllEnabledProfiles()(dcddif:387)。框架只执行,由 isAutoConnectable() 决定哪些 profile 被拉起。且只有车机主动发起的配对(isBondingInitiatedLocally=true)才自动连。

5.2 disableAutoConnect 黑名单

定义 CachedBluetoothDevice.isCloseAutoConnectDevice(xcddif:1032-1046/dcddif:821-836,MICAR-PORTING 标记),读 Settings.Secure bluetooth_close_autoconnect单槽位——只存一个 MAC 字符串等值匹配)。写入 ConnectionUtils.disableAutoConnect(ConnectionUtils.kt:88-95),仅 MiBluetoothPairingRequest.kt:106 在”CarPlay 设备且车机支持 CarPlay”时调。清除 clearDisableAutoConnect(:76-86),在 BluetoothSwitchController 切开关时调。

🚨 f3dif(A17 主线)黑名单完全失效:f3dif CachedBluetoothDevice 没有 isCloseAutoConnectDevice 方法(grep 0 命中,MICAR-PORTING 标记全部回归为纯 AOSP)。onBondingStateChanged(:1147 双参) 直接 if(mDevice.isBondingInitiatedLocally()) connect()(:1192),不查黑名单。而 disableAutoConnect 在所有 flavor 都会写 Settings.Secure——即 A17 主线”写得到、没人读”,CarPlay 设备配对后仍会自动连 HFP/A2DP。详见 31-连接策略 假设 H6。

5.3「2956 为何没拦住 HFP/PBAP」——待验证,不给定论

先纠正前提:2956 是 Jira 号(配对取消仍成功),不是车型,描述的是”迟到广播”缺陷,不直接等于 HFP/PBAP 漏拦。若真有”HFP/PBAP 仍自动连”,排查方向(详见 31-连接与自动连接策略 §4):①写入时机竞态 ②Settings.Secure 单槽位覆盖 ③MAC 格式不匹配 ④onUuidChanged 旁路不查黑名单(证据最硬) ⑤clearDisableAutoConnect 误触发。当前不就此给定论。

6. 设计要点

  1. 角色由 getSupportedProfiles() 运行时决定:车机默认 HF/Sink/Client,手机 AG/Source/Server;反向角色类(A2dpProfile/HeadsetProfile/PbapServerProfile)由标志位独立 gate。
  2. UI 极简、Profile 自动化:仅 PBAP 有独立开关,其余自动连接交给框架 + 各 profile isAutoConnectable
  3. CarPlay 用私有 128UUID 在扫描期识别,刻意断开蓝牙 Profile 自动连接,把数据通路让给 WiFi
  4. 黑名单是 App 层补丁:framework 无”按设备抑制自动连接”公开 API,MiCar 用 Settings.Secure 单槽位 + settingslib patch 实现 CarPlay 隔离,存在脆弱点(见 31)。
  5. PAN 不自动连:按需手动触发,勿与 MAP/SAP 混。
  6. 三 flavor 行号差异:引用 settingslib 必标 flavor;f3dif(A17主线) onBondingStateChanged 双参,与 xcddif/dcddif 单参签名不同。

相关03-设备详情(PBAP 开关)、21-settingslib架构(四件套+connect链)、31-连接策略(自动连接+黑名单5假设)、33-CarPlay链路