first commit

This commit is contained in:
admin committed 2026-11-05 14:35:04 +08:00
commit 669f49b1d6
342 files changed
+320811

No files matched your search

@@ -0,0 +1,544 @@
# 蓝牙模块开发熟悉手册
> 整理日期:2026-07-06
> 整理范围:`F:\zdCode\xugong` 当前代码、`agent.md` 历史线索、蓝牙目录现有 Markdown、docx、xlsx、PDF 可抽取文本。
> 目的:帮助新接手开发者快速理解徐工项目蓝牙模块的架构、主流程、代码入口、日志排查路径和历史高风险点。
## 一句话结论
徐工蓝牙不是单一 APK 功能,而是一条跨 `YX_BlueTooth`、`YX_BluetoothSDK`、`YX_Music`、`YX_Voice`、`YX_Launcher`、`YX_SystemUI`、Android Frameworks、系统 Bluetooth 应用、顾凯 SDK/芯片的链路。开发时优先从 `YX_BluetoothSDK` 看协议封装,再看上层应用如何订阅 SDK 回调和 `DataManager` 数据。
## 1. 模块地图
| 模块 | 定位 | 重点入口 |
| ------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `YX_BluetoothSDK` | 蓝牙核心 SDK,封装 HFP/A2DP/AVRCP/PBAP/MAP、数据库、状态推送、语音同步 | `manager`、`modules`、`platform`、`audio`、`database` |
| `YX_BlueTooth` | 蓝牙电话主业务应用,联系人、通话记录、拨号、通话页、常驻服务、widget | `MainActivity`、`ContactsFragment`、`CallLogsFragment`、`BluetoothPhoneActivity`、`BluetoothService` |
| `YX_Music` | 蓝牙音乐页面所在应用,负责蓝牙音乐页面 UI、播放控制、封面显示 | `BluetoothMusicFragment`、`BluetoothMusicViewModel` |
| `YX_Voice` | 语音控制蓝牙开关、电话、音乐、通讯录/通话记录状态同步 | `VoiceProtocol`、`BlueToothHelper`、`BluetoothMonitorReceiver`、`VoiceEventHelper` |
| `YX_Launcher` | 首页音乐卡片、蓝牙音乐信息展示和媒体按键入口 | `WidgetsMedia`、`DisplayShareViewModel`、`Id3Info` |
| `YX_SystemUI` | 状态栏/导航媒体区、通话状态显示、系统级入口 | `MediaControlView`、`VirtualManager`、SystemUI 蓝牙封装类 |
| `YX_CarControl` | 配对弹窗、自动同步开关等外围联动 | 注意不要重复初始化 PBAP 联系人同步 |
| Frameworks / Bluetooth Process | Android 蓝牙 API、Binder、系统 Bluetooth app、JNI、协议栈 | `android.bluetooth.*`、`packages/apps/Bluetooth` |
| 顾凯 SDK/芯片 | 蓝牙协议栈供应商实现和 HCI 交互 | 底层联调、btsnoop/顾凯日志 |
## 2. 总体分层
PDF《蓝牙框架原理以及通讯数据交互流程》给出的系统层级可落到项目代码中:
```mermaid
flowchart TD
A["上层应用<br/>YX_BlueTooth / YX_Music / YX_Voice / Launcher / SystemUI"] --> B["YX_BluetoothSDK<br/>ControlManager / Manager / Platform Client"]
B --> C["Frameworks API<br/>android.bluetooth.*"]
C --> D["Bluetooth Process<br/>packages/apps/Bluetooth"]
D --> E["JNI<br/>packages/apps/Bluetooth/jni"]
E --> F["AOSP 蓝牙协议栈<br/>system/bt"]
F --> G["顾凯 SDK / HAL / HIDL"]
G --> H["蓝牙芯片"]
```
反向回调通常是:芯片/协议栈事件 -> JNI -> 系统 Bluetooth 服务 -> Frameworks 广播/API -> `YX_BluetoothSDK platform` 接收 -> `modules` 整理状态 -> 上层 UI、语音、Launcher/SystemUI。
## 3. SDK 内部分层
`YX_BluetoothSDK` 是最值得先读的模块,建议按下面顺序看:
| 层 | 包路径 | 职责 |
|---|---|---|
| 对外控制层 | `com.yaxon.bluetoothsdk.manager` | 单例入口,给 App/语音/音乐模块调用,例如 `BtPhoneControlManager`、`BtContactsControlManager`、`BtMusicControlManager` |
| 业务模块层 | `com.yaxon.bluetoothsdk.modules` | 真正组织业务状态机、监听、数据库、语音/投屏同步,例如 `BtContactsManager`、`BtPhoneManager`、`BtMusicManager`、`BtInfoPushManager` |
| 平台适配层 | `com.yaxon.bluetoothsdk.platform` | 封装 Android 蓝牙 API 和广播,例如 `BluetoothHfpClient`、`BluetoothPbapClient`、`BluetoothAvrcpClient`、`BluetoothConnectListenerClient` |
| 音频层 | `com.yaxon.bluetoothsdk.audio` | 蓝牙音乐音频焦点、A2DP mute/unmute、MediaSession 媒体键 |
| 数据层 | `com.yaxon.bluetoothsdk.database` | 联系人、通话记录、配对设备、收藏等本地 SQLite 数据 |
| 工具层 | `utils` | 系统联系人/通话记录读取、号码处理、日志、权限等 |
开发时一般从 `ControlManager` 找入口,然后下钻到 `modules`,再看 `platform` 是调用系统 API 还是接收广播。
## 4. 启动与初始化
蓝牙电话应用开机常驻入口:
```mermaid
flowchart LR
BootReceiver["YX_BlueTooth BootReceiver"] --> Service["BluetoothService.onCreate"]
Service --> SDK["初始化 BtPhone/BtContacts/BtCallRecords 等控制器"]
Main["MainActivity.onCreate/initData"] --> VM["SettingViewModel / Fragment ViewModel"]
VM --> Managers["BtSettingControlManager / BtContactsControlManager / BtPhoneControlManager"]
```
关键文件:
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/receiver/BootReceiver.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/service/BluetoothService.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtSettingManager.java`
`BtSettingManager.init()` 会初始化 `BluetoothHfpClient`、`BluetoothA2dpSinkClient`、`BluetoothDeviceClient`、`BluetoothMapConnectClient`、`BluetoothConnectListenerClient`,并在蓝牙已打开时初始化已配对设备列表。它还会延迟检查已连接设备,并把当前活跃蓝牙 MAC 同步给语音模块。
## 5. 蓝牙开关、扫描、配对、连接
### 5.1 状态来源
蓝牙开关状态来自 `BluetoothAdapter.ACTION_STATE_CHANGED`,SDK 侧由 `BluetoothConnectListenerClient.setBluetoothSwitchState()` 处理:
| 状态值 | Android 常量 | 含义 |
|---|---|---|
| `10` | `BluetoothAdapter.STATE_OFF` | 蓝牙已关闭 |
| `11` | `BluetoothAdapter.STATE_TURNING_ON` | 正在打开 |
| `12` | `BluetoothAdapter.STATE_ON` | 蓝牙已打开 |
| `13` | `BluetoothAdapter.STATE_TURNING_OFF` | 正在关闭 |
注意:`BluetoothAdapter.enable()` / `disable()` 返回 `true` 只代表请求被接受,不代表最终状态已切到 `ON/OFF`。语音场景必须看后续 `blueState -> 12` 或 `blueState -> 10`。
### 5.2 连接状态
连接状态由 `BluetoothConnectListenerClient` 聚合 HFP、A2DP、A2DP Sink 等 profile 广播,再通知各模块:
- `notifyBluetoothConnected()`:写入连接 MAC、连接名、连接状态,并回调 `onConnect`
- `notifyBluetoothDisconnect()`:清理蓝牙音乐信息,写入断开状态,并回调 `onDisconnect`
- `setBondState()`:处理配对状态
- `setPairingRequest()`:处理配对请求
### 5.3 HFP 与 A2DP 的关系
交接说明里特别强调:蓝牙连接包含 `hfp`(电话)和 `a2dp`(音乐/音频连接);通讯录同步需要再连 `pbap`,同步完成后需要断开 `pbap`。
## 6. 通讯录与通话记录同步(PBAP)
PBAP 是 Phone Book Access Profile,用于从手机拉取通讯录和通话记录。本项目中通讯录和通话记录共享一次 PBAP 同步窗口:联系人完成、通话记录完成后,才认为本轮电话本下载整体完成,并主动断开 PBAP,避免阻塞和卡死。
### 6.1 主流程
```mermaid
sequenceDiagram
participant UI as ContactsFragment/CallLogsFragment
participant CM as BtContactsControlManager
participant M as BtContactsManager
participant P as BluetoothPbapClient
participant SYS as Android PBAP/System Provider
participant DB as DataContactsHelper/DataCallRecordHelper
participant V as ViewModel/Voice
UI->>CM: phoneBookPullRequest(context)
CM->>M: phoneBookPullRequest(context)
M->>P: connectPbap 或 phoneBookPullRequest
P->>SYS: 删除系统 Contacts/CallLog 缓存
P->>SYS: 发送 Intent.ACTION_USER_UNLOCKED
SYS-->>P: contacts.download.complete
P-->>M: onContactsPullCompleted()
M->>SYS: ContactUtils.getAllContacts()
M->>DB: deleteContacts + saveContactsList
M-->>V: onContactsPhoneBook(list)
SYS-->>P: calllogs.download.complete
P-->>CallRecords: onCallLogsPullCompleted()
CallRecords->>SYS: CallLogUtils.getCallLogList()
CallRecords->>DB: clearCallRecord + saveCallRecordList
CallRecords-->>V: onCallRecords(list)
M->>P: contacts + calllogs 都完成后 disconnectPbap()
```
### 6.2 关键代码入口
| 场景 | 文件/方法 |
|---|---|
| UI 点击同步联系人 | `ContactsFragment.phoneBookPullRequest()` |
| UI 点击/通话页触发同步 | `CallLogsFragment` -> `CallHistoryViewModel.phoneBookPullRequest()` |
| SDK 对外入口 | `BtContactsControlManager.phoneBookPullRequest()` |
| 同步状态、权限、自动同步 | `BtContactsManager.phoneBookPullRequest()`、`isAutoSync()`、`checkContactsPermissionGranted()` |
| PBAP 连接和系统广播 | `BluetoothPbapClient.connectPbap()`、`phoneBookPullRequest()`、`PbapClientReceiver` |
| 联系人读取与保存 | `ContactUtils.getAllContacts()`、`DataContactsHelper.savePhoneBookList()` |
| 通话记录读取与保存 | `CallLogUtils.getCallLogList()`、`BtCallRecordsManager.loadCallsLog()`、`DataCallRecordHelper.saveCallRecordList()` |
| UI 状态判断 | `ContactsFragment.syncPhoneBookSynchronized()`、`CallLogsFragment.notifyPhoneRecordList()` |
### 6.3 关键状态位
| 状态 | 说明 | 风险 |
|---|---|---|
| `isPhoneBookPulling` | PBAP 本轮同步是否正在进行 | UI loading 和重复点击判断要优先看它 |
| `isContactsPullCompeted` / `getContactLoadWonType()` | SDK 本轮联系人读取、写库、回调是否完成 | 比 `isPhoneBookSynchronized()` 更能代表本轮同步完成 |
| `isPhoneBookSynchronized()` | 本地 DB 当前设备是否已有至少一条联系人 | 只能代表“本地有数据”,不代表本轮 PBAP 完成 |
| `isCallRecordUpdated()` | 本地 DB 当前设备是否已有通话记录 | 通话记录页显示和 loading 判断会用到 |
### 6.4 已验证历史结论
- 清空蓝牙本地 SQLite 表不是通讯录同步失败的直接原因;正常清空后 PBAP 会重新写入。
- `isPhoneBookSynchronized()` 只表示 DB 有联系人,不能作为“本轮同步完成”的唯一条件。
- 曾出现 `YX_CarControl` 和 `YX_BlueTooth` 双进程同时初始化 PBAP,导致重复 `phoneBookPullRequest`、重复删写联系人表;当前应统一由 `com.yaxon.bluetooth` 负责 PBAP 同步。
- 联系人同步中应优先显示 loading,避免在旧库数据或第一条联系人写入后提前显示“0 个联系人”或旧列表。
- 通话记录 Adapter 曾因传入同一个 list 导致 `clear()` 自清空,已通过传 `new ArrayList<>(mCallRecordList)` 规避。
### 6.5 排查日志关键字
| 阶段 | 关键字 |
|---|---|
| 请求开始 | `BluetoothPbapClient: phoneBookPullRequest` |
| PBAP 连接变化 | `PbapClientReceiver device name`、`prevState`、`state` |
| 联系人下载完成 | `BtContactsManager: onContactsPullCompleted` |
| 联系人读取完成 | `contacts read completed size =` |
| 联系人 UI 收到 | `ContactListViewModel: onContactsPhoneBook =` |
| 通话记录读取 | `mCallRecordBeanList size =` |
| 通话记录保存 | `saveCallRecordList size =` |
| 通话记录 UI | `CallLogsFragment: notifyPhoneRecordList list ==` |
| 失败 | `onContactsPullFailed`、`contacts synchronization failed`、`通讯录同步失败` |
## 7. 蓝牙电话(HFP)
HFP 是 Hands-Free Profile,用于拨号、接听、拒接、挂断、通话音频状态等。
### 7.1 电话状态
| 状态值 | 常量 | 说明 |
|---|---|---|
| `0` | `CALL_STATE_ACTIVE` | 通话中 |
| `1` | `CALL_STATE_HELD` | 保持中 |
| `2` | `CALL_STATE_DIALING` | 正在拨号 |
| `3` | `CALL_STATE_ALERTING` | 正在响铃,项目中通常合并到拨号处理 |
| `4` | `CALL_STATE_INCOMING` | 来电 |
| `5` | `CALL_STATE_WAITING` | 第三方等待 |
| `6` | `CALL_STATE_HELD_BY_RESPONSE_AND_HOLD` | 响应并保持,项目中通常合并到保持处理 |
| `7` | `CALL_STATE_TERMINATED` | 挂断 |
### 7.2 拨号调用链
PDF《蓝牙电话交互流程解析》给出的拨号链路,对应当前项目可理解为:
```mermaid
flowchart TD
A["YX_BlueTooth UI / BluetoothService / Voice"] --> B["BtPhoneControlManager.dialing"]
B --> C["BtPhoneManager.dialing"]
C --> D["BluetoothHfpClient.dial"]
D --> E["BluetoothHeadsetClient.dial"]
E --> F["BluetoothHeadsetClientBinder.dial"]
F --> G["HeadsetClientService.dial"]
G --> H["HeadsetClientStateMachine.Connected.dial"]
H --> I["NativeInterface.dial"]
I --> J["com_android_bluetooth_hfpclient.dialNative"]
J --> K["顾凯 SDK / 蓝牙芯片"]
```
接听、拒接、挂断与拨号类似,只是 Frameworks API 分别换成:
- `BluetoothHeadsetClient.acceptCall()`
- `BluetoothHeadsetClient.rejectCall()`
- `BluetoothHeadsetClient.terminateCall()`
### 7.3 状态回调链
```mermaid
sequenceDiagram
participant FW as BluetoothHeadsetClient 广播
participant HFP as BluetoothHfpClient
participant M as BtPhoneManager
participant UI as BluetoothPhoneViewModel/UI/Window
participant Push as BtInfoPushManager
participant Voice as BtVoiceControlManager
FW-->>HFP: ACTION_CALL_CHANGED
HFP->>HFP: getCurrentCalls()
HFP-->>M: OnCallPhoneStateListener.callState(list)
M->>M: 转成 CallingState
M-->>UI: onHfpCallChanged(list)
M->>Push: setPhoneState / setPhoneCallState / setPhoneGroups
M->>Voice: notifyBtInComing / notifyBtCall / notifyBtAccept / notifyBtHangup
```
### 7.4 关键文件
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtPhoneControlManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtPhoneManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/BluetoothHfpClient.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/viewmodel/BluetoothPhoneViewModel.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/BluetoothPhoneActivity.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/window/TelephoneWindowControl.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/window/ThirdCallingControl.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/service/BluetoothService.java`
### 7.5 排查日志关键字
| 问题 | 关键字 |
|---|---|
| 拨号没有发出 | `BluetoothHfpClient: dial phoneNumber`、`start dial phoneNumber` |
| HFP 未连接 | `hfp_phone_protocol_not_connected`、`isHfpConnect disconnect` |
| 来电/去电 UI 不刷新 | `ACTION_CALL_CHANGED`、`clientCallList`、`onHfpCallChanged` |
| 挂断/拒接失败 | `hangupCall`、`rejectCall`、`acceptCall flag` |
| 通话音频异常 | `ACTION_AUDIO_STATE_CHANGED`、`audio connection state`、`connectAudio`、`disconnectAudio` |
## 8. 蓝牙音乐(A2DP/AVRCP/MediaSession)
蓝牙音乐涉及两条线:
- A2DP:音频流链路,决定声音是否从手机到车机。
- AVRCP/MediaBrowser/MediaSession:播放控制、播放状态、歌曲元数据、媒体按键。
### 8.1 主流程
```mermaid
sequenceDiagram
participant UI as YX_Music/BluetoothMusicFragment
participant CM as BtMusicControlManager
participant M as BtMusicManager
participant Audio as BtMusicAudioManager
participant AVRCP as BluetoothAvrcpClient/MediaBrowserClient
participant Push as BtInfoPushManager/DataManager
participant L as Launcher/SystemUI/Voice
UI->>CM: requestAudioFocus/playBtMusic
CM->>M: requestAudioFocus/playBtMusic
M->>Audio: requestAudioFocus()
Audio->>M: setA2dpUnMute / informAudioFocusStateNative=1
M->>AVRCP: play()
AVRCP-->>M: onPlaybackStateChanged / onMetadataChanged
M-->>UI: onPlayMusicState/onPlayMusicInfo
M->>Push: sendMusicInfoToLauncher(BT_MUSIC_INFO)
Push-->>L: ContentObserver/DataManager 读取 BT_MUSIC_INFO
```
### 8.2 关键代码入口
| 场景 | 文件/方法 |
|---|---|
| 蓝牙音乐页面 | `YX_Music/src/music/java/com/yaxon/musicplay/view/music/BluetoothMusicFragment.java` |
| 音乐 ViewModel | `YX_Music/src/main/java/com/yaxon/musicplay/viewmodel/BluetoothMusicViewModel.java` |
| SDK 控制入口 | `BtMusicControlManager.playBtMusic()`、`pauseBtMusic()`、`requestAudioFocus()` |
| 音乐业务状态 | `BtMusicManager.onPlaybackStateChanged()`、`onMetadataChanged()` |
| 音频焦点和媒体键 | `BtMusicAudioManager.requestAudioFocus()`、`onMediaButtonEvent()` |
| AVRCP 控制 | `BluetoothAvrcpClient.play/pause/previous/next()` |
| A2DP 音频状态 | `BluetoothA2dpSinkClient` |
| Launcher/SystemUI 显示 | `BtInfoPushManager.sendMusicInfoToLauncher()`、`DataUtils.BT_MUSIC_INFO` |
### 8.3 焦点与保护逻辑
`BtMusicManager.onPlaybackStateChanged()` 中有一条关键保护:如果蓝牙音乐开始播放,但当前蓝牙音乐不在前台且没有蓝牙音乐焦点,就会 mute A2DP 并 `pauseBtMusic()`。这是为了防止手机端或后台异常播放抢声道,但也会影响 Launcher 首页音乐卡片冷启动首次播放场景。
历史上已经通过 SDK 侧 `BtMusicAudioManager` 的 MediaSession 兜底处理过 Launcher 首次点击:
- MediaSession 在无焦点但蓝牙音乐是当前音源时也可接媒体键。
- 收到 `KEYCODE_MEDIA_PLAY_PAUSE` 后先申请焦点,再 `playBtMusic()`。
- A2DP 连接、元数据、播放状态变化后刷新 MediaSession,避免第一次点击只激活 session、第二次才播放。
### 8.4 封面高风险点
蓝牙音乐封面风险集中在固定路径:
```text
/sdcard/ID3/id3cover.JPEG
```
问题模式:
- 系统/底层复用同一个封面路径。
- 歌曲元数据先到,封面路径后到。
- 文件可能短时间为 0 字节或半写入。
- 空 `albumUrl` 或无效文件可能覆盖当前有效封面。
排查关键字:
- `albumUrl = /sdcard/ID3/id3cover.JPEG`
- `length = 0`
- `Failed to create image decoder`
- `获取bitmap为null`
- `skip invalid album file`
- `loadFileImageToView success`
## 9. 语音与蓝牙交互
### 9.1 语音开关蓝牙
```mermaid
flowchart TD
A["TXZ / VoiceService 请求<br/>openBluetooth / closeBluetooth"] --> B["VoiceProtocol.handleBluetooth"]
B --> C["BlueToothHelper.openBlueToothByVoice / closeBlueToothByVoice"]
C --> D["BluetoothAdapter.enable / disable"]
D --> E["BluetoothMonitorReceiver.ACTION_STATE_CHANGED"]
E --> F["BlueToothHelper.syncBluetoothStatusToTxzByStateChanged"]
F --> G["VoiceEventHelper.notifyBluetoothOpenStaus / notifyBluetoothConnectStaus"]
```
关键文件:
- `YX_Voice/src/main/java/com/yaxon/voice/protocol/VoiceProtocol.java`
- `YX_Voice/src/main/java/com/yaxon/voice/helper/BlueToothHelper.java`
- `YX_Voice/src/main/java/com/yaxon/voice/receiver/BluetoothMonitorReceiver.java`
- `YX_Voice/src/main/java/com/yaxon/voice/helper/VoiceEventHelper.java`
重点:
- `openBlueToothByVoice()` / `closeBlueToothByVoice()` 做了同目标去抖、相反目标保护、有限重试、延迟状态回填。
- `BluetoothMonitorReceiver` 收到 `STATE_ON/OFF` 后立即同步 TXZ,并延迟补发状态。
- 不能以 `enable()/disable()` 返回值作为最终成功依据。
### 9.2 语音电话与通讯录
语音电话/通讯录状态同步主要由 SDK 侧 `BtVoiceManager` 和 `BtVoiceControlManager` 承接:
- 电话来电:`BtPhoneManager` -> `BtVoiceControlManager.notifyBtInComing`
- 去电:`notifyBtCall`
- 接通:`notifyBtAccept`
- 挂断:`notifyBtHangup`
- 联系人同步完成:`BtVoiceManager.syncBtActiveDeviceMac`、`BtVoiceControlManager.notifyBtContactReload`
- 通话记录变化:`BtVoiceManager.notifyCallRecordChange`
## 10. Launcher/SystemUI/投屏联动
### 10.1 数据通道
SDK 通过 `BtInfoPushManager` 写入公共数据,外围模块通过 `ContentObserver` 或 `DataManager.getObject()` 读取。
| 数据 | 写入方 | 读取方 |
|---|---|---|
| `DataUtils.BT_MUSIC_INFO` | `BtInfoPushManager` | `YX_Launcher`、`YX_SystemUI`、`YX_Voice` |
| `DataUtils.BT_INFO` | `BtInfoPushManager` | Launcher/SystemUI/投屏相关 |
| 电话状态、联系人、号码、通话时长 | `BtPhoneManager` -> `BtInfoPushManager` | 仪表、Launcher、SystemUI、语音 |
### 10.2 常见现象
- Launcher 卡片能显示蓝牙音乐元数据,不代表按钮控制一定会走蓝牙音乐控制链路;按钮可能只是全局媒体键。
- SystemUI/Launcher 读取封面路径时要防空路径、0 字节、解码失败、同一首歌空封面覆盖已有封面。
- 电话投屏状态依赖 `BtInfoPushManager.setPhoneState()`、`setPhoneCallState()`、`setPhoneGroups()`。
## 11. 数据存储
| 数据 | 类 | 说明 |
|---|---|---|
| 联系人 | `DataContactsHelper` | 按蓝牙设备 MAC 存储联系人 |
| 通话记录 | `DataCallRecordHelper` | 按蓝牙设备 MAC 存储通话记录,过滤网络电话等异常号码 |
| 已配对设备 | `DataPairedHelper` | 配对设备列表 |
| 收藏 | `DataCollectionHelper` | 收藏联系人/号码 |
| 系统 Provider | `ContactUtils`、`CallLogUtils` | PBAP 下载完成后从系统联系人/通话记录 Provider 读取 |
| 跨模块状态 | `DataManager`、`YxIPC` | 音乐、电话、语音、Launcher/SystemUI 状态同步 |
开发时要分清:
- 系统联系人/通话记录 Provider 是 PBAP 下载中间数据。
- SDK 自己的 SQLite 是业务最终缓存。
- `DataManager` 是跨应用状态和展示数据的同步通道。
## 12. 常见问题排查索引
| 问题 | 先看模块 | 关键日志 |
|---|---|---|
| 蓝牙打不开/关不掉,语音已回复成功 | `YX_Voice`、`BluetoothConnectListenerClient` | `openBlueTooth`、`closeBlueTooth`、`blueState -> 10/11/12/13`、`syncBluetoothStatusToTxz` |
| 手机能连但电话不可用 | HFP | `hfp onServiceConnected`、`updateHfpConnectedState`、`isHfpConnect` |
| 手机能播但车机无声 | A2DP/焦点 | `bluetooth music requestAudioFocus`、`setA2dpUnMute`、`informAudioFocusStateNative` |
| Launcher 首页点播放无效 | MediaSession/焦点 | `WidgetMedia: KEYCODE_MEDIA_PLAY_PAUSE`、`MediaSessionService: Sending KeyEvent`、`BtMusicAudioManager: onMediaButtonEvent` |
| 通讯录不同步 | PBAP | `phoneBookPullRequest`、`onContactsPullCompleted`、`contacts read completed size`、`onContactsPhoneBook` |
| 通话记录为空 | PBAP/Adapter/UI | `mCallRecordBeanList size`、`saveCallRecordList size`、`notifyPhoneRecordList list ==` |
| 封面闪烁/消失/上一首封面 | 蓝牙音乐封面 | `albumUrl`、`length = 0`、`decodeFile`、`skip invalid album file` |
| 电话 UI 不弹/不消失 | HFP/UI/悬浮窗 | `ACTION_CALL_CHANGED`、`onHfpCallChanged`、`CallingState`、`TelephoneWindowControl` |
| 断电上电状态错 | ACC/POWER/SOURCE_ID | `POWER_MODE`、`ACC_STATE`、`SOURCE_ID`、`restoreBtMusicIfNeeded` |
## 13. 开发路线图
建议按这个顺序熟悉:
1. 先读现有文档:`蓝牙模块流程概览.md`、`蓝牙框架原理以及数据通讯交互流程.pdf`、`蓝牙电话交互流程解析.pdf`、`徐工项目蓝牙模块交接说明.docx`。
2. 看 `YX_BluetoothSDK` 分层:先 `manager`,再 `modules`,最后 `platform/audio/database`。
3. 跟一遍 PBAP:从 `ContactsFragment.phoneBookPullRequest()` 跟到 `BluetoothPbapClient.phoneBookPullRequest()`,再跟回 `onContactsPullCompleted()`。
4. 跟一遍 HFP:从 `BluetoothPhoneViewModel.acceptCall/rejectCall` 或 `BtPhoneControlManager.dialing()` 跟到 `BluetoothHfpClient`。
5. 跟一遍蓝牙音乐:从 `BluetoothMusicFragment` 跟到 `BtMusicControlManager`、`BtMusicManager`、`BtMusicAudioManager`、`BluetoothAvrcpClient`。
6. 跟语音开关蓝牙:从 `VoiceProtocol.handleBluetooth()` 跟到 `BlueToothHelper` 和 `BluetoothMonitorReceiver`。
7. 看外围联动:`BtInfoPushManager` 写 `BT_MUSIC_INFO/BT_INFO`,再看 Launcher/SystemUI 的读取和显示。
8. 最后看历史问题:`agent.md` 中的 PBAP 双进程、联系人 loading、通话记录自清空、封面固定路径、Launcher 首次点击播放。
## 14. 修改代码时的注意事项
- PBAP 同步不要在多个进程重复初始化监听,联系人/通话记录同步应以 `YX_BlueTooth` 进程为主。
- UI 判断“同步完成”不要只看 `isPhoneBookSynchronized()`,本轮同步期间还要看 `isPhoneBookPulling` 和 `getContactLoadWonType()`。
- PBAP 完成后要断开 PBAP,否则历史上有“阻塞、软件卡死”的风险。
- 蓝牙开关语音反馈要等最终 `STATE_ON/OFF`,不能只看 `enable/disable result=true`。
- 蓝牙音乐播放控制必须先处理音频焦点和 A2DP unmute,再发 AVRCP play。
- 封面路径下发/展示前要校验文件存在、非 0 字节、可解码,且要处理固定路径重写竞态。
- 通话记录/联系人传给 Adapter 或回调前尽量使用快照,避免共享 list 被清空或并发修改。
- `destroy()` 类清理要注意真正 `shutdown()` 线程池,历史代码里存在只调用 `isShutdown()` 的可疑写法。
## 15. 已知潜在风险
| 风险 | 说明 |
|---|---|
| `BtMusicControlManager.isA2dpSinkConnected()` | 历史排查中怀疑可能固定返回 false,需要结合当前代码确认 |
| 多处线程池销毁 | 部分 `destroy()` 里可能只调用 `isShutdown()` 而不是 `shutdown()` |
| `BluetoothMapConnectClient.isMapConnect()` | 历史排查中提到潜在 NPE |
| PBAP 同步耗时 | 现场 1973 联系人约 85 秒级,日志时间还可能因设备校时跳变 |
| 固定封面路径 | `/sdcard/ID3/id3cover.JPEG` 会复用、清零、半写入,跨 Music/Launcher/SystemUI 都要防 |
| 全局媒体键路由 | Launcher 卡片点击可能先被系统媒体 session 路由影响,需看 `MediaSessionService` 日志 |
## 16. 构建、部署、底层日志
常用构建:
```powershell
.\gradlew :YX_BluetoothSDK:assembleDebug --console plain
.\gradlew :YX_BlueTooth:assembleDebug --console plain
.\gradlew :YX_Music:assembleDebug --console plain
.\gradlew :YX_Voice:assembleDebug --console plain
```
交接说明里的部署注意点:
- 蓝牙音乐修改后,需手动打包更新到 `third_apps` 目录下。
- 配合顾凯 SDK 修改时,可以打开顾凯 SDK 日志:
```shell
adb shell setprop persist.bluetooth.btsnoopenable true
```
日志生成位置:
```text
/data/misc/bluetooth/logs
```
烧录文档补充:
- `pac` 格式通过 `SDToolBox -> SDFactoryTool` 烧录。
- `zip` 格式为 U 盘升级,将 `totalUpdate.zip` 放 U 盘根目录,系统弹窗升级。
## 17. 参考资料
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙模块流程概览.md`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\bluetooth_git_summary.md`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目蓝牙模块交接说明.docx`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目蓝牙模块主流程.xlsx`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙框架原理以及数据通讯交互流程.pdf`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙电话交互流程解析.pdf`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目软件烧录文档.pdf`
## 18. 快速入口清单
### SDK
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtSettingManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtPhoneManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtContactsManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtCallRecordsManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtMusicManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtInfoPushManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\audio\BtMusicAudioManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothConnectListenerClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothHfpClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothPbapClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothAvrcpClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\MediaBrowserClient.java`
### 蓝牙电话应用
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\MainActivity.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothPhoneActivity.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\ContactsFragment.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\CallLogsFragment.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\ContactListViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\CallHistoryViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\BluetoothPhoneViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\service\BluetoothService.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\provider\BluetoothContactsProvider.java`
### 音乐、语音和外围
- `F:\zdCode\xugong\YX_Music\src\music\java\com\yaxon\musicplay\view\music\BluetoothMusicFragment.java`
- `F:\zdCode\xugong\YX_Music\src\main\java\com\yaxon\musicplay\viewmodel\BluetoothMusicViewModel.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\protocol\VoiceProtocol.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\helper\BlueToothHelper.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\receiver\BluetoothMonitorReceiver.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\helper\VoiceEventHelper.java`
- `F:\zdCode\xugong\YX_Launcher\src\main\java\com\yaxon\launcher\view\WidgetsMedia.java`
- `F:\zdCode\xugong\YX_SystemUI\src\main\java\com\android\systemui\sdk\view\navigation\MediaControlView.java`