12 · Profile 体系与车机角色
核心文件:
ConnectionUtils.kt、MiCarBluetoothDeviceDetailFragment、settingslibCachedBluetoothDevice.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 role,SRC_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 | 是 |
| PanProfile | PANU | false ❗(常见误解为true) | :87 / :88 | 否(按需手动) |
| SapProfile | SAP Client | true | :112 / :113 | 是 |
| HidProfile | Host(键鼠/手柄) | true | :105 / :105 | 是(手柄另走 PairingRequest 直通) |
| HidDeviceProfile | Host Device | false | :104 / :105 | 否 |
| OppProfile | — | false | :39 / :39 | 否 |
| PbapClientProfile | 读电话本(Client) | true | :117 / :118 | 是 |
| PbapServerProfile | PBAP Server | false | :94 / :94 | 否(车机一般不启用) |
⚠️ PAN 硬错纠正:PAN
isAutoConnectable=false(PanProfile.java:87-89直读return false),不自动连,需用户在详情页手动触发。早期草稿误记为 true,已纠正。
1.5 LeAudio / HearingAid / HapClient(BLE Audio 家族)— 仅 xcddif + f3dif
comm -13 实测:LeAudioProfile/HapClientProfile/CsipSetCoordinatorProfile/VolumeControlProfile/LocalBluetoothLeBroadcast*/HearingAidAudioRoutingHelper 在 xcddif 与 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 类 | 独立开关 | 框架自动连 |
|---|---|---|---|---|
| HFP | HF 免提 | HfpClientProfile | ❌ | 是 |
| HFP-AG | AG(备用) | HeadsetProfile | ❌ | 通常不实例化 |
| A2DP | Sink 放音乐 | A2dpSinkProfile | ❌ | 是 |
| A2DP-Source | Source(备用) | A2dpProfile | ❌ | 视 supportedList |
| PBAP-Client | 读电话本 | PbapClientProfile | ✅ 唯一开关 | 是 |
| PBAP-Server | Server(备用) | PbapServerProfile | ❌ | 否(默认不启用) |
| MAP-Client | 读短信 | MapClientProfile | ❌ | 是 |
| HID | Host(键鼠/手柄) | HidProfile | ❌ | 是 |
| PAN | PANU | PanProfile | ❌ | 否(手动) |
| SAP | SAP Client | SapProfile | ❌ | 是 |
| OPP | — | OppProfile | ❌ | 否 |
| LeAudio | LE Audio Source | LeAudioProfile(仅xcddif+f3dif) | ❌ | 是(CSIP 组级联) |
| HearingAid | Hearing Aid Source | HearingAidProfile | ❌ | 是 |
| HapClient | HAP Client | HapClientProfile(仅xcddif+f3dif) | ❌ | 是 |
| CarPlay | 私有UUID识别 | (无 Profile 类) | ❌(走WiFi) | 否(见 §3) |
3. CarPlay:蓝牙只是「握手跳板」,数据走 WiFi
ConnectionUtils.kt:30-31:CARPLAY_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:106 调 ConnectionUtils.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 侧 settingslib:CachedBluetoothDevice.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. 设计要点
- 角色由
getSupportedProfiles()运行时决定:车机默认 HF/Sink/Client,手机 AG/Source/Server;反向角色类(A2dpProfile/HeadsetProfile/PbapServerProfile)由标志位独立 gate。 - UI 极简、Profile 自动化:仅 PBAP 有独立开关,其余自动连接交给框架 + 各 profile
isAutoConnectable。 - CarPlay 用私有 128UUID 在扫描期识别,刻意断开蓝牙 Profile 自动连接,把数据通路让给 WiFi。
- 黑名单是 App 层补丁:framework 无”按设备抑制自动连接”公开 API,MiCar 用
Settings.Secure单槽位 + settingslib patch 实现 CarPlay 隔离,存在脆弱点(见 31)。 - PAN 不自动连:按需手动触发,勿与 MAP/SAP 混。
- 三 flavor 行号差异:引用 settingslib 必标 flavor;f3dif(A17主线)
onBondingStateChanged双参,与 xcddif/dcddif 单参签名不同。
相关:03-设备详情(PBAP 开关)、21-settingslib架构(四件套+connect链)、31-连接策略(自动连接+黑名单5假设)、33-CarPlay链路。