first commit
No files matched your search
@@ -0,0 +1,503 @@
|
||||
# agent.md
|
||||
|
||||
## 项目目标
|
||||
|
||||
新项目 `F:\zdCode\tongli` 的蓝牙业务交互、需求和 UI,需要对齐旧项目 `F:\zdCode\tongli_old` 的蓝牙体验。
|
||||
|
||||
用户明确要求:
|
||||
|
||||
- 业务逻辑一样。
|
||||
- UI 和业务交互要一样。
|
||||
- 代码可以不参考旧项目。
|
||||
- 当前阶段先梳理,不急着改代码。
|
||||
|
||||
## 当前阶段
|
||||
|
||||
蓝牙模块主界面迁移开发阶段。
|
||||
|
||||
已生成详细文档:
|
||||
|
||||
- `蓝牙业务迁移梳理_2026-07-09.md`
|
||||
- `蓝牙开发实施方案_2026-07-09.md`
|
||||
|
||||
## 当前环境
|
||||
|
||||
- 当前系统:Windows
|
||||
- 当前 shell:PowerShell
|
||||
- 当前工作目录:`F:\zdCode\tongli`
|
||||
- 旧项目参考路径:`F:\zdCode\tongli_old`
|
||||
|
||||
## 核心结论
|
||||
|
||||
这不是单纯 UI 换皮,而是要按旧项目蓝牙业务基线,对齐整套车机蓝牙体验。
|
||||
|
||||
新项目已有承接点:
|
||||
|
||||
- 蓝牙电话:`YX_BlueTooth`
|
||||
- 蓝牙 SDK:`YX_BluetoothSDK`
|
||||
- 蓝牙音乐:`YX_BtMusic`
|
||||
- 桌面卡片:`YX_Launcher`
|
||||
- 状态栏/快捷蓝牙:`YX_SystemUI`
|
||||
|
||||
更新 2026-07-14:`YX_BtMusic` 当前仅保留历史代码,APK 已不再集成;当前运行链路中的蓝牙音乐入口归入 `YX_BlueTooth`,蓝牙能力仍统一由 `YX_BluetoothSDK` 封装。用户已从 `third_apps/preinstall.mk` 删除 `YX_BtMusic` 预装项;根目录 release 打包脚本也已移除主动构建 `:YX_BtMusic:assembleRelease`。本地仍存在部分旧包名常量,先按历史入口残留处理,后续结合入口迁移需求清理。
|
||||
|
||||
旧项目核心业务入口:
|
||||
|
||||
- 蓝牙拨号
|
||||
- 联系人
|
||||
- 蓝牙音乐
|
||||
- 蓝牙设置
|
||||
- 通话记录
|
||||
- 来电/拨号/通话中/三方通话
|
||||
|
||||
## 关键决策
|
||||
|
||||
- 不搬旧代码,优先抽旧项目业务行为和 UI 状态。
|
||||
- 新平台保持现有 SDK/ViewModel/Activity 架构,按旧业务补齐交互。
|
||||
- 后续开发前应先确认页面资产清单、弹窗清单、状态清单。
|
||||
- 开发策略:旧项目作为需求基线和验收标准,新项目作为实现架构和平台能力。
|
||||
- 三方通话在老项目中明确存在,应作为新项目高风险专项验收。
|
||||
- 当前阶段只专注 `YX_BlueTooth` 蓝牙模块,不考虑 SystemUI、Launcher、MCU 或其他模块联动。
|
||||
- 主界面先按老项目四入口落地:蓝牙拨号、联系人、蓝牙音乐、蓝牙设置。
|
||||
- 资源优先从老项目复用,不从截图切图;代码继续使用新项目架构。
|
||||
- 顶部栏“蓝牙”标题先不作为 `YX_BlueTooth` 蓝牙模块功能处理;老项目该标题属于顶部栏/主壳视觉,当前蓝牙模块内先隐藏,避免压住左侧菜单。
|
||||
- 长期边界:`YX_Setting` 保持无蓝牙;`YX_BlueTooth` 是唯一蓝牙 UI 和业务入口;`YX_BluetoothSDK` 是唯一蓝牙能力封装。
|
||||
- `YX_BtMusic` 不进入当前 APK 集成和运行链路,只作为历史代码保留;本次不修改其代码本体。
|
||||
|
||||
## 蓝牙后续分析基线(2026-07-24)
|
||||
|
||||
- 已结构化回顾历史会话 `019f406a-be1b-7722-bb19-96f14e36fa5e`。后续蓝牙分析统一以本节为需求口径,不再沿用会话中已被撤回的临时结论。
|
||||
- 最终实现原则:保留新项目的协议、数据、PBAP、数据库和同步状态逻辑;旧项目只作为 UI、交互和用户可见状态的基线。该结论覆盖早期“业务逻辑也完全照旧项目”的表述。
|
||||
- 证据优先级:当前代码与当前 Git 历史 > `agent.md` 中较晚的“更新标记” > 较早记录、交接摘要和临时提交号。运行行为以最新实车复现和日志为准,`BUILD SUCCESSFUL` 只能证明编译通过;若证据冲突,必须重新核对代码、提交 diff 和实车日志。
|
||||
- 范围继续限定为 `YX_BlueTooth` 与 `YX_BluetoothSDK`。除非用户重新授权,不扩展到 SystemUI、Launcher、MCU 或其他模块;Launcher 临时将音乐入口改为蓝牙的代码不属于蓝牙正式提交。
|
||||
- 四入口维持“蓝牙拨号 / 联系人 / 蓝牙音乐 / 蓝牙设置”。拨号和联系人依赖 HFP,音乐依赖 A2DP + AVRCP,设置始终可用;未连接时引导到设置页,HFP 重连后尽快进入拨号页。
|
||||
- 通话记录和联系人未就绪时显示加载/失败状态,不先显示空白或误导性权限结果;同步成功只刷新列表,不增加成功 Toast。
|
||||
- 手动下载通讯录保留旧项目式确认与进度弹框,文案使用“正在下载通讯录列表,请稍等...”;解析进度封顶 95%,数据库保存成功后显示 100% 并短暂保留。联系人不读取真人头像。
|
||||
- 后续重点验证:PBAP 连续下载、断线、超时、空通讯录、空通话记录、双设备迟到事件、数据库故障和切页/后台恢复;HFP 扬声器路由、蓝牙音乐系统 APEX 自动暂停、MCU 媒体键抖动和三方通话仍是独立未闭环专项。
|
||||
- 当前验证以编译、截图、ADB 和实车日志为主,有效单元测试与自动化覆盖不足;提交或宣称修复完成前必须重新审查当前 Git 差异并执行对应异常场景测试。
|
||||
- 历史会话在 2026-07-24 最后 8 次请求均因上游 HTTP 503 结束且没有生成助手答复;这是服务不可用,不代表会话丢失、线程 ID 无效或代码任务失败。
|
||||
|
||||
## 蓝牙深度静态分析基线(会话 019f91d1-340f-72b2-8e3e-c94249a57be6)
|
||||
|
||||
- 已读取本机会话归档 `C:\Users\Administrator\.codex\archived_sessions\rollout-2026-07-24T09-50-39-019f91d1-340f-72b2-8e3e-c94249a57be6.jsonl`。会话名称最终为“蓝牙模块 0724”,工作目录为 `F:\zdCode\tongli`,内容覆盖历史修改回顾、会话 `019f406a-be1b-7722-bb19-96f14e36fa5e` 的结构化分析,以及当前蓝牙代码的深度静态检查。
|
||||
- 当前架构主链路为 `Activity / Fragment -> ViewModel -> ControlManager -> Bt*Manager -> HFP / A2DP / AVRCP / PBAP / MediaBrowser / SQLCipher`。拨号和联系人依赖 HFP,音乐依赖 A2DP + AVRCP,设置始终可进入;正常单设备连接、PBAP 同步和页面门禁主流程基本闭环。
|
||||
- 安全最高优先级风险:`BluetoothService` 对外导出且缺少权限保护,外部应用可能通过显式 Intent 或广播触发拨号、挂断等系统权限操作。修复方向为取消不必要的导出,或增加 signature 级权限并限制广播来源。
|
||||
- PBAP 最高优先级风险:有效空通话记录不能稳定表示为“同步成功”,之后产生的第一条本地通话也可能因同步状态判断而无法入库;同步超时后后台数据库事务仍可能提交;同一设备连续同步缺少真正的会话 ID,迟到广播可能串入新一轮;广播缺少设备信息时存在双设备串代风险;已有缓存时同步失败可能使通话记录页面永久停留在 loading。
|
||||
- 更新标记(修正 2026-07-22“原子终态标志互斥,避免事务提交与超时竞态”的记录):当前原子终态只能互斥 UI 层成功/失败通知,不能保证超时后旧后台事务停止或禁止提交,仍可能出现“UI 已失败,旧任务随后覆盖数据库”。后续须以事务代次校验、提交前终态检查和强制超时测试重新验证。
|
||||
- 更新标记(细化 2026-07-23“同步代次隔离上一轮后台任务”的记录):应用内 generation 只能隔离已接入该代次检查的任务,不能替代平台 PBAP 会话 ID;同 MAC 上一轮迟到广播以及未携带设备信息的广播仍可能被当前轮接受,须通过连续同步和双设备迟到事件实测。
|
||||
- 生命周期与交互风险:Activity 重建可能留下重复 Fragment、ViewModel 和监听;`BtMusicViewModel`、`SettingViewModel`、`CallHistoryViewModel` 等存在监听释放缺口,音乐监听移除接口还存在参数类型不匹配风险;隐藏的音乐 Fragment 仍可能申请音频焦点;自动接听只在完整通话 Activity 中执行;匿名或网络电话空号码可能导致挂断失败但页面先关闭;设置流程调用全局 `HandlerUtil.removeAll()` 可能误删电话 pending 超时和 Profile 回调任务。
|
||||
- 历史归属:`e233a2d1` 主要涉及 PBAP、事务和超时保护,上述空记录、超时提交和迟到事件风险集中在该状态机;`c439bcfd`、`d7bca4ba` 主要负责菜单/Profile 刷新;`8ec79048`、`8cb0e58e` 主要负责蓝牙音乐页面和播放进度。
|
||||
- 建议验证顺序:外部 Intent/广播安全测试 -> 空通讯录和空通话记录 -> 强制数据库事务超时 -> 同 MAC 连续同步及双设备迟到广播 -> Activity 重建后的 Fragment/监听计数 -> APEX 音频焦点、后台自动接听和匿名电话挂断。
|
||||
- 本会话最后一轮仅完成 Windows PowerShell 环境下的静态只读分析,没有运行 Gradle、ADB 或实车测试。以上问题属于待复核风险,后续修复前必须重新对照当前代码、Git 差异和最新实车日志,不能直接视为已复现缺陷。
|
||||
|
||||
## 蓝牙音乐后续分析基线(会话 019f4080-a188-7aa0-98b6-92d3e5f10fb2)
|
||||
|
||||
- 已只读回顾该会话从 2026-07-08 到 2026-07-24 的本地 JSONL 转录,并用当前代码、Git 历史和 `Y:\tongli\android14.0_rk3562` 系统源码复核关键结论。后续蓝牙音乐问题应先以本节为口径,再结合最新实车日志判断。
|
||||
- 当前产品链路为:`YX_BlueTooth` 提供唯一蓝牙音乐 UI,`YX_BluetoothSDK` 负责 A2DP Sink、AVRCP、歌曲信息和控制能力;`YX_BtMusic/com.yaxon.btmusic` 仅保留历史代码,不进入当前 APK 集成和运行链路。音乐入口门禁依赖 A2DP 与 AVRCP 均连接。
|
||||
- 蓝牙音乐 UI 已通过提交 `8ec79048 modify(YX_BlueTooth):新增蓝牙音乐功能` 集成:主界面 `BtMusicFragment` 和独立 `BluetoothMusicActivity` 对齐旧项目圆形封面、歌曲信息、进度条和上一首/播放暂停/下一首按钮,控制逻辑继续复用新项目 `BtMusicViewModel`。
|
||||
- “点击播放后立即暂停”的系统侧根因是 `AvrcpControllerStateMachine.MESSAGE_PROCESS_PLAY_STATUS_CHANGED` 在非活动、焦点错误或无焦点分支主动下发 AVRCP `PAUSE(70)`。正常分支 `Y:\txj\android14.0_rk3562` 的提交 `926b121d63500ff2f7f9c6305d106e45c8fc1f68` 注释了这三处自动暂停;该差异已同步到 `Y:\tongli\android14.0_rk3562` 并提交为 `3d18d82958b fix(Bluetooth):修复蓝牙音乐播放自动暂停`。当前目标文件工作区干净,用户实车确认该修复可解决播放即暂停。
|
||||
- 7 月 13 日曾尝试恢复完整 A2DP Sink 焦点状态机、让应用通过 AVRCP `prepare()` 委托系统焦点以及增加应用侧失焦防重;这些属于中间试验,用户随后已还原,不是最终实现基线。旧独立 `com.yaxon.btmusic` 也曾在测试固件中造成第二次暂停,但当前已不集成,不能继续作为默认根因。
|
||||
- “播放进度条不变化”的直接原因是 AVRCP 位置已每秒上报,但 `PlaybackStateCompat.getExtras()` 经常为 null,旧代码因此没有写回 `playPosition`。提交 `8cb0e58e modify(YX_BlueTooth):修改蓝牙音乐进度条不会刷新问题。` 已改为 `playPosition >= 0` 时直接更新;当前代码和 Git 历史均确认该修复仍在。
|
||||
- `BtMusicManager.onPlaybackStateChanged()` 当前仍保留 `isPlaying && !mBtMusicAudioManager.isFocus()` 时调用 `setA2dpMute()` 和 `pauseBtMusic()` 的旧逻辑。7 月 14 日临时加入的 1200ms 延迟失焦策略已撤回,临时 `pauseBtMusic()` 调用栈日志在当前代码中也不存在。若再次出现自动暂停,必须先按时间线区分 `com.android.bluetooth` 发出的系统 `PAUSE(70)` 与 `BtMusicManager`、UI、MCU、语音或外部控制触发的应用侧暂停,不能直接沿用旧结论。
|
||||
- 已有学习文档:`F:\AI\Obsidian\项目文档\同力\蓝牙音乐\蓝牙音乐模块功能流程梳理_2026-07-13.md`。后续实车验收重点为 A2DP/AVRCP 连接门禁、播放/暂停、上下首、歌曲元数据、进度连续刷新、音源切换、来电后恢复、断线重连和后台恢复。
|
||||
|
||||
## 已完成内容
|
||||
|
||||
- `YX_BlueTooth` 主界面从“键盘 + 通话记录/联系人横向页签”改为老项目式“左侧四菜单 + 右侧页面容器”。
|
||||
- 新增 `DialHomeFragment`:左侧拨号键盘,右侧嵌入现有 `CallLogsFragment`,并保留号码输入后的联系人匹配列表。
|
||||
- 新增 `BtMusicFragment`:复用现有 `BtMusicViewModel` 和蓝牙音乐控制逻辑,作为主界面内切页面。
|
||||
- 新增 `BtSettingFragment`:复用现有 `BtSettingViewModel`、已配对设备/可用设备 Adapter 和扫描连接逻辑,将原弹窗式设置迁成页面式入口。
|
||||
- `ContactsFragment` 继续复用,主界面右上角保留搜索和重新同步入口。
|
||||
- 已从老项目拷贝主界面视觉资源:`activity_bg.png`、`common_bg_line.png`、`bluetooth_tabvertical.xml`、`text_click_selector.xml`、`icon_tabvertical_selected.png`、`title_head_background.png`。
|
||||
- 已将 `BtSettingFragment` 主页面改成老项目式设置列表结构:自动接听、蓝牙开关、蓝牙名称、已配对设备、可用设备。
|
||||
- 已将设置页自动接听开关接到新平台 SDK 既有 `setAutoAcceptCall/isAutoAcceptCall` 能力。
|
||||
- 已对齐蓝牙主界面标题资源:顶部标题为“蓝牙”,四个左侧入口为“蓝牙拨号 / 联系人 / 蓝牙音乐 / 蓝牙设置”。
|
||||
- 已将蓝牙设置页两个开关切换为老项目蓝色开关资源 `common_switch_on/off`,尺寸对齐老项目 `72dp x 40dp`。
|
||||
- 已修正左侧四入口菜单选中态文字颜色:`text_click_selector` 增加 `state_selected=true` 白色,适配 `MenuAdapter` 的 `TextView.setSelected(true)`。
|
||||
- 已按老项目补齐蓝牙设置页“蓝牙名称”二级编辑页:点击名称行后隐藏设置列表,显示 360x64 输入框与“确定”按钮;确认后调用新平台 SDK 设置本机蓝牙名称,成功后返回设置列表并刷新名称。
|
||||
- 已补齐蓝牙名称设置结果提示:保存成功/失败不再使用系统 Toast,改为老项目样式居中结果弹层,成功显示 `icon_success` 和“蓝牙设置成功”,失败显示 `ico_failed` 和“蓝牙设置失败”。
|
||||
- 已按老项目继续对齐蓝牙设置页前三行 UI:行高改为 88dp,左侧标题改为 30sp/#F3F3F3,蓝牙名称右侧值改为 20sp/#E6E6E6,并改用老项目 48x48 右箭头 selector 资源。
|
||||
- 已按老项目继续对齐蓝牙拨号页 UI:键盘改为 356dp 宽、按键 116x60、4dp 间距,拨号按钮改为 356x68 旧项目整条绿色按钮;号码输入区删除键挪回顶部右侧;通话记录 item 改为图标 + 号码 + 联系人/未知号码两行样式。
|
||||
- 根据新旧拨号截图对比,确认新项目拨号页主要偏差不在键盘资源,而在 `YX_BlueTooth` 主壳额外标题区导致整体内容下移;已将主壳改回更接近老项目的坐标:隐藏 80dp 大标题、顶部横线使用老项目 916x40 资源高度、左侧菜单从根布局 37dp 起、右侧内容从根布局 4dp 起,菜单选中胶囊改回老项目 172x60。
|
||||
- 已按老项目拨号盘截图和 `fragment_keyboard_test.xml` 进一步对齐拨号盘本体:数字键只显示居中数字/符号,不显示 ABC/GHI 等副标题;删除键按老项目保持常显;号码文字改为 `#DFDFDF`,号码下划线改为 `#505050`,数字键文字改为 `#F3F3F3`。
|
||||
- 根据用户新旧拨号截图像素对比,确认新项目绿色拨号按钮纵向范围为 y=511-578,老项目为 y=482-549,新项目拨号内容整体低约 29px;已将 `fragment_dial_home.xml` 中拨号键盘顶部 margin 从 40dp 调整为 11dp,并移除 `fragment_call_logs.xml` 通话记录 RecyclerView 顶部 28dp margin,让右侧记录同步上移。
|
||||
- 用户确认只改蓝牙模块后,继续按老项目拨号页对齐 UI 坐标:`activity_main.xml` 中左侧菜单调整为 start=36dp/top=30dp,右侧容器 start 改为 0dp;`fragment_dial_home.xml` 中拨号盘 start=24dp/top=0dp;`fragment_call_logs.xml` 中通话记录列表顶部 margin 调整为 16dp。只涉及 `YX_BlueTooth`。
|
||||
- 根据新旧拨号截图继续修正右侧通话记录:号码不再拼接通话次数 `(callTimes)`,避免比老项目更早省略;号码和联系人 TextView 改为占满右侧剩余宽度,去掉固定 `maxWidth=340dp`;列表右边距按老项目收窄到 5dp;通话记录号码色值改为老项目白色 `#F3F3F3`、联系人灰色 `#B7B9C0`、未接红色 `#FE0404`。
|
||||
- 根据 2026-07-10 新旧拨号截图复核,确认号码显示已完整,剩余主要偏差是拨号盘偏左/偏下、通话记录偏左/偏上;已将 `fragment_dial_home.xml` 中拨号盘 start 从 24dp 调到 36dp、top 从 0dp 调到 -9dp,右侧通话记录容器 start 从 38dp 调到 50dp;将 `fragment_call_logs.xml` 列表顶部 margin 从 16dp 调到 40dp,使布局更接近老项目截图。
|
||||
- 根据 2026-07-10 改后截图复核,拨号盘本体已基本对齐,右侧通话记录存在号码省略和列表偏低;已保持拨号盘不动,将 `fragment_dial_home.xml` 右侧通话记录容器 start 从 50dp 收到 42dp 以释放号码宽度,将 `fragment_call_logs.xml` 列表顶部 margin 从 40dp 回调到 24dp。
|
||||
- 根据 adb 截图复核,右侧通话记录仍存在 `15659813.../18250742...` 省略,判断主界面 `activity_main.xml` 的右侧容器 `container` 仍有 `38dp` 右边距,压缩了通话记录可用宽度;已将 `container` 的 `layout_marginEnd` 从 `@dimen/dip_38` 改为 `0dp`,优先保证 11 位手机号完整显示。
|
||||
- 根据老项目 `KeyBoardFragment/SeparatorEditText` 和老项目截图 13.png 复核,老项目输入号码时右侧仍保持通话记录列表,不显示“最佳适配”;号码显示使用 `SeparatorEditText` 空格分隔规则,纯数字长度不超过 11 时在第 3 位和第 8 位后插入空格。已在新项目中调整:`DialHomeFragment` 输入号码后不再切换 `isMathCalls=true`,不再触发联系人匹配搜索;`KeyBoardView.setDialNumber()` 按老项目规则格式化显示号码,但内部拨号仍使用原始号码。
|
||||
- 根据 2026-07-10 通话记录点击后“等很久才弹通话页”的日志分析,确认应用点击和 `ATD` 拨号命令已快速发出,慢点主要在等待 HFP `CALL_CHANGED/CLCC` 回调以及底层音频链路异常。已先做应用层体验兜底:`CallRecordAdapter` 对通话记录点击加 2 秒防连点,`dialing()` 返回成功后立即以本地 pending 拨号状态打开 `BluetoothPhoneActivity`;`BluetoothPhoneActivity` 针对 `EXTRA_PENDING_LOCAL_DIAL` 放行 `isPhoneCalling()==false` 的短窗口,并增加 15 秒超时保护,真实 HFP 回调到达后清除 pending 并刷新状态。
|
||||
- 根据老项目联系人截图 `联系人4.png` 与旧项目 `fragment_phonebook.xml/listitem_phonebook.xml`,已对齐新项目联系人页:搜索框和下载按钮固定在联系人内容顶部,隐藏新项目 A-Z 索引和分组字母;联系人列表行改为 88dp 高、60dp 联系人图标、左侧姓名 26sp、右侧电话号码 20sp;主界面右上角联系人搜索/刷新入口保持隐藏,改用页面内老项目样式搜索/下载按钮;搜索逻辑保留新项目 ViewModel,但空关键词时恢复正常列表,有关键词时切到搜索结果列表。
|
||||
- 根据 2026-07-10 联系人实际截图复核,新项目联系人内容整体偏右偏下;已二次微调 `fragment_contacts.xml`:搜索区 marginStart 从 132dp 改为 38dp、marginTop 从 88dp 改为 4dp,列表/搜索结果 marginStart 从 132dp 改为 24dp,并将联系人搜索提示从“搜索”改为老项目“请在联系人中搜索”。
|
||||
- 根据 2026-07-10 14:17 联系人截图和老项目截图做像素对比:老项目搜索框约为 x=362..878,下载按钮约为 x=896..959;新项目为 x=350..912、x=930..993。已继续调整 `fragment_contacts.xml`:搜索区宽度 652dp -> 614dp,start 38dp -> 50dp,搜索输入 marginEnd 88dp -> 98dp;联系人列表 start 24dp -> 36dp,让搜索框、下载按钮和列表左边界更贴近老项目。
|
||||
- 根据 2026-07-10 14:23 联系人截图与老项目继续对比:搜索框本体已基本对齐,但下载按钮仍右偏约 8px,联系人分割线右端因 `share_contact_lv_line.xml` 内缩 65dp 明显短于老项目,且顶部“蓝牙”标题被隐藏。已恢复 `activity_main.xml` 顶部标题显示,搜索区宽度 614dp -> 606dp、输入框右边距 98dp -> 88dp,将下载按钮左移;`share_contact_lv_line.xml` 取消左右内缩,让分割线拉到右侧号码区域后方。
|
||||
- 根据 2026-07-10 14:33 联系人截图复核,恢复 `activity_main.xml` 顶部“蓝牙”标题后会压到左侧菜单“蓝牙拨号”,当前新项目主壳坐标体系不适合显示该全局标题;已将 `tv_main_title` 重新隐藏,保留联系人主体坐标、搜索框、下载按钮和列表分割线调整。
|
||||
- 根据 2026-07-10 15:05/15:06 截图复核,左侧系统栏/主界面可用宽度变化后,蓝牙主壳左侧菜单和页面容器整体右移,导致联系人页右移、拨号页右侧通话记录宽度被压窄,11 位号码重新显示省略号。已调整 `activity_main.xml`:顶部蓝线 start 从 48dp 改为 20dp,左侧菜单 start 从 36dp 改为 8dp;调整 `fragment_contacts.xml`:搜索区 start 50dp -> 38dp,列表 start 36dp -> 24dp;调整 `fragment_dial_home.xml`:右侧通话记录容器 start 42dp -> 24dp,以恢复联系人坐标并释放通话记录显示宽度。
|
||||
- 根据蓝牙设置页新旧截图和老项目 `fragment_blueforum.xml/listitem_forum.xml/ForumAdapter.java`,已对齐设置页设备列表 UI:补齐旧项目搜索按钮、列表背景、分割线、下拉箭头资源;已配对/可用设备 item 改为 64dp 行高、左侧 36dp 手机图标、26sp 设备名、24sp 状态文字和右侧箭头;移除列表行内直接显示的“断开”大按钮;可用设备标题固定显示“可用设备”,避免有设备列表时仍显示“请搜索可用设备!”。
|
||||
- 根据老项目 `蓝牙设置6.png` 继续对齐设置页设备列表细节:设备行左侧图标改用老项目 `drawable-sw600dp/icon_iphone.png` 小矩形手机图标;可用设备标题右侧补旧项目同款 32dp `ProgressBar`,扫描时显示在“可用设备”文字后,最右侧搜索按钮保持常显。
|
||||
- 根据老项目 `progressbar_bg.xml` 资源链路修正可用设备搜索菊花:旧项目动画引用 `@drawable/progress_loading_bar`,已复制旧项目 `drawable-sw600dp/progress_loading_bar.png` 到新项目并将新项目 `progressbar_bg.xml` 从 `@drawable/icon_loading` 改为 `@drawable/progress_loading_bar`。
|
||||
- 根据老项目 `fragment_setting.xml` 继续修正设置页前三行分割线:将新项目 `fragment_bt_setting.xml` 中外层主列表容器 `bt_layout` 从 `LinearLayoutCompat` 改回普通 `LinearLayout`,使 `android:showDividers="middle"` 和 `android:divider="@drawable/shape_my_divider"` 行为更贴近老项目。
|
||||
- 已按用户要求先提交当前蓝牙设置设备列表 UI 对齐版本,提交号 `31f1ac3e`,未推送;随后补齐已配对设备点击后的老项目式下拉 popup:箭头展开/收起、连接/断开、删除、点击外部关闭,并复用老项目 `180dp x 152dp` 深色弹层样式。
|
||||
- 根据 2026-07-10 用户提供的蓝牙音乐新旧截图,已按旧项目 `fragment_music.xml` 对齐蓝牙音乐 UI:主界面 `BtMusicFragment` 和独立 `BluetoothMusicActivity` 均改为旧项目圆形默认封面、歌名/歌手居中、旧项目图片进度条和底部三颗圆形上一首/播放暂停/下一首按钮;隐藏新项目音效扁按钮;封面行为按旧项目固定显示 `icon_bluetooth_music`,不再使用手机专辑图;播放控制逻辑继续复用新项目 `BtMusicViewModel`。
|
||||
- 根据 2026-07-10 19:49 蓝牙音乐实机截图与老项目效果图复核,二次修正主壳和音乐页坐标:恢复顶部“蓝牙”标题并按老项目 30sp 显示,顶部蓝线下移 10dp,左侧菜单按老项目 `activity_main.xml` 下移到 50dp,菜单横向回到 0dp;音乐封面/歌名/歌手下压 6dp,同时收窄歌手到进度条距离,保持进度条和底部三颗控制按钮位置贴近老项目。
|
||||
|
||||
## 验证结果
|
||||
|
||||
- 已使用 Android Studio JBR 临时 Java 17 环境编译通过:
|
||||
|
||||
```powershell
|
||||
$env:JAVA_HOME='C:\Program Files\Android\Android Studio\jbr'
|
||||
$env:Path="$env:JAVA_HOME\bin;$env:Path"
|
||||
.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain
|
||||
```
|
||||
|
||||
- 结果:`BUILD SUCCESSFUL`。
|
||||
- 注意:默认系统 Java 是 11,直接编译会失败;工程当前 AGP 8.8 需要 Java 17。
|
||||
|
||||
## 待办事项
|
||||
|
||||
1. 上车/模拟器实测四个入口切换是否和老项目 UI 效果一致。
|
||||
2. 对齐拨号页细节:号码显示、删除键、未连接提示、点击通话记录拨号。
|
||||
3. 对齐联系人页细节:搜索、重新同步、空状态、未授权状态。
|
||||
4. 对齐蓝牙音乐页细节:未连接状态、播放/暂停、上一首/下一首、歌曲信息刷新。
|
||||
5. 对齐蓝牙设置页细节:蓝牙开关、设备扫描、连接/断开/删除、蓝牙名称修改。
|
||||
6. 后续再专项处理通话页和三方通话验收。
|
||||
7. 蓝牙设置页下一轮继续补齐:已配对设备点击弹窗、可用设备连接确认弹窗、打开/关闭蓝牙进度提示。
|
||||
|
||||
## 更新记录
|
||||
|
||||
- 2026-07-27:结合 `tongli_2026_07_27_14_50_12.log` 与旧项目复核 HFP 重连后的通话记录交互。日志确认新平台自动 PBAP 同步正常,但重连后约 20 秒内会多次读取并展示旧数据库记录;旧项目行为是立即清空旧列表、在列表区域显示“正在下载通话记录”,同步完成后再展示最新记录。实现决策:不重复发起 PBAP 请求,仅在 HFP 重连路径等待本轮通话记录同步完成,并过滤完成前的旧记录回调。
|
||||
|
||||
- 2026-07-24:按用户要求备份后,读取并记录会话 `019f91d1-340f-72b2-8e3e-c94249a57be6`。新增“蓝牙深度静态分析基线”,固化该会话的架构结论、安全/PBAP/生命周期风险、历史提交归属和建议验证顺序;同时明确这些风险尚未经过本轮 Gradle、ADB 或实车复测。修改前备份为 `F:\zdCode\tongli-backup-20260724-115233\agent-before-session-019f91d1-record.md`,源文件与备份的 SHA-256 一致。本次仅修改 `agent.md`,未修改蓝牙业务源码。
|
||||
|
||||
- 2026-07-24:按用户要求且不备份,读取会话 `019f4080-a188-7aa0-98b6-92d3e5f10fb2` 的本地 JSONL 转录,并用当前应用代码、Git 提交和 Android 14 系统源码复核蓝牙音乐结论;新增“蓝牙音乐后续分析基线”,区分已落地修复、已撤回试验和后续日志判定方法。本次仅更新 `agent.md`,未修改蓝牙代码。
|
||||
|
||||
- 2026-07-22:根据 `tongli_2026_07_22_15_53_54.log` 确认连续 7 轮通讯录与通话记录同步均成功;重复点击时出现“通话记录正在同步中”,根因是联系人和通话记录共用 PBAP busy 状态,数据保存完成后仍有约 1~2 秒断开收尾。保留底层并发保护和现有 `100% -> 400ms -> 关闭` 交互,不提前清除 pulling 状态;移除 `BtContactsManager` 的固定 Toast,改由 `ContactsFragment` 在显示进度弹框前拦截重复点击并提示“通讯录同步尚未完成,请稍后”。Java 17 执行 `:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
|
||||
- 2026-07-22:根据 10:57~10:58 三轮实车日志复核,258 条联系人每轮均完整成功,PBAP 约 8.6~8.9 秒、本地解析约 7.3 秒、保存约 0.5 秒;保存完成后进度弹框均在约 400ms 后关闭,确认 100% 展示闭环生效。针对等待阶段 48dp 橙色菊花切换到 148dp 蓝色圆环造成的视觉跳变,改为全程复用同一个 148dp 蓝色圆环:PBAP 阶段显示不确定旋转弧,本地解析阶段原位平滑切换到真实百分比;提示语恢复老项目“正在下载通讯录列表,请稍等...”。使用 Java 17 执行 `:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
|
||||
- 2026-07-22:补齐通讯录下载完成与保存失败闭环。`DataContactsHelper.savePhoneBookList()` 改为返回 SQLCipher 事务结果,`BtContactsManager` 在保存失败时进入既有同步失败处理,不再继续上报 100% 和成功列表;成功时 `ContactListViewModel` 保留 100% 进度约 400ms 后再隐藏,`ContactsFragment` 不再因联系人列表先到而提前关闭弹框。使用 Java 17 执行 `:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
|
||||
- 2026-07-22:按老项目需求确认不需要真人联系人头像。旧项目 `PhoneBook` 仅保存姓名、拼音和号码,列表使用固定联系人图标;新项目联系人列表也未消费 `photoData`,通话页虽读取该字段但当前未绑定到头像控件。已从 `ContactUtils.getAllContacts()` 的逐联系人解析循环中移除 `PHOTO_URI/data15` 查询,避免有头像联系人额外执行 Provider 查询和读取 Blob;保留 `Contacts` 模型及数据库头像字段,避免 Schema 和接口变更。姓名、号码、拼音、T9、排序、去重逻辑不变。使用 Java 17 执行 `:YX_BlueTooth:assembleDebug` 编译通过。该改动减少应用本地解析耗时,但不能修复系统 Bluetooth vCard 阶段的 `Decode error on base64 photo`。
|
||||
|
||||
- 2026-07-21:根据 `tongli_2026_07_21_20_00_33.log` 优化联系人手动下载交互。日志确认 PBAP 远端传输约 8.6 秒、联系人本地解析约 7.4 秒、258 条写库约 0.58 秒,功能链路正常;原先 PBAP 无进度阶段显示 `0%` 会造成卡住错觉。现将手动下载与自动同步的 UI 状态分离:手动点击后立即显示弹框并保留已有联系人列表作为背景,不再先闪整页 loading;PBAP 传输阶段显示不确定进度圈,收到真实本地解析进度后才切换百分比圆环;成功、失败、切页和生命周期退出时清理手动弹框。自动连接同步仍使用联系人页内 loading,不弹手动下载弹框。使用 Java 17 执行 `:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
|
||||
- 2026-07-21:分析 `tongli_2026_07_21_19_47_53.log`,确认 6 次手动通讯录下载均完整成功,单轮约 21~27 秒;进度到 100% 后仍等待约 4~5 秒,根因是 `ContactUtils` 在解析结束即上报 100%,随后 `DataContactsHelper.savePhoneBookList()` 对 258 条联系人逐条独立提交 SQLCipher。首轮还因设置 PBAP priority 自动触发连接后再次显式 `connect()`,第二次返回 false 被误判为失败。已修改:联系人解析进度封顶 95%,数据库事务提交完成后上报 100%;联系人批量写库改为单事务;PBAP connect 返回 false 后若实际状态为 CONNECTING/CONNECTED 则继续等待,不再报失败;HFP 手机连接成功后重新安排未同步状态检查。`:YX_BlueTooth:assembleDebug` 编译通过。实车复测关注 `PBAP connect already in progress`、`contacts read completed`、`contacts save completed` 的时间差。
|
||||
|
||||
- 2026-07-21:分析 `tongli_2026_07_21_19_28_43.log`,确认联系人页长期停留“正在同步通讯录”属于假加载:日志中 `phoneBookPullRequest/MSG_DOWNLOAD/onContactsPullCompleted/download.failed` 均为 0,且 `isPhoneBookPulling=false`。根因是蓝牙应用进程启动时手机 HFP/A2DP 已连接,PBAP 仅回调 profile proxy 就绪,应用错过此前的 PBAP 连接广播;同时 `ContactsFragment` 将“未同步且未失败”无条件显示为加载态。已增加 PBAP proxy-ready 状态补查:已连接手机未同步且无任务时只恢复一次下载;联系人页仅在真实 pulling 状态显示加载,否则显示“通讯录未同步”。`:YX_BlueTooth:assembleDebug` 编译通过。实车复测应检查 `reconcile PBAP service ready`、`phoneBookPullRequest`、`MSG_DOWNLOAD`、`onContactsPullCompleted` 日志链路。
|
||||
|
||||
- 2026-07-09:创建蓝牙业务迁移长期索引,承接昨日截图和项目梳理上下文。
|
||||
- 2026-07-09:补充蓝牙开发实施方案,明确按“旧项目需求基线 + 新项目架构实现”推进。
|
||||
- 2026-07-09:本次需求“不备份”后执行;在 `YX_McuService` 启动阶段关闭 Android `show_touches` 与 `pointer_location` 系统开关,用于去掉屏幕触点显示和坐标显示。
|
||||
- 2026-07-09:用户补充“应该是调试模式,点击有触点”;已将处理方式升级为监听 `show_touches` / `pointer_location` 变化,调试模式重新打开后自动拉回关闭。
|
||||
- 2026-07-09:用户确认只专注蓝牙模块,不考虑 SystemUI 和其他模块;已完成 `YX_BlueTooth` 主界面四入口骨架并编译通过。
|
||||
- 2026-07-09:按老项目 `fragment_setting.xml` 结构改造新项目蓝牙设置主界面;`BtSettingViewModel` 补出自动接听接口封装;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据用户反馈“页面标题不对”,修正 `YX_BlueTooth` 标题资源:`app_name=蓝牙`、`keyboard=蓝牙拨号`、`contacts=联系人`,保留 `music=蓝牙音乐`、`setting=蓝牙设置`;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据截图反馈蓝牙设置页开关应参考老项目蓝色样式;从旧项目复制 `common_switch.xml/common_switch_on.png/common_switch_off.png`,设置页自动接听和蓝牙开关改为 `72dp x 40dp` 老项目开关;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据用户反馈左侧“蓝牙拨号/联系人/蓝牙音乐/蓝牙设置”选中后字体应为白色;确认新项目 Adapter 使用 `TextView.setSelected(true)`,而 `text_click_selector` 缺少 `state_selected`,已补选中态白色;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:参考老项目 `fragment_bluename.xml` 和截图,补齐新项目 `BtSettingFragment` 内部蓝牙名称编辑页;复制旧项目 `edit_bg_selector/edit_confirm_bg_selector` 及 360x64 输入框/按钮图片资源,新增确认保存、空名称提示、未开蓝牙提示、返回键回列表、切换菜单退出编辑页逻辑;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据用户反馈老项目蓝牙名称保存成功有绿色成功图标弹层;新增 `BluetoothResultToast` 和 `toast_result_layout.xml`,复制旧项目 `common_toast_bg.png/icon_success.png/ico_failed.png`,蓝牙名称保存成功/失败改为老项目样式结果提示;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据 MasterGo 与老项目 `fragment_setting.xml` 对照,蓝牙设置页自动接听、蓝牙开关、蓝牙名称三行改为老项目 88dp 行高和 30sp 标题;蓝牙名称值改为 20sp/#E6E6E6、右侧间距 64dp,并从老项目复制 `btn_arrow_right_selector.xml/arrow_left.png/arrow_left_pressed.png`;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据老项目蓝牙拨号页截图和 `fragment_keyboard_test.xml/listitem_phonerecord_new.xml` 对照,复制旧项目 `dial_keyboard/button_diag/keyboard_delete/icon_phone_*` 等资源,调整 `view_keyboard_dial.xml/fragment_dial_home.xml/listitem_phonerecord.xml` 和 `CallRecordAdapter`;保留原拨号/删除/长按 0 输入 +/点击通话记录拨号逻辑;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据用户提供的新旧拨号截图继续修正拨号主界面偏差;对齐老项目主壳坐标,修改 `activity_main.xml/listitem_main.xml/dimens.xml`,让左侧菜单、键盘、通话记录整体上移并使用老项目 172x60 选中项尺寸;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据用户明确要求“拨号盘 UI 按老项目”,继续修改 `view_keyboard_dial.xml/KeyBoardView.java/theme.xml/colors.xml`;去除数字键字母副标题、删除键常显、拨号盘文字/线色按老项目色值对齐;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据新旧拨号截图继续微调坐标;将拨号盘整体上移约 29dp,通话记录列表去掉顶部 28dp margin;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:用户确认“只改蓝牙模块”后,继续对齐老项目拨号页相对间距;调整左侧菜单、拨号盘和通话记录列表坐标,只修改 `YX_BlueTooth` 资源布局;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-09:根据用户反馈“通话列表电话号码显示不全”,修正 `listitem_phonerecord.xml/fragment_call_logs.xml/CallRecordAdapter.java/colors.xml`;去掉通话次数后缀、释放号码文字宽度并按老项目色值对齐;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:用户确认“不备份,直接改”后,根据新旧拨号截图继续微调拨号页坐标;只修改 `fragment_dial_home.xml` 与 `fragment_call_logs.xml`,拨号盘右移并上移,通话记录区域右移且列表下移;首次编译因不存在 `@dimen/dip_42` 失败,改用现有 `@dimen/dip_40` 后 `:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:用户确认“不备份,直接改”后,继续按改后截图微调右侧通话记录;`ll_dial_right` start 改为字面量 `42dp`,`lv_record` top 改为 `@dimen/dip_24`;首次编译因项目无 `@dimen/dip_42` 失败,改为字面量后 `:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:用户确认“不备份,直接改”后,根据 adb 截图继续修正通话记录号码显示宽度;将 `activity_main.xml` 中 `container` 右边距从 `@dimen/dip_38` 改为 `0dp`;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:用户确认“不备份,直接改”后,根据老项目输入号码截图和 `SeparatorEditText` 逻辑修正拨号输入交互;输入号码后右侧保持通话记录,不再显示“最佳适配”,号码显示增加老项目空格分隔;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:根据用户要求“第一步应用层先做体验兜底”,只改 `YX_BlueTooth`:通话记录点击防连点,拨号成功后立即显示“正在拨号”通话页,保留 HFP 真实回调刷新;`:YX_BlueTooth:assembleDebug` 编译通过。底层 `pcm_open Device or resource busy`、`AudioFlinger/audio-hal` 异常仍需系统/BSP 或蓝牙厂商继续分析。
|
||||
- 2026-07-10:根据用户提供的老/新联系人截图,继续只改 `YX_BlueTooth` 联系人页;复制旧项目联系人搜索、下载和联系人头像资源,调整 `fragment_contacts.xml/listitem_phonebook.xml/ContactsAdapter.java/ContactsSearchAdapter.java/ContactsFragment.java/MainActivity.java/colors.xml`,让联系人页按老项目固定搜索栏、下载按钮、姓名+号码列表显示;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:根据联系人页实机截图继续调坐标和提示文案;`fragment_contacts.xml` 搜索区/列表整体上移左移,`values/strings.xml` 与 `values-zh/strings.xml` 的 `contact_search_hint` 改为“请在联系人中搜索”;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:根据 14:17 联系人截图做像素级横向复核,微调搜索区宽度、下载按钮位置和联系人列表左边界;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:根据 14:23 联系人截图继续补齐顶部“蓝牙”标题、下载按钮横向偏移和联系人分割线长度;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:根据 14:33 联系人截图确认全局“蓝牙”标题会与左侧菜单重叠,已再次隐藏 `tv_main_title`;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:根据 15:05/15:06 截图修复 SystemUI/主壳宽度变化后导致的联系人页右移和拨号页通话记录号码省略;只改 `YX_BlueTooth` 的 `activity_main.xml/fragment_contacts.xml/fragment_dial_home.xml`;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:用户确认“不备份”后,对齐蓝牙设置页设备列表 UI;只改 `YX_BlueTooth` 的设置页布局、设备列表 Adapter 和旧项目列表资源;`:YX_BlueTooth:assembleDebug` 编译通过。当前只完成 UI 对齐,已配对设备点击弹出“连接/断开/删除”操作层仍作为下一步。
|
||||
- 2026-07-10:用户确认“不备份,直接改”后,继续按老项目 `蓝牙设置6.png` 修正设置页设备列表图标和搜索中进度圈;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:用户确认“不备份”后,修正可用设备搜索菊花资源为老项目橙色 `progress_loading_bar.png`;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:用户确认“不备份”后,按老项目方式将设置页主列表容器改回普通 `LinearLayout`,用于恢复自动接听/蓝牙开关/蓝牙名称下方分割线;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:按用户要求先提交当前代码但不推送,提交 `31f1ac3e feat(YX_BlueTooth): 对齐蓝牙设置设备列表UI`;提交后新增已配对设备下拉 popup,参考旧项目 `popup_window.xml/ForumAdapter/MainActivity.popUpWindow`,`:YX_BlueTooth:assembleDebug` 编译通过。popup 改动暂未提交。
|
||||
- 2026-07-10:用户确认“这个项目都不备份”后,对齐蓝牙音乐页 UI;复制旧项目 `icon_bluetooth_music`、`music_wg_*` 和 `icon_seekbar_horizontal_*` 资源,修改 `fragment_bt_music.xml/activity_bluetooth_music.xml/selector_music_*.xml/bt_music_progress_layer.xml/BtMusicViewModel.java/BtMusicFragment.java/BluetoothMusicActivity.java`;封面按旧项目固定显示默认蓝牙音乐图;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:根据用户 19:49 新截图继续对齐蓝牙音乐与老项目效果;修改 `activity_main.xml/fragment_bt_music.xml/activity_bluetooth_music.xml`,恢复顶部标题、下移菜单和音乐封面文字;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-10:用户确认顶部“蓝牙”标题属于顶部栏/主壳显示,不作为当前蓝牙模块功能处理;已将 `activity_main.xml` 内 `tv_main_title` 重新隐藏,后续若要显示应在主壳/顶部栏范围单独处理。
|
||||
- 2026-07-13:按当前代码重新梳理蓝牙音乐模块功能、入口、A2DP/AVRCP/音频焦点/状态回调流程,并输出文档到 `F:\AI\Obsidian\项目文档\同力\蓝牙音乐\蓝牙音乐模块功能流程梳理_2026-07-13.md`。
|
||||
- 2026-07-13:用户确认“不备份”后,重排蓝牙音乐模块文档中的 Mermaid 流程图;将大图拆为 17 个 3-6 节点的小图,并用表格承接完整类名、分支规则和代码定位;已检查 Markdown 围栏成对。
|
||||
- 2026-07-13:用户确认“不备份”后,结合 `xugong_2026_07_13_11_01_55.log` 分析蓝牙音乐播放后自动暂停。日志确认 App 下发 AVRCP PLAY(68)并成功获得音频焦点后,`com.android.bluetooth` 很快主动下发 AVRCP PAUSE(70);当前工程 `BluetoothA2dpSinkClient.setFluorideStreaming()` 内真正通知 A2DP Sink 焦点状态的调用被注释,导致 App 焦点与蓝牙 Sink 焦点状态脱节。`F:\zdCode\xugong`、`seaz`、`weidu` 同类工程均启用了该调用。另发现无焦点播放回调会在 `BtMusicManager` 中重复触发暂停,建议作为防重保护一并处理。本次仅分析,未修改业务代码。
|
||||
- 2026-07-13:用户要求修改蓝牙音乐自动暂停问题并沿用“不备份”。已在 `BtMusicManager` 增加失焦暂停防重,同一轮 PLAYING 回调只下发一次暂停。Android 14 编译和运行框架均确认 `BluetoothA2dpSink.setFluorideStreaming(int)` 已不存在,不能恢复旧接口;实机分别验证移除全局 MediaSession、手动写入 `SOURCE_BT_MUSIC` 均无法阻止自动暂停,相关试验已撤回。反编译设备 Bluetooth APK 确认:`AvrcpControllerStateMachine` 在 PLAYING 回调中读取 A2DP Sink 焦点状态,状态无效时主动发送消息 302/AVRCP PAUSE(70);同时当前 Bluetooth APK 的 `A2dpSinkService.requestAudioFocus()` 与 `A2dpSinkStreamHandler.handleMessage()` 已为空实现,且无可运行时开启的 feature flag。根治需要在 Android 14 系统 `com.android.bluetooth` 模块恢复 A2DP Sink 焦点状态处理,或在该能力关闭时取消 AVRCP Controller 的强制暂停,应用层无法完整修复。
|
||||
- 2026-07-13:用户提供 Android 14 源码 `Y:\tongli\android14.0_rk3562` 并继续沿用“不备份”。已恢复系统 Bluetooth 的 `A2dpSinkService.requestAudioFocus()` 转发、A2DP 音频流开始/停止事件,以及 `A2dpSinkStreamHandler.handleMessage()` 焦点状态机;应用侧将蓝牙音乐焦点请求改为通过 AVRCP `MediaController.prepare()` 委托 `com.android.bluetooth` 获取真实 A2DP Sink 焦点,删除 PLAYING 回调按应用焦点主动暂停手机的逻辑,并停止 `com.yaxon.bluetooth` 直接调用 `AudioManager.requestAudioFocus()`。`:YX_BlueTooth:assembleDebug` 编译成功;系统 Bluetooth 需在 Linux Android 源码构建环境编译并上车验证。
|
||||
- 2026-07-13:分析 `xugong_2026_07_13_14_08_42.log`,确认新 `com.yaxon.bluetooth` 应用代码已生效并发出 AVRCP `prepare()`,但设备激活的仍是 `/system/apex/com.android.btservices.apex` 出厂 APEX,日志没有 `A2dpSinkStreamHandler` 焦点处理及 `com.android.bluetooth` 的实际 `requestAudioFocus()`,因此系统修复尚未上车。另确认系统预装的独立旧应用 `com.yaxon.btmusic`(`/system/app/YX_BtMusic/YX_BtMusic.apk`)仍在运行,其旧 SDK 在 PLAYING 且应用焦点为 false 时再次调用 `pauseBtMusic()`;同一轮播放先由旧系统 Bluetooth 下发一次 PAUSE(70),再由旧独立应用下发一次 PAUSE(70)。产品当前只有 `YX_BlueTooth` 内一个蓝牙音乐入口,应从 `third_apps/preinstall.mk` 移除 `PRODUCT_PACKAGES += YX_BtMusic`,当前设备可先禁用该包验证,同时必须编译并刷入新的 `com.android.btservices` APEX 后重启。
|
||||
- 2026-07-13:分析 `xugong_2026_07_13_14_22_51.log`,确认旧 `com.yaxon.btmusic` 已不再运行,第二次应用侧 PAUSE 已消失;当前只剩 `com.android.bluetooth` 在手机回报 PLAYING 后发送 PAUSE(70)。ADB 确认设备系统指纹仍为 `eng.znzc.20260708.173717`、构建日期为 2026-07-08,激活的仍是 `/system/apex/com.android.btservices.apex`,日志仍无 `A2dpSinkStreamHandler`、`com.android.bluetooth requestAudioFocus()` 和 `onAudioFocusStateChanged(1)`,因此 7 月 13 日系统 Bluetooth 修复仍未刷入设备,本轮不能用于判断系统修复是否有效。
|
||||
- 2026-07-13:分析 `xugong_2026_07_13_14_26_43.log`,确认设备已完整重启,但实时 ADB 仍显示系统指纹 `eng.znzc.20260708.173717`、构建日期 `Wed Jul 8 17:37:08 CST 2026`,`com.android.bluetooth` 仍来自 `/apex/com.android.btservices/app/Bluetooth@UQ1A.240205.004.B1/Bluetooth.apk`。旧独立 `com.yaxon.btmusic` 已无法正常解析/启动,不再参与暂停;本轮仍是 `BtMusicManager.prepareA2dpSinkAudio` 后发 `PLAY(68)`,A2DP Stream started,手机回报 `Playback status changed to 3`,随后 `com.android.bluetooth` 立即发 `PAUSE(70)`。日志仍无 `A2dpSinkStreamHandler`、`callingPack=com.android.bluetooth`、`onAudioFocusStateChanged(1)`,说明系统 Bluetooth A2DP Sink 焦点修复仍未上车。
|
||||
- 2026-07-14:用户说明正常播放分支为 `Y:\txj\android14.0_rk3562`,疑似修复提交 `926b121d63500ff2f7f9c6305d106e45c8fc1f68`。已按“不备份”同步该提交到 `Y:\tongli\android14.0_rk3562`:仅修改 `packages/modules/Bluetooth/android/app/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerStateMachine.java`,在 `MESSAGE_PROCESS_PLAY_STATUS_CHANGED` 中三处原本会触发 `PASS_THRU_CMD_ID_PAUSE` 的保护分支改为只打印 `WL...` 日志,不再发送 AVRCP PAUSE。目标文件 diff 已确认与 `txj` 提交一致,待 Linux Android 构建环境编译 `com.android.btservices`/刷机验证。
|
||||
- 2026-07-14:对比 `Y:\txj\android14.0_rk3562` 与 `Y:\tongli\android14.0_rk3562` 的 `packages/modules/Bluetooth` 提交历史。两边已有三组内容一致的提交:`屏蔽原生蓝牙音乐焦点注册处理`、`添加诚谦libbluetooth_jni so库集成`、`修改通讯录通话记录下载失败问题`。`txj` 额外多两项:`926b121d635` 蓝牙播放自动暂停修复(已同步到 `tongli`,验证可解决播放即暂停)和 `5528a8da056` 蓝牙搜索设备时额外向 `com.yaxon.launcher` 发送 `ACTION_FOUND` 广播(尚未同步,属于桌面设备发现联动,不影响蓝牙音乐播放)。
|
||||
- 2026-07-14:结合 `xugong_2026_07_14_13_53_10.log` 分析蓝牙音乐进度条不变化。日志显示 AVRCP 播放位置每秒上报并进入 `BtMusicManager`,但 `PlaybackStateCompat.getExtras()` 多数为 null,旧代码只有 `bundle != null` 才写回 `mMusicInfoBean.playPosition`,导致 UI 收到的 `MusicInfo.playPosition` 不更新。已按用户确认“不备份”修改 `YX_BluetoothSDK/modules/BtMusicManager.java`:`playPosition >= 0` 时直接写入;同时屏蔽 PLAYING 回调里因应用音频焦点为 false 而主动 `pauseBtMusic()` 的旧逻辑,只保留日志,避免再次中断远端播放。
|
||||
- 2026-07-14:用户已提交“进度条变化问题”并还原直接屏蔽失焦暂停逻辑;继续按“不备份”优化 `BtMusicManager` 的失焦暂停策略。新逻辑在 `isPlaying && !isFocus` 时先判断蓝牙音乐页前台、当前音源为 `SOURCE_BT_MUSIC` 或本机 3 秒内刚下发 PLAY,满足则请求/等待焦点不暂停;否则只安排 1200ms 延迟检查,二次确认仍播放且无焦点且非蓝牙音乐场景时才 `setA2dpMute()` + `pauseBtMusic()`。同时 A2DP 断开和模块销毁时取消延迟任务,避免残留回调。
|
||||
- 2026-07-14:根据最新日志复核,进度条修复已经生效,剩余 `PAUSE(70)` 来自应用主动 `pauseBtMusic()`,且播放期间应用焦点一直为 true,失焦延迟暂停策略本轮未触发。用户担心策略改动过大后确认改为保守排查:已撤回失焦延迟暂停策略,只在 `BtMusicManager.pauseBtMusic()` 增加调用栈日志,用下一份日志定位 UI/按键/语音/外部控制中的具体调用来源。
|
||||
- 2026-07-14:用户确认“不备份”后,处理 `YX_Setting` 移除 `YX_BluetoothSDK` 依赖后的编译错误,并按“蓝牙模块独立,设置里不体现蓝牙”清理设置模块内蓝牙入口。已移除 `SettingApplication` 蓝牙 SDK 初始化/监听、连接页蓝牙开关和弹窗入口、蓝牙设置 ViewModel/Fragment/Adapter/配对浮层及相关布局、字符串、常量和未引用图标;`rg` 确认 `YX_Setting/src/main` 已无 `com.yaxon.bluetoothsdk`、`BluetoothDialogFragment`、`title_bluetooth` 等残留;使用 Java 17 执行 `.\gradlew.bat :YX_Setting:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-14:用户澄清 `com.yaxon.btmusic` 当前只是保留代码,APK 已不集成。按长期边界继续处理:不恢复 `YX_Setting` 蓝牙依赖,不依赖 `YX_BtMusic` 旧独立应用;已增强 `YX_BlueTooth` 自启动/常驻职责,`application`、`BluetoothService`、`BootReceiver` 支持 direct boot,`BootReceiver` 监听 `LOCKED_BOOT_COMPLETED` 和 `BOOT_COMPLETED` 拉起 `BluetoothService`,`BluetoothService.onStartCommand()` 默认返回 `START_STICKY`。使用 Java 17 执行 `.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-14:用户已删除 `third_apps/preinstall.mk` 中的 `PRODUCT_PACKAGES += YX_BtMusic`,本地核对确认预装清单不再集成 `YX_BtMusic`;`settings.gradle` 中 `include ':YX_BtMusic'` 仍为注释。当前仍残留 `YX_Launcher`、`YX_Music`、`YX_Radio`、`YX_Factory`、`YX_BlueTooth/utils/ItsBluetoothApp.java` 等旧入口对 `com.yaxon.btmusic` 的引用,后续若这些入口仍可见,应迁移到 `com.yaxon.bluetooth` 对应入口或隐藏。
|
||||
- 2026-07-14:继续清理 release 打包脚本中的旧独立 APK 构建项,已从 `build_fd_apk.sh` 和 `build_znzc_apk.sh` 移除主动执行的 `gradle :YX_BtMusic:assembleRelease`;注释掉的历史命令保留作为历史线索,不参与执行。
|
||||
- 2026-07-14:用户确认“参考之前设置的蓝牙连接功能,挪到蓝牙模块”。对比 `a9c0c3f2` 旧 `YX_Setting` 实现后确认 `YX_BlueTooth` 已承接扫描、设备列表、连接、断开、删除、蓝牙开关和名称设置;本轮补齐旧 `SettingApplication + BluetoothPairedRequestView` 承担的系统配对请求确认能力:新增 `YX_BlueTooth/views/BluetoothPairedRequestView.java`、`view_paired_request_layout.xml` 及配套按钮/背景资源,并在 `BluetoothService.onPairingRequest/onPairingBondStatus/onBtClose` 中显示或关闭 PIN 配对确认层。使用 Java 17 执行 `.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-15:用户确认采用保守启动策略,撤回 `YX_BlueTooth` 中新增的 direct boot 相关项:移除 `application/service/receiver` 的 `android:directBootAware="true"`,移除 `LOCKED_BOOT_COMPLETED` 监听和 `BootReceiver` 对应分支。最终保留 `android:persistent="true"`、普通 `BOOT_COMPLETED`、`BluetoothService START_STICKY` 和系统浮层权限,贴近原 `YX_Setting` 的蓝牙宿主行为。使用 Java 17 执行 `.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-15:用户确认“不备份”后,按老项目规则给 `YX_BlueTooth` 主界面四入口补菜单门禁:`SettingViewModel` 聚合蓝牙开关/HFP/A2DP 状态,拨号和联系人依赖 HFP,蓝牙音乐先依赖 A2DP,设置始终可用;`MenuAdapter` 支持入口灰显和不可点击;`MainActivity` 对入口点击和外部 `setPositionFragment()` 做二次拦截,协议断开导致当前页不可用时自动跳蓝牙设置。使用 Java 17 执行 `.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-15:结合 `tongli_2026_07_15_19_38_11.log` 分析菜单灰显不同步问题:断开时 A2DP 先从 connected 进入 disconnected,HFP 后面才断;连接时 A2DP 先 connected,HFP 再 connected,AVRCP 更晚,因此“蓝牙音乐”和“蓝牙拨号/联系人”先后变灰/恢复是底层 profile 事件时序暴露到 UI。已在 `SettingViewModel` 增加 450ms 菜单状态合并刷新,协议瞬时事件先收敛后统一通知菜单,同时保留拨号/联系人依赖 HFP、音乐依赖 A2DP 的旧项目门禁规则;`:YX_BlueTooth:assembleDebug` 编译通过,待实车复测断开/连接时左侧四入口视觉是否同步。
|
||||
- 2026-07-15:用户确认最终业务逻辑按老项目。复核旧项目 `MenuListAdapter/MainActivity/BtAdapterManager` 后,菜单门禁基线更新为:蓝牙拨号和联系人依赖 HFP connected,蓝牙音乐依赖 A2DP connected 且 AVRCP connected,蓝牙设置始终可点;蓝牙打开但手机未连接时自动落蓝牙设置页,蓝牙开关保持真实打开状态并保留设备列表/搜索。已在 `YX_BluetoothSDK` 的 `BtSettingManager/BtSettingControlManager` 暴露 `isAvrcpConnected()`,`SettingViewModel` 的音乐入口门禁改为 `isBtOpen && isA2dpConnected() && isAvrcpConnected()`,并继续保留 450ms 合并刷新;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-15:根据老项目断开连接截图继续对齐左侧菜单 UI。旧项目 `MenuListAdapter` 默认将菜单文字置为 `white_10=#1AFFFFFF`,满足协议或设置项时才改为 `gray_low=#DFDFDF`;断开时“蓝牙设置”主要是亮字,不显示明显选中胶囊。新项目已新增 `white_10`,禁用文字改为 `#1AFFFFFF`,`MenuAdapter` 手动按可用状态设置文字颜色;当蓝牙断开且当前落在“蓝牙设置”时,取消该项 selected 背景,仅保留亮字。`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-15:结合 `tongli_2026_07_15_20_23_24.log` 复核“打开蓝牙时蓝牙音乐比蓝牙拨号/联系人慢几秒”。日志显示 HFP 在 20:23:37.098 connected,A2DP 在 20:23:37.775 connected,AVRCP 在 20:23:39.577 connected,菜单到 20:23:40.102 才刷新为 `avrcp=true`;旧项目 `MenuListAdapter.isEnabled()` 同样要求音乐入口 `A2DP + AVRCP`,电话/联系人只要求 HFP。因此音乐入口晚亮是旧项目规则和底层协议连接时序导致,不作为新项目回归问题处理。
|
||||
- 2026-07-16:结合 `tongli_2026_07_16_09_40_45.log` 分析“蓝牙已连接但蓝牙音乐入口置灰”。日志显示 09:33:30 菜单先刷新为 `hfp=true a2dp=true avrcp=false`,09:33:31 `BluetoothAvrcpClient` 才 `avrcp onServiceConnected=12`,09:33:32 后已能收到 AVRCP 音乐元数据/播放状态,但菜单没有再次刷新。根因是 AVRCP profile proxy 接入后没有通知设置模块刷新菜单。已按 A2DP service-ready 设计补齐:`BluetoothAvrcpClient` 新增 AVRCP service connected listener,`BtSettingManager` 转发到 `IBtSettingModuleListener.onAvrcpServiceConnected()`,`SettingViewModel` 收到后 `scheduleRefreshMenuState()`;其他监听实现补空方法。保留老项目音乐入口 `A2DP + AVRCP` 规则,`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-16:结合 `tongli_2026_07_16_10_57_37.log` 分析源切换浮层反复弹出,确认 MCU `0x15/mode` 被映射为 `KEYCODE_ASSIST` 后触发 `SystemUIService.startAssist()`,进而连续发送 `android.intent.action.CHANGE_SW_MUSIC`。按用户要求先临时屏蔽弹框:`YX_Factory` 的 `BootReceiver` 收到 `ACTION_CHANGE_SW_MUSIC` 时只记录 ignored,不再调用 `SwitchAudioManager.switchAudioSource()`;后续正式修复应考虑 MCU 按键防抖和修正收音机/蓝牙音乐目标 Activity。
|
||||
- 2026-07-17:根据 `tongli_2026_07_16_20_01_29.log` 复核“自动跳转蓝牙拨号页后不显示通话记录”。旧项目 HFP 连接后会 `setPositionFragment(TAB_KEYBOARD, HFP_PROFILE)` 并调用 `mKeyBoradFragment.clear(); mKeyBoradFragment.loadCallrecords()`,其中 `loadCallrecords()` 会 `start(true)` 强制加载通话记录。新项目此前只在 HFP false->true 时跳转拨号页,`CallLogsFragment` 又被 `isCallRecordUpdated=false` 拦在同步提示面板。已在 `YX_BlueTooth` 中补齐 HFP 连接后的通话记录加载链路:`MainActivity.switchToDialOnHfpConnected()` 跳拨号页后通知 `DialHomeFragment`,再转发到 `CallLogsFragment.loadCallRecordsOnHfpConnected()`;有本地记录则读取,没有且未同步中则触发 `phoneBookPullRequest()` 并显示 loading。使用 Java 17 执行 `.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-17:根据 `tongli_2026_07_17_11_05_34.log` 复核实车效果“通话记录列表只显示几个号码”。日志显示 `notifyPhoneRecordList list = 3`,第一次 HFP 自动跳转时 `CallLogsFragment` 子页面尚未初始化,因此强制加载入口未执行;第二次入口执行时 `isCallRecordUpdated=true`,旧实现只读本地 3 条并返回,没有像老项目 `start(true)` 那样重新拉取。已修正:`DialHomeFragment` 增加 pending+重试,子 Fragment 未 ready 时最多重试约 2 秒;`CallLogsFragment.loadCallRecordsOnHfpConnected()` 不再被 `isCallRecordUpdated=true` 短路,HFP 连接后始终优先触发 `phoneBookPullRequest()` 重新 PBAP 同步,发起失败才回退读本地或显示同步提示。`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-17 更新标记:用户明确“通话记录逻辑还是要用新项目,只是交互参考老项目”。因此撤回上一条中的“连接后始终强制 `phoneBookPullRequest()`”方向。最终口径改为:HFP 连接后自动跳拨号页,并在 `DialHomeFragment` 子页面 ready 后延迟触发一次 `CallLogsFragment.loadCallRecordsOnHfpConnected()`;该方法仍遵循新项目规则,`isCallRecordUpdated=true` 时只读本地记录并经过 `handleMergeList()` 合并显示,`false` 时显示新项目原有同步面板/加载态,不主动强制 PBAP 同步。`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-17 更新标记:用户确认“这显示估计要参考老项目”。继续保留新项目 PBAP/数据库/读取状态逻辑,但通话记录列表展示口径改按老项目:不再经过 `handleMergeList()` 合并连续同号码同类型记录,SDK 返回多少条就交给列表展示;新项目 Adapter 原有最多显示 50 条限制暂时保留。`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-17:用户确认老项目连接后跳蓝牙拨号页直接显示“正在同步通讯录/通话记录”,没有新项目式“通讯录未同步/一键同步”大按钮;已将拨号页和联系人页未同步态改为 loading,隐藏一键同步按钮,loading 圈按老项目缩小到拨号 35dp、联系人 32dp,文字降到 20sp;`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-17:继续收口同步失败体验。拨号页 loading 文案改为“正在同步通话记录”;通话记录同步失败后改为停止 loading 并显示老项目式 `callrecord_get_failed` 文本;联系人同步失败去掉 `同步失败` toast,改为停止 loading 并显示 `contact_get_failed` 提示;`CallHistoryViewModel` 补齐 PBAP 失败回调到通话记录页面;`BluetoothPbapClient.connectPbap()` 将 PBAP `STATE_CONNECTING/STATE_CONNECTED` 视为成功,避免重复连接时误判失败。使用 Java 17 执行 `.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-17:对齐老项目拨号/联系人主页面成功提示逻辑:联系人同步成功后只刷新列表,不再弹 `同步成功` toast;拨号页本身也不增加成功 toast。`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-17:根据 `tongli_2026_07_17_19_44_50.log` 复核,PBAP 同步会出现早到的 `download.failed` 或重复连接失败信号,但 10-60 秒内通话记录仍可能陆续返回。旧项目不会一开始显示 `callrecord_get_failed`,而是 loading 后约 60 秒超时才失败。已将 `CallLogsFragment` 的通话记录失败态改为延迟 60 秒确认,期间继续显示“正在同步通话记录”,一旦记录返回即取消失败提示;同时将最终失败文案改为“通话记录同步失败,请检查手机蓝牙权限或稍后重试”,避免误判为一定未授权。`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-17:根据 `tongli_2026_07_17_19_56_00.log` 分析“设置页提示已连接后几秒才跳蓝牙拨号页”。日志显示 A2DP/设备列表在 19:57:33 已显示连接,HFP 广播 19:57:36 到达,但原逻辑等 `SettingViewModel` 菜单状态合并刷新和主线程空闲后,19:57:40 才执行 `HFP connected, switch to dial page`。旧项目 HFP connected 事件会直接 `setPositionFragment(TAB_KEYBOARD, HFP_PROFILE)`。已新增 `SettingViewModel.mHfpConnectedLiveData`,在手机 HFP `STATE_CONNECTED` 到达时即时通知 `MainActivity`;主界面收到后直接切拨号页并触发 `loadCallRecordsOnHfpConnected()`,菜单灰显仍保留 450ms 合并刷新,音乐入口仍按 A2DP+AVRCP 判断。`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-17:根据 `tongli_2026_07_17_20_18_07.log` 继续复核重连概率延迟跳转。旧项目 HFP 连接事件不受 `lastMainSelectedItem`/菜单刷新影响,直接跳 `TAB_KEYBOARD` 并 `loadCallrecords()`。新项目上一版 `force=true` 仍被 `!mWasHfpConnected` 限制,且 `SettingViewModel.onConnect()` 会立即触发通讯录读取。已修正 `MainActivity.switchToDialOnHfpConnected()`:HFP 事件 force 路径在当前未初始化/仍停蓝牙设置页/此前未连接时立即跳拨号页;已将 `SettingViewModel` 连接后的通讯录读取延后 1200ms 且去重,断开/关闭/释放时取消,避免抢在跳转前触发重复读取。`:YX_BlueTooth:assembleDebug` 编译通过。
|
||||
- 2026-07-18:根据 `tongli_2026_07_18_14_30_05.log` 继续分析“提示蓝牙已连接后约 3 秒才跳蓝牙拨号页”。日志显示 `SettingViewModel.notifyHfpConnected` 已到达,但 `MainActivity` 仍受 force 条件、通用 `onConnect` 提前隐藏面板/触发读取、以及重复 `SettingViewModel onConnect` 监听回调影响。已改为:`MainActivity.handleHfpConnected()` 收到 HFP connected 后直接强制切到 `TAB_KEYBOARD` 并触发 `DialHomeFragment.loadCallRecordsOnHfpConnected()`,仅保留 1500ms 重复事件防抖;`SettingViewModel.onConnect()` 不再向主界面发送“已连接”和读取通讯录,真正 HFP connected 时才发送 `mConnectedStateLiveData(true)` 并延后 1200ms 读取联系人;`SettingViewModel` 构造时主动移除上一个主界面同类实例的 setting/phone/contacts 三类监听,避免旧实例重复回调放大主线程压力,同时不改变 SDK 全局多监听语义。使用 Java 17 执行 `.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-21:按新旧联系人截图像素对比调整 `fragment_contacts.xml`,搜索区、结果提示、联系人列表和搜索结果列表统一右移 `9dp`、下移 `22dp`;联系人 item 的 `88dp` 行高、`60dp` 头像、姓名/号码字号和右侧对齐保持不变。使用 Java 17 执行 `.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain` 编译通过。
|
||||
- 2026-07-21:用户确认“不备份”后,补齐联系人页顶部下载按钮的旧项目式二次确认。新增 `ContactDownloadConfirmDialog` 和 `640x420dp` 弹框布局,直接复用旧项目弹框背景及按钮图片;已有联系人时显示“提示信息 / 是否要重新下载联系人? / 确定 / 取消”,确定后继续调用新项目既有 `reLoadContact()`,取消只关闭弹框,无联系人时保持直接同步。弹框按宿主窗口可见区域动态居中,并在页面隐藏、暂停或销毁时关闭。`:YX_BlueTooth:assembleDebug` 编译通过,已安装到 `1024x600/160dpi` 实机验证弹框显示和取消行为。
|
||||
- 2026-07-21:继续沿用“不备份”口径,补齐联系人同步进度弹框。PBAP 传输阶段显示旧项目式 `640x420dp` 等待弹框和 `0%`,系统联系人数据就绪后,由 `ContactUtils` 按 `Cursor` 解析条数上报真实百分比;进度经 `BtContactsManager -> IBtContactsModuleListener -> ContactListViewModel -> ContactsFragment` 到达 UI,并按百分比变化节流。成功、失败、蓝牙断开、切页和页面销毁时关闭弹框,页面恢复时可继续显示当前进度;SDK 使用同步代次隔离旧解析任务,断开、关闭或失败后旧任务不能重新弹出进度,线程池内部解析异常也会进入失败收口。该百分比仅表示本地联系人解析进度,不代表 PBAP 空中传输进度;未使用刘汽项目的 `SystemProperties` 全局总数方案,也未伪造计时进度。
|
||||
- 2026-07-21:实车发现联系人进度到 `100%` 后弹框未关闭。复核旧项目确认关闭点不在 `REFRESH_PROGRESS`,而在联系人结果回调 `PhoneBookFragment.showResults()`;新项目已对应调整为 `ContactListViewModel.onContactsPhoneBook()` 收到最终列表后立即隐藏进度弹框,`onContactsLoadCompeted()` 保留为 PBAP 断开完成兜底。
|
||||
- 2026-07-21:根据 `tongli_2026_07_21_18_52_50.log` 修复第二次下载联系人误报“未授予下载权限”。日志确认第一次已解析并保存 258 条联系人,但 PBAP 断开后系统残留插入任务与应用新请求重叠,产生 `socket closed`、`OBEX request is already being performed` 和共用的 `bluetooth.pbapclient.download.failed`;该广播不等于权限被拒绝。`BtContactsManager` 现会在 PBAP 刚断开时等待约 1.5 秒再重连,新建 PBAP 会话使用当前平台自动下载,不再额外发送一次 `ACTION_USER_UNLOCKED`;等待重连期间忽略上一会话迟到的完成/失败广播,当前联系人完成广播收到后忽略同批通话记录失败和重复失败,解析异常仍按真实失败处理。蓝牙/手机断开时取消待执行重连。使用 Java 17 执行 `:YX_BlueTooth:assembleDebug` 编译通过,待实车连续点击两次下载验证。
|
||||
- 2026-07-21:继续修复“通讯录一直重复下载”。根因补充为:当前平台在 PBAP `STATE_CONNECTED` 后自动执行 `MSG_DOWNLOAD`,旧应用的自动同步分支在已有联系人时仍调用 `phoneBookPullRequest()`,导致同一连接再发送 `ACTION_USER_UNLOCKED` 并排队第二轮下载。现已移除所有 PBAP 连接回调中的手动下载请求;非用户发起的新连接只调用 `beginPlatformPhoneBookPull()` 初始化完成标志、同步代次和 UI 进度,不清空既有联系人、不发送广播。仅保留“PBAP 已稳定连接且用户主动重新下载”路径发送一次广播。`:YX_BlueTooth:assembleDebug` 编译通过;实车验收要求每个 PBAP 会话只出现一次 `MSG_DOWNLOAD`。
|
||||
- 2026-07-22:提交前复核通讯录下载异常闭环后继续按“不备份”修改。联系人数据库更新改为 SQLCipher 单事务执行“按 MAC 删除旧数据 + 批量插入新数据”,任一步失败均回滚;PBAP 下载增加分阶段超时,连接/手机传输阶段 60 秒、收到联系人完成广播后的本地解析与保存阶段 120 秒,超时会作废当前同步代次、复位 pulling/恢复标志并进入既有失败状态;成功、失败和超时通过原子终态标志互斥,避免事务提交与超时竞态导致先失败后成功;`ContactListViewModel` 使用独立完成标志保证 100% 保留约 400ms,不再被快速 PBAP 断开回调提前关闭。Java 17 执行 `:YX_BlueTooth:assembleDebug` 编译通过,待实车验证正常下载、连续下载、超时和断连场景。
|
||||
- 2026-07-22 更新标记:用户认为原有提示时机更合理,撤回联系人页提前拦截 PBAP busy 以及新增的“通讯录同步尚未完成,请稍后”提示,恢复 `BtContactsManager.phoneBookPullRequest()` 在同步中收到重复请求时提示原有“通话记录正在同步中”并返回失败;进度弹框、PBAP 超时、解析进度和数据库事务修复均保留。
|
||||
- 2026-07-23:重新审查通讯录下载代码后按“不备份”修复异常闭环。PBAP 开始同步时只清理系统临时 Contacts/CallLog 数据,不再提前删除应用数据库中最后一次有效联系人和通话记录;同步失败时 ViewModel/Fragment 保留并继续显示旧联系人,首次同步无缓存时仍显示失败态。迟到的联系人完成广播若发现 PBAP 已断开,会取消处理超时、结束当前代次并关闭 loading,避免 120 秒后再次误报失败。`BtContactsManager` 联系人监听集合改为 `CopyOnWriteArrayList`,避免后台完成回调与页面销毁移除监听并发冲突;`BluetoothPbapClient.isPhoneBookPulling` 改为 `volatile`,保证主线程与后台线程状态可见。
|
||||
- 2026-07-23:继续按“不备份”收口 PBAP 同步一致性。联系人和通话记录均改为按 MAC 单事务替换,保存失败回滚并保留旧数据;联系人解析改用局部列表,页面回读不再清空下载线程数据,过滤无号码/异常联系人并完整关闭 Cursor;有效空通讯录通过按 MAC 同步标记识别,旧数据场景遇到空 Provider 结果会短暂重试。PBAP 完成/失败广播仅在活动同步且设备匹配时分发,联系人/通话记录完成标志改为 `volatile`;通话记录缺少完成广播、保存失败或断线时由 60 秒传输超时/120 秒本地处理超时释放 busy 并断开 PBAP,成功也主动清除 pulling,不再完全依赖断开广播。Java 17 执行 `:YX_BlueTooth:assembleDebug` 编译通过;仍需实车覆盖正常、空通讯录、通话记录失败、断线和连续下载场景。
|
||||
- 2026-07-23:提交前再次复核后继续修复 PBAP 异常路径。联系人同步终态改为独立于全局 `pulling` 的原子状态,联系人完成广播前遇到 PBAP 断开会立即失败,不再依赖联系人/通话记录监听注册顺序或等待超时;完成广播到达且设备校验通过后,即使通话记录阶段断开 PBAP,本地联系人解析和保存仍继续,避免把通话记录失败扩大为通讯录失败。PBAP 连接状态事件在活动同步期间按目标 MAC 过滤。通话记录增加同步代次、重复完成广播拦截和代次绑定超时,上一轮后台任务不能结束下一轮同步;系统 CallLog Provider 返回空 Cursor 时明确失败并保留旧数据库,联系人和通话记录的有效空结果均短暂重试后再提交。联系人页切页/后台仅暂时关闭手动下载弹框,返回时恢复当前进度;成功、失败、断开或销毁才清除手动状态;ViewModel 使用独立列表快照发布和搜索,避免复用列表并发混合。Java 17 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 通过,仍需实车覆盖断线、连续下载、空通讯录和空通话记录场景。
|
||||
- 2026-07-23:根据 `tongli_2026_07_23_15_15_43.log` 修复点击重新下载通讯录时的应用崩溃。根因是 `reLoadContact()` 清空搜索框后触发 `searchPhoneBookByKeyWord("")`,后续高亮循环仍调用 `keyWord.charAt(0)`,抛出 `StringIndexOutOfBoundsException`;现对空关键词发布空搜索结果并立即返回,普通联系人列表继续由 `ContactsFragment.showNormalContactListIfNeeded()` 恢复。仅修改搜索空值保护,未调整 PBAP、数据库和弹框流程;Java 17 执行 `:YX_BlueTooth:assembleDebug` 通过,待实车验证重新下载、关键词搜索和清空搜索框。
|
||||
- 2026-07-24:按用户要求读取并分析 Codex 会话 `019f406a-be1b-7722-bb19-96f14e36fa5e` 的本地 JSONL 转录,确认最终需求口径、方案回退、提交边界、验证证据和遗留风险,并将其固化为“会话回顾后的后续分析基线”。本次只修改 `agent.md`,修改前已创建独立备份并校验 SHA-256 一致。
|
||||
- 2026-07-24:处理 `YX_BlueTooth` 安装时报 `Requested internal only, but not enough space`。设备 `/data` 仅 502MB,F2FS 配置 `reserve_root=32768` 固定预留约 128MB,`dumpsys diskstats` 显示普通安装可用空间为 0;主要占用为 `/data/apex/decompressed` 约 205MB 和正在使用的 `/data/gsi/remount/scratch.img` 约 136MB,不能直接删除。当前固件未预装 `com.yaxon.bluetooth`,已按系统应用部署方式将 APK 写入现有 overlay 的 `/system/app/YX_BlueTooth/YX_BlueTooth.apk`,设置权限与 SELinux 标签并重启。验证 `pm path` 正常、包标志包含 `SYSTEM/DEBUGGABLE/PERSISTENT`、主入口解析为 `com.yaxon.bluetooth/.main.MainActivity`,进程已启动。后续 Android Studio 直接 Run 仍会受 `/data` 空间限制,调试更新应继续覆盖系统 APK 后重启,长期方案是调整 userdata 分区容量或系统 F2FS 预留配置。
|
||||
- 2026-07-24:用户确认“不备份”后恢复 `YX_Setting` 主界面蓝牙入口,在 `IntentUtil` 增加蓝牙包名和主 Activity 常量,并在 `SettingActivity` 的 `home_grid_phone` 点击分支通过显式组件跳转 `com.yaxon.bluetooth/.main.MainActivity`;保留用户已有入口恢复改动。使用 Android Studio JBR 21 执行 `:YX_Setting:assembleDebug` 编译通过。
|
||||
- 2026-07-24:用户要求蓝牙通话页对齐目标图中的深灰背景、四个横排操作项界面,并确认“不备份”。静态及像素复核确认当前 `activity_bluetooth_phone.xml` 和现有 80x80 mdpi 图标已与目标图一致;`tongli_old` 的 `phoning_layout_futian.xml` 反而是雪山背景、三功能键加独立挂断键的旧界面。本轮仅将音频切换控件初始硬编码“转到手机”改为现有 `@string/textview_switch_phone`(“切换至手机”),保留来电、拨号、DTMF、网络电话和三方通话逻辑;Android Studio JBR 21 执行 `:YX_BlueTooth:assembleDebug` 通过。当前 ADB 设备为 Android 4.4.4/API 19 的 `rkpx3`,运行 `/system/app/YXBlueToothFuTian.apk` 和旧 `com.yaxon.bluetooth.keyboard.PhoningActivity`,而当前工程要求 minSdk 29;标准覆盖安装明确失败为 `INSTALL_FAILED_OLDER_SDK`。因此该设备不能作为当前 Android 14 工程的安装/截图验收目标,也不能强刷当前 APK,后续须连接同力 3562 对应 API 29+ 设备验证。
|
||||
- 2026-07-24 更新标记:用户确认参考项目就是 `F:\zdCode\tongli_old`,因此上一条对两张截图目标方向的判断作废;本次实际目标为老项目雪山背景界面,而不是深灰四按钮界面。已在新项目 `YX_BlueTooth` 中复用老项目原始按钮 PNG:普通通话改为顶部 `60sp` 号码、`26sp` 状态与计时,接通后显示“通话中”;中部横排键盘、音频路由、麦克风三个 `120dp` 图标,拨号中隐藏、接通后显示;底部独立 `140dp` 挂断键;来电改用老项目 `140dp` 接听/拒接资源。通话 Activity 增加沉浸式全屏并取消系统栏 inset,普通通话隐藏返回键;原有 ViewModel、挂断、DTMF、静音、音频路由、网络电话和三方通话逻辑保留。使用 Android Studio JBR 21 执行 `:YX_BlueTooth:assembleDebug` 成功;当前仅连接 API 19 旧设备,无法安装 minSdk 29 的新 APK,仍需同力 3562/API 29+ 实车截图及通话状态验收。
|
||||
- 2026-07-24 更新标记:已连接同力 3562 `rk3562_u`(Android 14/API 34,`1024x600/160dpi`)完成通话页沉浸式实机复测。供应商配置仍报告 `mAppBounds=916x600`,但 `BluetoothPhoneActivity` 窗口 frame 为完整 `[0,0][1024,600]`;Activity 专用 `BluetoothPhoneTheme` 配合 `setDecorFitsSystemWindows(false)` 后,顶部 `54px` 和左侧 `108px` 系统栏 inset 均不再影响通话页。有效截图 `YX_BlueTooth/build/bluetooth_phone_current2.png` 中号码、状态、计时、三功能键和挂断键统一以 `x=512` 居中,纵向无重叠,因此不增加额外 `-54dp` 布局补偿,避免在当前正确窗口上造成左偏。
|
||||
- 2026-07-24:用户明确第二张截图为老项目通话页基准后,继续按“不备份”做像素和代码双重对比。已将普通通话页顶部 `y=0..199` 从 `#181818` 改为老项目黑色,去掉“通话中”及对应 en/fr 文案的尾部省略号;针对 Android 14 与旧平台 `26sp` 字体度量差异,将状态和计时字形分别上移约 `2px/3px`。修正音频路由按钮的目标动作映射:SCO 在车机时显示电话图标并表示“切换至手机”,SCO 在手机时显示车机图标并表示“切换至车机”,同时动态更新 `contentDescription`。号码、三个功能键、挂断键和雪山背景坐标保持不变。Android Studio JBR 21 执行 `:YX_BlueTooth:assembleDebug` 成功,`git diff --check` 通过,最终 APK 已通过 `adb install -r` 覆盖安装成功;安装后设备 HFP 当前未连接,因此最终真实 SCO 图标切换仍需下一次实车通话截图确认。
|
||||
- 2026-07-25:提交前继续按“不备份”审查老项目式通话页。修复 DTMF 键盘展开时与三个功能键重叠、功能键 selector 的 pressed/selected 状态未透传、来电接听/拒接顺序、手机音频路由下麦克风禁用状态以及 DTMF 长按 `0` 误输入 `+`;selector 补齐 disabled、pressed、focused 状态。`:YX_BlueTooth:assembleDebug` 与 `:YX_BlueTooth:lintDebug` 成功,`:YX_BlueTooth:testDebugUnitTest` 为 `NO-SOURCE`。Lint 报告仍有 24 errors/589 warnings,24 个 error 均位于本次未修改的历史代码行,本次不扩展处理;最终仍需同力 3562 实车验证真实来电、DTMF、三方通话和 SCO 路由切换。
|
||||
- 2026-07-25:用户要求参考老项目左侧蓝牙通话 DTMF 界面继续修改,并确认“不备份”。`view_keyboard_dtmf.xml` 改为左上 `20dp` 定位的 `3x4` 圆形纯数字键盘,单键 `84x84dp`、间距 `4dp`、文字 `36sp/#E6E6E6`,移除 ABC 等副标题;复用老项目 `button_numsmall` 普通态/按下态原图及 selector,两张界面按钮图放在 `drawable-mdpi`(不是应用图标目录 `mipmap-mdpi`),资源 SHA-256 与老项目一致。键盘展开时继续显示中央号码、通话状态、计时、键盘/音频路由/麦克风三个功能键和底部挂断键;DTMF 输入串在中央号码区回显,关闭键盘后恢复联系人名/原号码,并增加 `500ms` 键盘开关防抖。Android Studio JBR 21 执行 `:YX_BlueTooth:assembleDebug` 与 `:YX_BlueTooth:lintDebug` 成功,`:YX_BlueTooth:testDebugUnitTest` 为 `NO-SOURCE`;Lint 仍为历史基线 `24 errors/584 warnings`,本次文件没有新增 error。APK 已通过 `adb install -r` 安装到 Android 14、`1024x600/160dpi` 同力 3562 设备,主界面启动正常;设备当前无 HFP 通话,仍需下一次真实通话截图确认 DTMF 展开态最终像素效果。
|
||||
- 2026-07-25 更新标记:用户以老项目源码及最新确认截图为最终基准,来电状态文案应为“来电中”,不带省略号;此前按另一张截图保留“来电中...”的判断作废。已同步移除 `YX_BlueTooth` 默认、中文、英文及现有法文资源中 `textview_phone_in` 的末尾省略号,Android Studio JBR 21 执行 `:YX_BlueTooth:assembleDebug` 成功。
|
||||
- 2026-07-25:用户确认拨出电话页以老项目截图和源码为准,状态文案由“拨号...”改为“正在拨号”;`textview_dialing` 的默认/中文资源改为“正在拨号”,英文改为 `Dialing`,法文按老项目改为 `Composition en cours`。通话状态逻辑与布局坐标未调整,Android Studio JBR 21 执行 `:YX_BlueTooth:assembleDebug` 成功。
|
||||
- 2026-07-25:结合 `tongli_2026_07_25_14_59_17.log` 和实机系统 CallLog 查询,确认“每打一次多一笔记录”是双写导致:Android Telecom 已为 HFP 通话写入一条系统 CallLog,`BtPhoneManager.setCallLogDatebase()` 又按挂断时间手工写应用数据库;ContentObserver 随后按系统通话开始时间同步时,原有精确时间戳去重无法识别同一通话,再写第二条。已删除 `BtPhoneManager` 的手工入库链路,只保留 Telecom CallLog 监听作为实时来源,并将 `CallLogUtils` 的记录上限由 `<= 50` 修正为 `< 50`。`:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 成功;历史重复数据不做模糊删除,安装后需重新同步一次通话记录,再实测去电、来电接听、未接来电各新增一条。
|
||||
- 2026-07-25 更新标记:重新核对通话记录完整生命周期后,确认上述“双写”根因与删除手工入库方向不变,但原两文件补丁存在 PBAP 同步期间直接丢弃 CallLog Observer 事件、仅按秒级时间去重可能误合并不同号码/类型、Observer 和相关监听销毁不完整三个风险。现已增加 pending 刷新标志并在 PBAP 退出 pulling 时补读一次,去重键改为“标准化号码 + 通话类型 + 系统时间”,销毁时注销 CallLog Observer、PBAP/连接监听并允许控制管理器重新初始化;新增 4 个去重单元测试。使用本机 JBR 17 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 成功,5 个 SDK 单测全部通过;本轮不备份、不安装、不提交,仍需实车覆盖通话结束与 PBAP 同步重叠场景。
|
||||
- 2026-07-27 更新标记:提交前再次审查后,撤销“任意 CallLog Observer 事件生成通用 pending”的中间方案,改为仅由真实 HFP 通话结束生成带 MAC、号码、类型、开始/结束时间的刷新请求。HFP 使用稳定的“MAC + callId”跟踪,覆盖列表清空、单条 `TERMINATED`、三方通话中单条消失及手机/蓝牙断连;PBAP pulling 期间保留请求,成功、失败或超时退出后再处理。通话记录任务改为单线程并用独立 generation 隔离 PBAP 全量替换与本地增量刷新;增量刷新只匹配本次 HFP 通话,对连续拨打同一号码实行系统记录一对一占用并排除已保存旧记录,匹配结果与原数据库在单事务内合并、按时间排序并限制 50 条,失败最多重试 5 次后明确丢弃,避免永久 pending。`CallLogUtils` 达到 50 条即停止遍历并支持注销 Observer,PBAP/连接监听和控制管理器生命周期同步收口。JBR 17 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 成功;通话记录专项 8 个测试及原示例 1 个测试全部通过,`git diff --check` 通过。本轮按用户要求不备份、不安装、不提交,Launcher 两处用户改动不纳入本次修复;仍需同力 3562 实车覆盖去电、来电接听、未接来电、连续同号码、三方通话及通话结束与 PBAP 成功/失败/超时重叠。
|
||||
- 2026-07-27:结合 `tongli_2026_07_27_10_53_46.log` 修复“蓝牙断开期间手机产生通话,重连后车机记录不更新”。日志中三轮 HFP 重连后均出现 `reconcile PBAP service ready state = 0 synchronized = true pulling = false`,没有 `MSG_DOWNLOAD`、联系人完成或通话记录完成广播,列表一直为 50 条;根因是 `BtContactsManager` 把“该 MAC 历史上成功保存过联系人”的 `synchronized=true` 错当成本次连接数据仍为最新,跳过了 PBAP。现改为按 HFP 连接会话去重:每次 HFP 断开清除会话标记,每次 HFP 重连在自动同步开关开启时最多发起一次完整 PBAP 同步,同一连接内重复 HFP/PBAP 回调不会重复下载,PBAP busy 和恢复中状态继续拦截。平台源码 `PbapClientConnectionHandler.MSG_DOWNLOAD` 已确认一次请求会固定下载联系人及 MCH/ICH/OCH 三类通话记录,没有应用层 call-log-only 入口。新增 6 个自动同步决策单元测试;JBR 21 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 成功,SDK 共 15/15 测试通过,`git diff --check` 无空白错误。本轮按用户要求不备份、不安装、不提交;仍需同力 3562 实车复现“断开时手机通话 -> 重连”,确认每个 HFP 连接只有一轮 `MSG_DOWNLOAD`,并确认手机新增通话出现在车机列表。
|
||||
- 2026-07-27:曾尝试将通话记录重连同步改为“保留旧列表并显示不可取消下载弹窗”,同时新增专用弹窗类、布局和下载文案;该方案完成过构建验证,但尚未安装、提交。
|
||||
- 2026-07-27 更新标记:用户确认上述弹窗方案不合理,也不是老项目的真实交互。已完整撤销本次 `CallLogsFragment` 状态逻辑、专用弹窗类与布局以及四套下载文案,恢复修改前行为;后续不得再把“旧列表 + 下载弹窗”作为老项目基准。
|
||||
- 2026-07-27:对比老项目联系人首次进入交互并结合 `tongli_2026_07_27_15_11_53.log`,确认新项目约 1.4~1.8 秒空白的根因是把数据库历史同步标记 `isPhoneBookSynchronized()` 当成本次异步读取已完成,并在 Fragment 初始化和连接面板关闭时重复触发 `readPhoneBook()`。现由 `ContactsFragment` 独立记录本次页面数据是否返回:首次读取和自动 PBAP 开始时保留顶部搜索栏及下载按钮,隐藏联系人列表、搜索结果和字母索引,并显示页面中部“正在下载通讯录”;联系人回调后再显示列表或“暂无联系人”。连接面板关闭时只刷新页面状态,不再重复读取数据库,避免重连瞬间露出旧列表;手动重新下载仍保留旧列表和既有进度弹框。默认、中文、英文、法文下载文案同步调整。Android Studio JBR 执行 `:YX_BlueTooth:compileDebugJavaWithJavac --rerun-tasks` 与 `:YX_BlueTooth:assembleDebug` 均成功,未安装、未提交;仍需实车验证首次进入、切页返回、HFP 重连、自动 PBAP 及手动下载失败路径。
|
||||
- 2026-07-27:结合 `tongli_2026_07_27_16_42_42.log` 修复蓝牙拨号页首次启动时右侧通话记录短暂空白。日志显示 `16:42:50.484` 页面仅凭数据库历史状态 `isCallRecordUpdated=true` 提前显示空适配器并隐藏 loading,直到 `16:42:51.736` 本次 50 条数据回调才显示列表,空白约 1.25 秒;老项目则在 `showCallRecords()` 回调前持续显示右侧加载态。现由 `CallLogsFragment` 独立记录本次页面数据是否返回:初始化、本地读取、自动 PBAP 及 HFP 重连等待期间显示既有“正在同步通话记录”,数据回调后再显示列表或空态;失败状态优先于历史同步标志,连接面板关闭只刷新 UI,不再重复调用 `readCallRecords()`。未修改 PBAP、数据库和 SDK。Android Studio JBR 执行 `:YX_BlueTooth:assembleDebug` 成功,`git diff --check` 通过;本轮不备份、不安装、不提交,仍需实车验证首次启动、空记录、HFP 断开重连、PBAP 成功/失败/超时及切页返回。
|
||||
- 2026-07-27:用户确认“不备份”后,对齐老项目“返回后再次进入蓝牙模块仍显示最后页面”的交互。`BluetoothApplication.lastMainSelectedItem` 以拨号页为进程内默认值,`MainActivity` 每次切换到最终有效页面时更新该值,Activity 重新创建后仅在对应 Profile 可用时恢复联系人或蓝牙音乐,否则进入设置页;现有 HFP 重连自动切换拨号页逻辑保持不变。未使用 `SharedPreferences`,因此进程重启后仍按老项目恢复默认拨号页。Android Studio JBR 21 执行 `:YX_BlueTooth:assembleDebug` 和 `:YX_BlueTooth:compileDebugJavaWithJavac --rerun-tasks` 均成功,`git diff --check` 通过;未安装、未提交,待同力 3562 实车验证联系人/音乐返回恢复、断开及 HFP 重连场景。
|
||||
- 2026-07-27:用户确认“不备份”且当前产品无语音功能后,对齐老项目普通进入蓝牙音乐页不自动播放的交互。已从主界面 `BtMusicFragment` 和独立 `BluetoothMusicActivity` 的 `onResume()` 删除 `playBtMusicState()` 生命周期播放链路及仅为该链路服务的音源状态、最后播放状态和控制管理器引用;进入或恢复页面现在只刷新 A2DP 连接、歌曲信息和远端实际播放状态,手机原本播放时仍通过既有状态观察者申请焦点,用户点击播放按钮仍正常发送播放命令。未新增 MODE 或语音特殊入口。Android Studio JBR 21 强制执行 `:YX_BlueTooth:compileDebugJavaWithJavac --rerun-tasks :YX_BlueTooth:assembleDebug` 成功,`git diff --check` 通过;未安装、未提交,待实车验证手机暂停/播放两种状态下首次进入、返回后恢复及播放按钮。
|
||||
## 媒体无声诊断记录(2026-07-28)
|
||||
|
||||
- 分析日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_28_09_08_28.log`。本轮按用户要求不备份,只做日志、源码和 ADB 只读诊断,未修改业务代码。
|
||||
- 结论:蓝牙 A2DP/AVRCP 播放链路正常,媒体无声的直接原因是系统扬声器主声卡 `rockchip-rk809` 未注册。Audio HAL 因此得到 `card=-1`,无法打开 `AUDIO_DEVICE_OUT_SPEAKER (0x2)` 对应 PCM。
|
||||
- 蓝牙正常证据:音频焦点申请成功并执行 `setA2dpUnMute`;`handleAvStartInd Stream started`、`a2dp_player_start` 正常;SBC 数据持续到达;AVRCP 播放状态和进度持续更新。媒体音量为 39 且未静音,功放、DSP 和 MCU 音源也不是静音或错误源。
|
||||
- 内核/Audio HAL 证据:启动时 `rk809-sound` 持续 `Failed to register card: -517`,ALSA 只列出 `#0: rockchip,bt`;播放时 `route_pcm_card_open() card -1 error!` 共出现 12721 次,并伴随 `invalid card num.`。
|
||||
- `rockchip,audio-routing` 缺失会打印错误,但 `rockchip_multicodecs.c` 调用 `snd_soc_of_parse_audio_routing()` 后没有检查返回值,因此它不是本次 `-517` 的直接来源。首次 `-517` 来自 `rockchip,wait-card-locked=<0>` 等待 0 号声卡;后续 `-517` 来自 ASoC 注册时缺少 DAI 组件。
|
||||
- ADB 已锁定缺失组件:`rk817-codec` 已绑定,ASoC DAI 中存在 `rk817-hifi/rk817-voice`;`ff800000.sai` 未绑定且 ASoC DAI 中不存在,只有蓝牙 SCO 使用的 `ff820000.sai`。`/sys/kernel/debug/devices_deferred` 中 `rk809-sound` 一直 pending。
|
||||
- 根因:当前设备实际加载的 Device Tree 中 `/sai@ff800000/status` 为 `disabled`,而 `/rk809-sound/status` 为 `okay`,导致启用的主声卡引用了禁用的 `sai0` CPU DAI。当前源码 `rk3562-evb2-ddr4-v10.dtsi` 中 `&sai0` 已是 `status = "okay"`,且后续 include 未覆盖它,说明烧录固件中的 DTB 与当前源码不一致,或构建/打包/烧录复用了旧 DTB。
|
||||
- 当前设备:`Rockchip RK3562 EVB2 DDR4 V10 Board`,内核 `6.1.99 #35 Tue Jul 28 09:56:46 CST 2026`;日志中的内核为 `#34 Tue Jul 28 07:56:11 CST 2026`。两版运行时都只有 `rockchip,bt`,主声卡问题未恢复。
|
||||
- 修复落点:确保最终打包并烧录到设备的 DTB 中 `sai@ff800000` 为 `okay`,重新生成并烧录实际承载 DTB 的镜像/分区,避免只更新 Image 而沿用旧 DTB。修复后先验证 `/proc/device-tree/sai@ff800000/status`、`/sys/kernel/debug/asoc/dais`、`/proc/asound/cards` 和 `/proc/asound/pcm`,再测试蓝牙音乐、U 盘音乐和系统提示音。
|
||||
- 待确认:实际构建输出目录当前未挂载在 `Y:\tongli\android14.0_rk3562\out`,尚未核对被打包 DTB 的时间戳与内容;需在构建机确认 DTS 编译依赖、镜像打包输入和烧录分区。
|
||||
|
||||
## HFP 音频路由切换无声诊断(2026-07-28)
|
||||
|
||||
- 分析日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_28_15_59_31.log`。本轮沿用用户“不备份”要求,只修改本诊断记录,未修改系统或业务源码。
|
||||
- 复现时间线:`16:00:15.147` 主扬声器 PCM 在普通模式下打开;`16:00:18.464` Telecom 将 Audio HAL 设置为 `AUDIO_MODE_IN_CALL(2)`;前两轮切换后,厂商 HFP 均重新创建 `16000 Hz/双声道` AudioTrack。第二轮切到手机后,AudioFlinger 空闲约 3 秒,于 `16:00:30.915` 将主输出流置为 standby 并关闭 PCM。
|
||||
- 第三轮切回车机时,异常从 `16:00:36.420` 开始:主 Audio HAL 对 `AUDIO_DEVICE_OUT_SPEAKER(0x2)` 以约 90 次/秒反复执行 `do_out_standby/close device`,持续到 `16:01:24` 通话结束;同时 `get_presentation_position` 持续返回 `Operation not permitted`。蓝牙侧在 `16:00:36.907` 和 `16:00:48.417` 仍能重新创建 AudioTrack、持续写入并累计 delivered frames,因此“写入成功”只是 HAL 向上消费了数据,不代表 PCM 真正播放。
|
||||
- 系统源码根因位于 `Y:\tongli\android14.0_rk3562\hardware\rockchip\audio\tinyalsa_hal\audio_hw.c:1374`:`start_output_stream()` 在 `adev->mode == AUDIO_MODE_IN_CALL` 时无条件 `return 0`,未打开扬声器 PCM。之后 `out_write()` 因没有任何 `out->pcm[i]` 保持 `ret=-1`,向上仍返回 `bytes`,并在错误恢复分支执行 `do_out_standby()`,形成上述循环。
|
||||
- 该产品的 HFP Client 通话下行由厂商 SCO PCM 读取后写入 Android AudioTrack,Rockchip `voice.in_call` 实际为 false;HAL 仅凭 Audio Mode 禁止打开 PCM,错误地套用了“基带 voice session 独占声卡”的 workaround。`out_write()` 和 `do_out_standby()` 已使用 `AUDIO_MODE_IN_CALL && voice_is_in_call(adev)` 判断,只有 `start_output_stream()` 条件不一致。
|
||||
- 修复责任优先归属 Android 系统 Audio HAL/BSP 层。建议最小修复为仅在真实 `voice_is_in_call(adev)` 时跳过打开输出流,HFP Client + AudioTrack 场景必须允许重新打开 speaker PCM。修复后验证至少 5 轮“车机 -> 手机 -> 等待超过 3 秒 -> 车机”,确认每轮都有 `start_output_stream device=0x2`、无高频 standby/close、AudioTrack 有声;同时回归系统提示音、蓝牙音乐及真实基带通话(若产品支持)。
|
||||
- 2026-07-28 实车验证更新:用户确认按上述判断修正后重新编译并测试,通话音频切换已恢复。`tongli_2026_07_28_19_50_02.log` 中完成了多轮手机端/车机端切换;在 `AUDIO_MODE_IN_CALL` 下,`19:51:19.207` AudioTrack 停止、`19:51:22.237` 输出 standby 后,`19:51:22.263` 成功重新执行 `start_output_stream device=0x2`;第二组等待约 9 秒的场景中,`19:52:47.501` standby 后,`19:52:56.608` 同样成功重新打开扬声器输出,随后 HFP AudioTrack 创建并恢复音量。
|
||||
- 修复前日志共有 `18648` 次 `do_out_standby`、`19645` 次 `get_presentation_position` 错误,仅有 `19` 次完整输出启动;修复后日志仅有 `13` 次正常 standby、`13` 次 presentation 初始查询告警,并有 `13` 次完整 `start_output_stream`,高频约 90 次/秒的失败循环已消失。结合用户确认喇叭有声,可判定该 HAL 修复有效。
|
||||
- 版本管理风险:当前可见的 `Y:\tongli\android14.0_rk3562\hardware\rockchip\audio\tinyalsa_hal\audio_hw.c` 修改时间仍为 `2026-07-28 16:54:36`,Git 状态干净,且 `start_output_stream()` 仍是旧的无条件 `AUDIO_MODE_IN_CALL` 判断。说明本次验证镜像来自另一份源码/构建目录,或修复尚未同步回 `Y:\tongli`。正式提交或后续重编前必须先将已验证的一行条件修改同步到目标仓库,避免修复丢失。
|
||||
- 已生成BSP交付文档:`F:\zdCode\tongli\HFP通话音频切换无声_BSP分析与修复验证_2026-07-28.md`,包含问题现象、蓝牙侧证据、异常时间线、HAL根因、最小修复、修复前后日志统计、实车验证结果、责任归属、回归测试和版本管理风险。
|
||||
|
||||
## 系统蓝牙开机崩溃一次后恢复诊断(2026-07-29)
|
||||
|
||||
- 分析现场:用户开机后通过 ADB 只读采集 `F:\zdCode\tongli\logs\bt_boot_20260729_092224`,设备为 Android 14 `rk3562_u`,构建时间 `Tue Jul 28 19:21:50 CST 2026`,诚谦版本属性为 `persist.blinkbt.version=BLINK_2652_RELEASE0/2026:07:21:18:57:32`。
|
||||
- 复现结论:本次开机 `dumpsys bluetooth_manager` 显示蓝牙当前已恢复为 `ON`,但 `Bluetooth crashed 1 time`,Enable log 为 `SYSTEM_BOOT -> RESTARTED -> RESTARTED -> CRASH -> RESTARTED`。
|
||||
- 直接崩溃链路:`logcat_all.txt` 中先出现 `AdapterState: BLE_TURNING_ON : BLE_START_TIMEOUT`,随后 `blink-jni SEND: AT#INIT1`、`blink Cur BT Init Failed(0)`、`blink ----RSP: [IINIT2]----`、`CQ_IND_BTINIT status 2 BT reset so abort.`,最终 `com.android.bluetooth` 发生 `Fatal signal 6 (SIGABRT)`,native 堆栈落在 `/system/lib64/libbluetooth_jni.so` 的 `SockIndHandler::DoHandleAdapterInd/OnDataIn/ClientLocalSerial::SocketDataHander`。
|
||||
- 启动时序证据:设备属性显示 `ro.boottime.vendor.bluetooth-1-1=6877680253`,而 `ro.boottime.wifi_insmod=48899783939`、`ro.boottime.yx_bt=48927902356`;日志也显示 `vendor.bluetooth-1-1` 约 7 秒启动,Android `BluetoothService` 在 `19:21:52.904` 因 `SYSTEM_BOOT` 启用蓝牙,但 `wifi_insmod/yx_bt` 到 `19:22:24` 才被 `sys.boot_completed=1` 触发。系统蓝牙先启动,诚谦 blink、Unisoc SDIO BT 驱动和 `/dev/ttyBT0` 后准备好,足以解释首轮 BLE start timeout。
|
||||
- 源码定位:`Y:\tongli\android14.0_rk3562\device\rockchip\rk3562\init.rk3562.rc` 当前在 `on property:sys.boot_completed=1` 中同时 `start wifi_insmod` 和 `start yx_bt`;`wifi.insmod.sh` 负责加载 `umw2652_bsp_sdio.ko/sprdwl_ng.ko/sprdbt_tty_sdio.ko`;`yx_bt.sh` 启动 `/system/bin/blink`。`2927c583eb0 添加诚谦蓝牙blink库` 只替换了 `device/rockchip/rk3562/rk3562_u/firmware/w260/blink/blink` 二进制,未改 init、DTS、device.mk 或权限;`2927` 之后到当前 HEAD 也未再修改蓝牙集成脚本。
|
||||
- DTS 判断:`rk3562-evb2-ddr4-v10.dtsi` 中 `wireless-bluetooth` 为 `status = "disabled"`,当前主链路是 `sprd_marlin3` + SDIO WCN + `sprdbt_tty_sdio.ko`;`BT_RFKILL: rfkill-bt driver has not Successful initialized` 更像旧 Rockchip rfkill 节点未启用的噪声,不是当前崩溃的第一根因。
|
||||
- 仍需确认:`libbluetooth_jni.so` 在收到 `IINIT2/status 2` 后主动 abort 是否为诚谦设计;`AT#INIT1` 超时是否应由库内部等待/重试而非杀掉 `com.android.bluetooth`;`hal_util_load_bt_library: libbluetooth.so not found` 是否符合该 Android 14 集成方案;SELinux permissive 下的 `blink` 访问 `/data/blink/bt_conf.ini`、`/dev/socket/zj_bt_socket`、`/dev/ttyBT0`、`/sys/class/rfkill` denials 未来若切 enforcing 需要补规则。
|
||||
- 建议最小修复方向:系统侧先调整 init 时序,不再等 `sys.boot_completed=1` 才加载 WCN/BT 模块和启动 `blink`;建议在 framework 蓝牙自动启用前完成 `wifi_insmod`,并在 `wifi_insmod` 完成后再启动 `yx_bt`,避免 Android Bluetooth 先进入 BLE start timeout。修改前需确认是否按该方案动 `init.rk3562.rc`。
|
||||
- 2026-07-29 09:58 复现更新:按用户要求重启当前设备并重新采集 `F:\zdCode\tongli\logs\bt_reboot_20260729_095805`。设备正常完成启动且蓝牙最终为 `ON`,但 `dumpsys bluetooth_manager` 再次记录 `Bluetooth crashed 1 time`;Enable log 为 `SYSTEM_BOOT -> RESTARTED -> RESTARTED -> CRASH -> RESTARTED`,说明问题稳定复现。
|
||||
- 本轮时序更清晰:`ro.boottime.vendor.bluetooth-1-1=6678120961`(约 6.68 秒),`ro.boottime.wifi_insmod=49532270023`、`ro.boottime.yx_bt=49571142773`(约 49.53/49.57 秒),蓝牙 HAL 比 WCN 模块和 `blink` 早约 42.9 秒。内核日志确认 `init.rk3562.rc:1` 的 `sys.boot_completed=1` 到约 50.57 秒才触发,随后才启动 `wifi_insmod/yx_bt`。
|
||||
- 本轮异常链仍为两次 `BLE_START_TIMEOUT`(`19:40:12.941`、`19:40:31.617`)后,`blink` 看门狗到 `Cur BT Init Failed(0)`,返回 `IINIT2`;诚谦 `/system/lib64/libbluetooth_jni.so` 在 `SockIndHandler::DoHandleAdapterInd` 记录 `CQ_IND_BTINIT status 2 BT reset so abort.` 并触发 `SIGABRT`。Android 第三次重启蓝牙服务后于 `19:40:42.060` 收到 `CQ_IND_BTINIT, state: 1`,最终恢复。
|
||||
- ADB 核对设备运行时 `/vendor/etc/init/hw/init.rk3562.rc` 仍是修改前版本:第 13 行 `start wifi_insmod`、第 16 行 `start yx_bt` 均位于 `on property:sys.boot_completed=1`;因此本轮属于修复前基线,不能用于判断源码中移动到 `on boot` 的修改是否有效。待新镜像烧录后按同样流程复测,验收标准为无 `BLE_START_TIMEOUT/IINIT2/SIGABRT`、`Bluetooth crashed 1 time` 消失,且 `wifi_insmod/yx_bt` 启动不再晚于蓝牙自动启用。
|
||||
- 2026-07-29 10:19 新版本验证更新:设备已刷入构建时间 `Wed Jul 29 10:03:33 CST 2026` 的镜像,运行时 `init.rk3562.rc` 确认将 `exec_start wifi_insmod`、`start yx_bt` 放入 `on boot`。采集目录为 `F:\zdCode\tongli\logs\bt_reboot_fixed_20260729_101905`。本轮开机 `bluetooth_on=0`,框架记录 `Bluetooth persisted state: 0`,因此首次采集蓝牙保持 `OFF`、`Bluetooth crashed 0 times`,不能仅凭零崩溃判定修复通过。
|
||||
- ADB 将蓝牙正常开启后,`bluetooth_on` 已写为 `1`,但在 90 秒内始终无法到 `ON`;采集目录为 `F:\zdCode\tongli\logs\bt_manual_enable_fixed_20260729_102335`。`dumpsys` 显示 `APPLICATION_REQUEST` 后连续 5 次 `RESTARTED`,每轮均发生 `BLE_START_TIMEOUT/BLE_STOP_TIMEOUT`,蓝牙服务最终未连接;本轮没有 `SIGABRT`,但功能不可用。
|
||||
- 新回归根因:`on boot` 对 WCN 模块仍然过早且与 Rockchip Wi-Fi HAL 冲突。`vendor.wifi_hal_legacy` 约 7.90 秒启动,因此时看不到真实 SDIO Wi-Fi 芯片而回退加载 `/vendor/lib/modules/bcmdhd.ko`;自定义 `wifi_insmod` 约 8.35 秒同时加载 Unisoc 模块。`bcmdhd` 随后的重试反复操作同一个 `mmc1`,在约 10.69、12.96、15.20、17.44 秒移除已枚举的 WCN SDIO 卡,最终出现 `WCN loopcheck erro`、`mtty_sdio_write sprdwcn_bus_push_list failed: -19`,导致 `blink` 无法完成 `AT#INIT1`。
|
||||
- 启动阶段依据:本轮 `early-boot` 在约 7.58 至 7.66 秒执行,系统随后约 7.69 秒进入 `boot`,并在约 7.70 至 7.99 秒执行 `class_start hal`;当前自定义 `on boot` 直到约 8.32 秒才运行。设备 `/vendor/bin/wifi.insmod.sh` 仅包含三条 `insmod`,不依赖 `/data`,适合提前执行。
|
||||
- 更新建议:不要继续使用“`on boot` 内同时 `exec_start wifi_insmod` + `start yx_bt`”。将 `exec_start wifi_insmod` 单独放到 `on early-boot`,确保 Unisoc 驱动加载完成后系统才进入 `boot/class_start hal`,避免 Wi-Fi HAL误加载 `bcmdhd`;`start yx_bt` 保留在 `on boot`,等待驱动节点就绪后启动诚谦程序。`on property:wlan.driver.status=ok` 可作为等待 Broadcom 探测结束的备选触发点,但离蓝牙框架启动仅约 1.3 秒,时序余量小,不作为首选。
|
||||
|
||||
## PBAP 大通讯录超时与失败交互(2026-07-29)
|
||||
|
||||
- 结合 `C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_29_11_37_01.log` 确认,本次不是手机拒绝通讯录权限:PBAP 于 `11:36:52.450` 成功连接并开始拉取 `2393` 个联系人,日志未出现 OBEX Unauthorized/Forbidden。
|
||||
- `BtContactsManager` 在约 60 秒后触发固定传输超时并主动断开 PBAP;断开时底层已得到 `2392` 张 vCard,随后出现 `Interrupted durring insert`、`socket closed` 和通话记录 OBEX busy,均为主动断开后的连带结果。
|
||||
- 已将 `CONTACT_TRANSFER_TIMEOUT_MS` 从 60 秒调整为 5 分钟硬保护,避免大通讯录在仍有传输/落库进展时被误杀;平台当前未向应用暴露逐条传输进度,后续更理想方案是增加进度或无活动超时机制。
|
||||
- 联系人失败且无旧数据时不再显示不可操作、单行截断的权限文案,改为老项目风格的“请下载联系人”可重试状态;同步按钮和下载图标均可重新下载。已有联系人时继续保留旧列表,并通过短 Toast 提示同步失败。
|
||||
- 通话记录同步失败时,有旧数据则保留列表;无数据则显示“一键同步”入口,不再只显示不可操作的权限失败文案。收到明确失败回调后立即进入失败态,不再额外等待 60 秒。
|
||||
- 使用 Android Studio JBR 17 执行 `:YX_BlueTooth:assembleDebug` 成功,`git diff --check` 通过。本轮按用户要求不备份、不安装、不提交;待实车使用约 2393 个联系人的同一手机复测,确认 60 秒后仍继续同步并最终显示联系人及通话记录,同时验证失败页两个重试入口。
|
||||
- 提交前交互核对更新:联系人和通话记录同步失败提示已由系统 `Toast.makeText()` 改为蓝牙模块现有的 `BluetoothResultToast`,统一使用 `ico_failed` 和 2 秒展示时长;两个 Fragment 均在 `onDestroyView()` 释放 Dialog,避免窗口泄漏。`:YX_BlueTooth:assembleDebug` 再次构建成功。
|
||||
|
||||
## PBAP 去头像同步时长验证(2026-07-30)
|
||||
|
||||
- 日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_30_11_20_08.log`。本轮为系统 PBAP 请求字段去掉 `PBAP_FILTER_PHOTO` 后的实车结果;只做日志分析和记录,未修改源码、未备份。
|
||||
- 统一计时口径:PBAP 传输为 `MSG_DOWNLOAD -> onContactsPullCompleted`;本地读取解析为 `onContactsPullCompleted -> contacts read completed`;数据库保存为 `contacts read completed -> contacts save completed`;核心同步总时长为 `MSG_DOWNLOAD -> contacts save completed`;端到端总时长为首次 `Received request to ConnectPBAPPhonebook -> contacts save completed`。
|
||||
|
||||
| 258 联系人轮次 | PBAP 传输 | 本地读取解析 | 数据库保存 | 核心同步总时长 | 端到端总时长 |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| 第 1 轮 | 13.791s | 2.873s | 0.121s | 16.785s | 17.540s |
|
||||
| 第 2 轮 | 11.343s | 2.901s | 0.159s | 14.403s | 15.801s |
|
||||
| 第 3 轮 | 10.522s | 2.804s | 0.154s | 13.480s | 14.549s |
|
||||
| 平均 | 11.885s | 2.859s | 0.145s | 14.889s | 15.963s |
|
||||
| 中位数 | 11.343s | 2.873s | 0.154s | 14.403s | 15.801s |
|
||||
|
||||
- 与 2026-07-22 同为 258 联系人的历史基线相比:历史 PBAP 约 `8.6~8.9s`、本地解析约 `7.3s`、保存约 `0.5s`、核心阶段合计约 `16.4~16.7s`。本轮平均核心同步总时长 `14.889s`,快约 `1.5~1.8s`(约 `9%~11%`);按中位数 `14.403s` 看,快约 `2.0~2.3s`(约 `12%~14%`)。
|
||||
- 改善主要发生在本地读取解析阶段,约从 `7.3s` 降至 `2.8~2.9s`;PBAP 传输本身反而增加到 `10.5~13.8s`。因此当前只能确认完整同步总时长略有改善,不能凭这一份日志证明“去掉头像直接缩短了 PBAP 无线传输时间”。三轮连续变快,也表明手机端响应、连接状态或缓存存在波动。
|
||||
- 三轮均保存 258 条联系人,未出现 `Decode error on base64 photo`。每轮 `numberOfContactsRemaining` 依次为 `0 / 260 / 1`,结合平台固定下载顺序,对应 FAV 为空、主通讯录约 260 项、SIM 目录无有效联系人;FAV/SIM 在该手机上主要是额外请求开销,但不计入上述联系人保存完成时间之后的核心总时长。
|
||||
|
||||
| 2393 项大通讯录完整轮次 | PBAP 传输 | 本地读取解析 | 数据库保存 | 核心同步总时长 | 端到端总时长 |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| 第 1 轮 | 101.403s | 26.353s | 0.407s | 128.163s | 128.686s |
|
||||
| 第 2 轮 | 106.260s | 23.850s | 0.405s | 130.515s | 131.757s |
|
||||
| 平均 | 103.832s | 25.102s | 0.406s | 129.339s | 130.222s |
|
||||
|
||||
- 大通讯录两轮均从 2393 项规模收到 2392 张 vCard,但应用最终读取并保存 2000 条;该数据规模和 258 联系人历史基线不可直接混合比较。日志读取时第 3 轮大通讯录已开始但尚未完成,因此未纳入统计。后续应单独核对 2000 条上限是否为产品预期,并继续按相同五段口径记录至少 3 轮冷连接结果。
|
||||
- 更新标记:上述日志文件后来继续写入,第 3 轮大通讯录最终完整结束。补齐后“仅去头像”大通讯录基线为 3 轮:PBAP 平均 `104.226s`、应用读取解析 `24.550s`、保存 `0.396s`、核心同步总时长 `129.172s`、PBAP 连接请求到列表显示 `130.199s`。原“两轮平均”保留作为当时日志截点记录,后续比较统一使用本条三轮基线。
|
||||
|
||||
## PBAP 精简通讯录字段同步时长验证(2026-07-30)
|
||||
|
||||
- 日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_30_14_16_40.log`。系统 PBAP 已在上一笔去掉 `PHOTO`,本轮继续从 `PBAP_REQUESTED_FIELDS` 去掉应用未使用的 `ADR / EMAIL / NICKNAME`,仅保留 `VERSION / FN / N / TEL`。本轮只分析日志并记录,未修改源码、未备份。
|
||||
- 同一手机 `...2D:AD`、同为 258 条应用联系人,共有 8 轮关键时间点完整的同步;日志开头另有 1 轮缺少 PBAP 起点,未纳入统计。计时口径沿用上一节。
|
||||
|
||||
| 轮次 | PBAP 传输 | 本地读取解析 | 数据库保存 | 核心同步总时长 | 端到端总时长 |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| 第 1 轮 | 11.034s | 2.833s | 0.090s | 13.957s | 15.353s |
|
||||
| 第 2 轮 | 10.042s | 2.598s | 0.087s | 12.727s | 13.547s |
|
||||
| 第 3 轮 | 10.356s | 2.570s | 0.146s | 13.072s | 13.966s |
|
||||
| 第 4 轮 | 11.014s | 2.605s | 0.133s | 13.752s | 15.081s |
|
||||
| 第 5 轮 | 9.476s | 2.772s | 0.101s | 12.349s | 13.099s |
|
||||
| 第 6 轮 | 10.825s | 2.561s | 0.098s | 13.484s | 15.743s |
|
||||
| 第 7 轮 | 10.248s | 2.723s | 0.140s | 13.111s | 14.353s |
|
||||
| 第 8 轮 | 10.977s | 2.567s | 0.094s | 13.638s | 15.782s |
|
||||
| 平均 | 10.497s | 2.654s | 0.111s | 13.261s | 14.616s |
|
||||
| 中位数 | 10.591s | 2.602s | 0.100s | 13.298s | 14.717s |
|
||||
|
||||
- 用户可见结束点补充:以 `ContactsFragment: notifyPhoneBookList phoneBookBeanList size = 258` 作为列表数据已交给 Adapter 并切换到可见状态的日志点;该方法随后立即执行 `setData()`、`notifyDataSetChanged()` 和 `showNormalContactListIfNeeded()`。7 轮有明确 `ContactsFragment: reLoadContact` 的手动下载如下,另 1 轮为手机重连自动同步,不混入手动点击统计。
|
||||
|
||||
| 手动轮次 | 点击下载 -> 列表显示 | PBAP 连接请求 -> 列表显示 | 系统联系人下载完成 -> 列表显示 | 数据库保存完成 -> 列表刷新 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| 第 1 轮 | 15.546s | 15.360s | 2.930s | 7ms |
|
||||
| 第 2 轮 | 13.656s | 13.553s | 2.691s | 6ms |
|
||||
| 第 3 轮 | 14.078s | 13.973s | 2.723s | 7ms |
|
||||
| 第 4 轮 | 15.212s | 15.088s | 2.745s | 7ms |
|
||||
| 第 5 轮 | 15.857s | 15.751s | 2.667s | 8ms |
|
||||
| 第 6 轮 | 14.470s | 14.362s | 2.872s | 9ms |
|
||||
| 第 7 轮 | 15.901s | 15.790s | 2.669s | 8ms |
|
||||
| 平均 | 14.960s | 14.840s | 2.757s | 7.4ms |
|
||||
| 中位数 | 15.212s | 15.088s | 2.723s | 7ms |
|
||||
|
||||
- 因此,当前 258 联系人的真实手动操作体验可以记为:点击下载到列表更新平均约 `14.96s`,典型值(中位数)约 `15.21s`,范围 `13.66~15.90s`。系统发出联系人下载完成广播后,还需平均约 `2.76s` 完成本地读取、数据库保存和界面刷新;保存完成到界面列表刷新仅约 `7ms`,UI 通知不是主要瓶颈。
|
||||
|
||||
- 相比“仅去头像”版本的 3 轮基线,本轮 PBAP 平均从 `11.885s` 降到 `10.497s`,缩短 `1.389s`(`11.7%`);核心同步总时长从 `14.889s` 降到 `13.261s`,缩短 `1.628s`(`10.9%`);端到端总时长从 `15.963s` 降到 `14.616s`,缩短 `1.348s`(`8.4%`)。
|
||||
- 分段证据更支持字段精简有效:`MSG_DOWNLOAD -> 主通讯录大小返回` 的前置阶段平均约 `0.519s`,与仅去头像基线约 `0.491s` 基本相同;真正的主通讯录拉取阶段从约 `11.389s` 降到 `9.974s`,缩短约 `1.415s`(`12.4%`)。因此本次改善主要来自主通讯录负载减少,不是 FAV 空目录请求变快。
|
||||
- 8 轮均收到 259 张 vCard、读取并保存 258 条联系人,未出现 PBAP 下载失败、超时、OBEX 权限错误或 `Decode error on base64 photo`。从日志看,该手机上的姓名和号码链路没有回归;地址、邮箱、昵称本就不属于当前产品使用字段。
|
||||
- 日志仍有 10 次 ContactsProvider `SqliteObjectLeakedViolation / LeakedClosableViolation`,但没有导致本轮失败。告警 SQL 中出现 `photo_id / photo_uri / photo_thumb_uri`,是因为 Provider 查询使用 `projection=null` 返回默认全列,不代表 PBAP 仍下载了头像 Blob。当前 `ContactUtils` 已在 `finally` 中关闭主 Cursor 和逐联系人的号码 Cursor,不能仅凭该 Provider 侧终结栈归责给此方法;若继续处理泄漏,需要单独采集完整调用侧证据。
|
||||
- 后续更有价值的性能方向是应用层读取:`ContactUtils.getAllContacts()` 当前先查询联系人,再对每个联系人单独查询号码,属于 `1 + N` 次 Provider 查询;258 条本轮仍需约 `2.65s`,2393 项大通讯录此前需约 `25.1s`。建议下一笔改为一次批量读取号码并按 `CONTACT_ID` 聚合,保持姓名、多个号码、去重、拼音和进度行为不变。
|
||||
|
||||
### 大容量通讯录复测
|
||||
|
||||
- 同一日志在 `14:39~14:49` 新增同一台 `...54:6A` 手机的 4 轮完整同步。每轮主目录规模均为 2393、收到 2392 张 vCard、应用读取/保存/列表显示均为 2000 条;没有 PBAP 失败、超时、OBEX 权限错误或头像解码错误。
|
||||
|
||||
| 轮次 | PBAP 传输 | 应用读取解析 | 保存 | 核心同步总时长 | 连接请求 -> 列表显示 |
|
||||
|---|---:|---:|---:|---:|---:|
|
||||
| 第 1 轮 | 96.869s | 32.238s | 0.403s | 129.510s | 132.133s |
|
||||
| 第 2 轮 | 95.320s | 27.628s | 0.517s | 123.465s | 125.948s |
|
||||
| 第 3 轮 | 105.760s | 23.057s | 0.340s | 129.157s | 131.633s |
|
||||
| 第 4 轮 | 104.192s | 23.323s | 0.363s | 127.878s | 131.410s |
|
||||
| 平均 | 100.535s | 26.562s | 0.406s | 127.503s | 130.281s |
|
||||
| 中位数 | 100.531s | 25.476s | 0.383s | 128.518s | 131.522s |
|
||||
|
||||
- 对比“仅去头像”三轮大容量基线:PBAP 从 `104.226s` 降到 `100.535s`,缩短 `3.691s`(`3.5%`);核心同步总时长从 `129.172s` 降到 `127.503s`,缩短 `1.670s`(`1.3%`)。说明精简 `ADR / EMAIL / NICKNAME` 对主通讯录传输有小幅收益,但远小于 258 联系人样本中的比例。
|
||||
- 应用读取解析从基线 `24.550s` 波动到平均 `26.562s`,前两轮分别达到 `32.238s / 27.628s`,后两轮回落到 `23.057s / 23.323s`;该阶段受 ContactsProvider、GC、AppSearch 索引和缓存状态影响明显。日志累计出现大量 `AppSearchIcing Document not found`,连续反复删除/导入联系人并不是稳定的单变量环境。
|
||||
- 最终用户可见结果没有明显改善:PBAP 连接请求到列表显示由 `130.199s` 变为 `130.281s`,差异仅 `0.082s`,可视为持平。两轮有明确正常起点的手动点击到列表显示为 `131.935s / 131.518s`,平均 `131.727s`,即约 `2 分 11.7 秒`;另一次 `reLoadContact` 发生在上一轮已经下载完成但应用仍在解析期间,不作为新一轮起点。
|
||||
- 系统下载完成到列表显示平均 `27.003s`,保存完成到 UI 列表通知平均仅 `35.5ms`。大容量场景下一步优化重点仍应转向应用层批量读取,系统字段精简可保留,但不应预期它单独显著缩短最终列表出现时间。
|
||||
|
||||
## PBAP 应用层批量读取优化(2026-07-30)
|
||||
|
||||
- 已按“不备份、不提交”修改 `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/utils/ContactUtils.java`:将原先查询一次联系人表、再按每个联系人查询号码的 `1 + N` 次 ContactsProvider 查询,改为一次查询 `CommonDataKinds.Phone.CONTENT_URI`,仅投影 `CONTACT_ID / DISPLAY_NAME / NUMBER`,并使用 `LinkedHashMap` 按 `CONTACT_ID` 聚合多号码。
|
||||
- 保留既有号码清洗、空姓名回退、多个号码详情、拼音、T9、拼音排序、姓名+主号码去重、解析进度和最多 2000 条联系人等业务行为;超过 2000 条后不再构造联系人及执行拼音/T9计算,但仍遍历聚合结果并完成进度回调。
|
||||
- 查询显式按 `CONTACT_ID ASC, _ID ASC` 排序,保证同一联系人号码聚合顺序稳定。风险边界:超过 2000 条时按联系人 ID 截取,最终显示仍按拼音排序;旧查询未显式指定排序,本身也不保证超过上限时的入选顺序。需在实车重点核对大容量场景的联系人数量、姓名、主/副号码、搜索和拨号。
|
||||
- 新增 `YX_BluetoothSDK/src/test/java/com/yaxon/bluetoothsdk/utils/ContactUtilsTest.java`,覆盖多号码聚合及顺序、号码清洗、空 ID/号码过滤、后续记录回填姓名。
|
||||
- 使用 Android Studio JBR 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug`,构建成功;全部 19 项单元测试通过,其中本次新增 4 项通过。APK 输出为 `YX_BlueTooth/build/outputs/apk/debug/YX_BlueTooth.apk`,`git diff --check` 无错误,仅有工作区 LF/CRLF 提示。
|
||||
- 实车比较基线:258 联系人应用读取解析平均 `2.654s`;2393 项大通讯录应用读取解析平均 `26.562s`。下一步分别复测小容量与大容量至少 3 轮,统一比较 `onContactsPullCompleted -> contacts read completed`,同时记录“点击下载 -> notifyPhoneBookList”的最终可见总时长。
|
||||
|
||||
## PBAP 产品数量上限确认(2026-07-30)
|
||||
|
||||
- 产品已确认联系人上限为 `2000` 条。因此手机主通讯录报告约 `2393` 项、系统收到约 `2392` 张 vCard、应用最终读取和显示 `2000` 条属于符合产品约束的结果,不再按截断缺陷处理。
|
||||
- 产品已确认通话记录为呼入、呼出、未接三类合并后总共显示最新 `50` 条。应用侧 `CallLogUtils.MAX_CALL_LOGS_ROWS` 与 `BtCallRecordsManager.MAX_STORED_CALL_LOGS` 均已按总量 `50` 做时间倒序截取,不需要继续修改应用层数量限制。
|
||||
- Android 14 PBAP 当前分别拉取 `MCH_PATH / ICH_PATH / OCH_PATH`,且 `BluetoothPbapRequestPullPhoneBook` 的 `maxListCount` 为 `0`,实际会请求全部通话记录。为保证合并后的最新 50 条不遗漏任一类型,系统侧建议三个目录各请求最新 `50` 条,再由应用合并排序取最新 `50` 条;系统侧传输上限因此为 `150` 条,而不是直接按类型平均分配为 `17 / 17 / 16`。
|
||||
- 不优先改为单次 `CCH_PATH` 拉取总共 50 条:当前 AOSP `CallLogPullRequest` 仅根据 MCH/ICH/OCH 路径确定通话类型,而 CCH 的类型位于 `X-IRMC-CALL-DATETIME;TYPE=...` 参数中,现有 vCard 数据模型未向该处理器保留此参数。采用 CCH 需要扩展解析链并验证手机兼容性,风险和改动范围明显更大。
|
||||
- 下一步系统源码改动点:在 `PbapClientConnectionHandler.java` 增加通话记录单目录上限 `50`,并将 `downloadCallLog()` 构造 `BluetoothPbapRequestPullPhoneBook` 时的第五个参数 `maxListCount` 从 `0` 改为该常量。应用源码仓不包含该系统文件,按用户要求不通过 Windows 网络映射盘执行 Ubuntu Git 或源码修改。
|
||||
|
||||
## 蓝牙同步失败提示 UI 调整(2026-07-31)
|
||||
|
||||
- 用户实车截图确认,联系人/通话记录同步失败复用了蓝牙名称设置结果使用的 `680dp x 384dp` 大图标弹层,遮挡范围过大。
|
||||
- 对照老项目交互,通讯录和通话记录下载失败应使用紧凑的纯文字提示,带图标的大结果弹层仅保留给蓝牙名称设置成功/失败。
|
||||
- 按用户要求不备份;联系人和通话记录的“同步失败”改用模块现有 `ToastUtils + toast_layout.xml`,以 `484dp x 80dp` 深色居中文字 Toast 展示,不改变同步状态逻辑和蓝牙设置结果弹层。
|
||||
|
||||
## 通讯录搜索高亮色统一(2026-07-31)
|
||||
|
||||
- 用户实车截图确认,通讯录搜索命中文字使用 `#34C759` 绿色,与当前蓝色主视觉不一致,且绿色容易表达成功/在线状态。
|
||||
- 按用户要求不备份;将联系人搜索和拨号联系人匹配的姓名/号码命中文字统一改用已有 `text_blue`(`#009CFF`),未匹配文字颜色保持不变。
|
||||
|
||||
## 后台通话恢复完整页面(2026-08-03)
|
||||
|
||||
- 日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_08_03_14_47_26.log`。14:47:30.362 应用收到手机端去电 `DIALING`,14:47:32.176 收到 `ALERTING`,HFP 链路正常;`TelephoneWindowControl` 仅刷新了隐藏 View,没有真正显示窗口。
|
||||
- 直接原因是 `TelephoneWindowControl.show()` 只允许 `INCOMING`,去电 `DIALING / ALERTING` 在入口直接返回。日志中没有 `show elapsedMilli`,证明没有执行到真正添加悬浮窗的逻辑。
|
||||
- 老项目 `BtAdapterManager.actionStart()` 对来电、去电和通话中统一启动完整 `PhoningActivity`;需求确认按老项目执行:正常通话页面被 Home、Back 或其他应用切到后台后,只要通话未结束就恢复完整通话页面,不使用普通悬浮窗。
|
||||
- 已调整 `BluetoothService`:任意有效单路/多路通话状态在电话 Activity 不在前台时直接启动 `BluetoothPhoneActivity`;倒车、360、待机继续保留不拉起规则,已有普通悬浮窗会先关闭。
|
||||
- 已调整 `BluetoothPhoneActivity`:通话中忽略 Back,Home 或页面进入后台后延迟恢复完整通话页;通话结束、HFP 断开、倒车、360、待机时不恢复。
|
||||
- 使用 Android Studio JBR 17 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 成功,19 项 SDK 单元测试通过;`git diff --check` 通过。尚未安装和实车验证,需复测后台手机去电、接通后 Home/Back、来电、第二路电话、倒车/360和挂断退出。
|
||||
|
||||
## 呼叫等待交互对齐老项目(2026-08-03)
|
||||
|
||||
- 实车日志 `C:\Users\Administrator\Desktop\同力日志\tongli_2026_08_03_15_37_33.log` 在 `16:45:06` 显示原通话 `10000` 为 `ACTIVE`,第二路 `15659813013 / 007_99卡1` 为 `WAITING`;现有页面使用全屏 `rl_multi_layout` 覆盖原通话控制区,并显示新项目遗留的三方控制按钮。
|
||||
- 老项目 `PhoningActivity.setShowSecondPhoneIn()` 只显示 `second_phone_in`,文案为“呼叫等待,请在手机中操作…”;原通话信息和底部控制区保持可见。手机端接听或拒绝后,老项目在 `SECOND_PHONE_PICKUP_OR_REJECT` 或 `HELD` 状态隐藏提示。
|
||||
- 按用户要求不备份。新项目完整通话页移除 `ThirdCallControlView` 覆盖层,只保留居中的呼叫等待提示;两路通话时优先显示 `ACTIVE` 原通话,`WAITING / INCOMING` 仅控制提示显示,不再暴露车机端接听、拒接、保持或切换按钮。
|
||||
- `BluetoothPhoneViewModel.onHfpCallChanged()` 不再在通话数从 1 增加到 2 时提前返回,每次先更新内部快照,再发布完整通话列表;同时保留 `TelephoneWindowControl` 所需的兼容通知。这样第二路来电、手机端拒绝/接听及 `ACTIVE / HELD` 切换都能刷新完整页面状态。
|
||||
- 使用 Android Studio JBR 17 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 成功,SDK 单元测试 19/19 通过,Debug APK 输出为 `YX_BlueTooth/build/outputs/apk/debug/YX_BlueTooth.apk`,`git diff --check` 通过。
|
||||
- 尚需实车验收:原通话中触发第二路等待来电,确认原通话控制区不被遮挡且只出现提示;分别在手机端拒绝、接听、切换两路和全部挂断,确认提示收起、当前活动通话刷新和页面正常退出。
|
||||
|
||||
### 更新标记:老项目现行链路不显示呼叫等待提示
|
||||
|
||||
- 用户重新核对老项目实车,三方场景始终显示普通通话页面,没有出现“呼叫等待,请在手机中操作…”;该实车结果优先于先前仅依据资源和 Activity 消费代码得出的结论。
|
||||
- 重新追踪发送端确认:`SECOND_PHONE_IN` 只由旧 `CallWaitingCallback()` 发送,但创建 `BtAdapter_NF2311` 的代码已经注释;老项目当前构造流程实际注册 `GocBluetoothPhoneChangeListener`。该现行回调对 `WAITING` 没有发送 `SECOND_PHONE_IN`,对 `isMultiParty + ACTIVE` 也只更新三方状态,不显示提示。
|
||||
- 因此 `second_phone_in`、`setShowSecondPhoneIn()` 和 `textview_show_num_3` 属于遗留但未在当前运行链路触发的代码,不能作为现行产品交互基线。先前“等待时显示提示”的结论作废。
|
||||
- 新项目已撤掉完整通话页中的呼叫等待提示和三方按钮覆盖层;第二路电话到达时读取完整通话列表,忽略 `WAITING / INCOMING` 作为主显示对象,优先显示 `ACTIVE` 通话的普通页面。保留 `BluetoothPhoneViewModel` 每次发布完整列表的修复。
|
||||
- Android Studio JBR 17 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 成功,SDK 单元测试 19/19 通过,`git diff --check` 通过。仍需实车验证第二路等待、手机端接听/拒绝、切换和挂断全过程始终保持普通通话页面。
|
||||
|
||||
### 更新标记:多路通话显示优先级与退出幂等处理
|
||||
|
||||
- 按用户要求不备份。代码复核发现多路回调可能短暂包含 `TERMINATED + HELD/WAITING`;若只优先 `ACTIVE`,列表顺序可能导致页面误选已结束通话并退出。
|
||||
- `BluetoothPhoneActivity` 已按 `ACTIVE > DIALING/ALERTING > HELD/HELD_BY_RESPONSE_AND_HOLD > INCOMING/WAITING > 未知状态 > TERMINATED` 选择主显示通话,保证存在有效通话时不会优先处理 `TERMINATED`。
|
||||
- Activity 的所有退出入口统一经过 `finishOnce()`,避免空列表、结束回调和终止状态同时到达时重复请求 `finish()`;ViewModel 现有通知协议保持不变。
|
||||
- Android Studio JBR 17 执行 `:YX_BluetoothSDK:testDebugUnitTest :YX_BlueTooth:assembleDebug` 成功,SDK 单元测试 19/19 通过,`git diff --check` 通过。仍需实车验证第二路接听、手机端切换两路以及分别挂断时页面不闪退、不重进。
|
||||
|
||||
### 决策记录:后期恢复三方通话小窗
|
||||
|
||||
- 当前移除的是完整通话页中的三方覆盖布局,不是删除 HFP 多路通话能力。`ThirdCallControlView`、`ThirdCallingControl`、`TelephoneWindowControl`、`mThirdInCallingLiveData` 以及接听、保持、切换、挂断接口仍保留,可作为后期恢复基础。
|
||||
- 如产品后期要求恢复原有三方通话小窗,应优先恢复 `BluetoothService` 的小窗显示路由和 `TelephoneWindowControl` 的交互,不重新启用覆盖整个通话页的 `rl_multi_layout`。
|
||||
- 恢复时必须保留本次已验证的完整通话列表发布、主通话显示优先级和 `finishOnce()` 幂等退出处理;禁止整体回退本次提交,否则会重新引入第二路回调不刷新、`TERMINATED` 误选和重复 `finish()` 问题。
|
||||
- 恢复后需重新验证:`ACTIVE + WAITING`、`ACTIVE + HELD`、手机端两路切换、`TERMINATED + HELD`、分别挂断、后台与前台互斥显示,以及倒车、360、待机时不弹出小窗。
|
||||
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
跳转都系统设置:
|
||||
adb shell am start -a android.settings.SETTINGS
|
||||
|
||||
|
||||
编译app: ./build_znzc_apk.sh
|
||||
|
||||
![[Pasted image 20260714155734.png]]
|
||||
|
||||
清空,编译
|
||||
source build/envsetup.sh
|
||||
make clean
|
||||
|
||||
![[Pasted image 20261023184114.png]]
|
||||
|
||||
编译系统源码:![[README]]
|
||||
yx_build_ota.sh
|
||||
|
||||
![[Pasted image 20260714155759.png]]
|
||||
|
||||
线刷包
|
||||
![[Pasted image 20260805164653.png]]
|
||||
|
||||
|
||||
git reset --hard HEAD
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 335 KiB |
|
After Width: | Height: | Size: 151 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 361 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 330 KiB |
|
After Width: | Height: | Size: 319 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 224 KiB |
|
After Width: | Height: | Size: 333 KiB |
|
After Width: | Height: | Size: 334 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 305 KiB |
|
After Width: | Height: | Size: 332 KiB |
|
After Width: | Height: | Size: 387 KiB |
|
After Width: | Height: | Size: 248 KiB |
@@ -0,0 +1,580 @@
|
||||
# 同力老项目蓝牙模块完整代码分析
|
||||
|
||||
> 项目路径:`F:\zdCode\tongli_old`
|
||||
> 核心模块:`YX_BlueTooth`
|
||||
> 分析日期:2026-07-13
|
||||
> 分析方式:静态代码分析,未修改项目源码,未执行真机蓝牙测试
|
||||
|
||||
## 1. 总体结论
|
||||
|
||||
`YX_BlueTooth` 是一个拥有系统 UID 的独立车机蓝牙 APK,核心能力包括设备管理、HFP 蓝牙电话、A2DP/AVRCP 蓝牙音乐、PBAP 通讯录和通话记录同步,以及语音、仪表、音频通道、GPIO MIC、系统休眠唤醒等整车联动。
|
||||
|
||||
当前实际底层链路是:
|
||||
|
||||
```text
|
||||
业务 UI / 广播 / AIDL
|
||||
↓
|
||||
UseCase / Presenter / Repository
|
||||
↓
|
||||
BtAdapterManager(约 4200 行的核心总控)
|
||||
↓
|
||||
GocJar(外部 GOC 蓝牙服务客户端)
|
||||
↓
|
||||
外部蓝牙系统服务 / 蓝牙芯片
|
||||
↓
|
||||
HFP / SCO / A2DP / AVRCP / PBAP 回调
|
||||
```
|
||||
|
||||
旧的 `BtAdapter_NF2311` JNI 适配层仍保留大量 native API 和旧回调代码,但加载 native 库、初始化及主要调用均已注释。当前不能把旧 NF2311 回调中的功能等同于现行 GOC 链路已支持。
|
||||
|
||||
### 1.1 功能成熟度判断
|
||||
|
||||
| 功能 | 代码状态 | 结论 |
|
||||
|---|---|---|
|
||||
| 蓝牙开关、扫描、配对、连接 | 主链路存在 | 可用,但存在回调过早、配对时序和状态一致性风险 |
|
||||
| 单方 HFP 电话 | 主链路较完整 | 拨号、来电、接听、挂断、拒接、DTMF、MIC、音频切换基本具备 |
|
||||
| 三方通话 | 有 UI、状态和旧回调残留 | **不能认定为完整支持**,新 GOC 回调链未形成可靠闭环 |
|
||||
| 蓝牙音乐 | 主链路完整 | A2DP 出声、AVRCP 控制、ID3、进度、音频焦点均具备 |
|
||||
| 通讯录/通话记录 | 主链路完整 | PBAP 下载、本地缓存、搜索和 Provider 查询均具备 |
|
||||
| 语音与整车联动 | 多条链路存在 | 功能丰富,但广播、全局状态和服务绑定耦合较重 |
|
||||
|
||||
## 2. 工程定位与依赖
|
||||
|
||||
- 包名:`com.yaxon.bluetooth`
|
||||
- 系统身份:`android:sharedUserId="android.uid.system"`
|
||||
- 最低 Android:API 19(Android 4.4)
|
||||
- 编译/目标 SDK:27
|
||||
- 代码规模:约 160 个 Java 文件、23,911 行
|
||||
- 自动化测试:未发现 `src/test` 或 `src/androidTest`
|
||||
|
||||
主要依赖:
|
||||
|
||||
- `goc.jar`:当前蓝牙协议栈和外部服务接口
|
||||
- `EventBus 2.4.0`:模块内事件分发
|
||||
- `LitePal 1.3.1`:配对设备、通讯录、通话记录持久化
|
||||
- `classes_44new.jar`:Android framework 隐藏 API
|
||||
- `yx_skin`:项目 UI/皮肤依赖
|
||||
|
||||
主要源码入口:
|
||||
|
||||
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/YXApplication.java`
|
||||
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/nf2311/BtAdapterManager.java`
|
||||
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.java`
|
||||
- `YX_BlueTooth/src/main/AndroidManifest.xml`
|
||||
|
||||
## 3. 启动和初始化
|
||||
|
||||
```text
|
||||
YXApplication.onCreate()
|
||||
├─ 初始化 SharedPreferences / LitePal
|
||||
├─ 绑定语音切换服务
|
||||
├─ 绑定音频通道服务
|
||||
├─ 绑定 GPIO 服务
|
||||
├─ 绑定仪表音乐状态服务
|
||||
├─ BtAdapterManager.moduleInit()
|
||||
│ ├─ GocJar.init(context)
|
||||
│ ├─ 注册 Adapter/设置回调
|
||||
│ ├─ 注册 HFP/SCO 电话回调
|
||||
│ └─ 注册 A2DP/AVRCP/ID3 回调
|
||||
└─ 启动 OpenBlueToothService
|
||||
```
|
||||
|
||||
`OpenBlueToothService` 根据持久化的用户期望状态决定是否开蓝牙。这里存在冷启动竞态:如果 GOC AIDL 服务尚未连接,代码重新调用 `GocJar.init()` 后没有可靠的排队与重试,服务很快结束,可能出现“配置为开,实际未开”。
|
||||
|
||||
## 4. 模块划分
|
||||
|
||||
| 模块 | 主要职责 | 关键类 |
|
||||
|---|---|---|
|
||||
| 应用总控 | 初始化、服务绑定、全局状态 | `YXApplication`、`ServiceBindManage` |
|
||||
| 底层适配 | GOC 命令、Profile 回调、状态汇聚 | `BtAdapterManager`、`GocJar` |
|
||||
| 蓝牙设置 | 开关、扫描、配对、连接、名称、PIN | `BluetoothSettingPresenter`、Setting Fragments |
|
||||
| 电话 | 拨号、来电、通话控制和通话 UI | `KeyBoardFragment`、`PhoningActivity`、`PhoningPresenter` |
|
||||
| 音乐 | AVRCP 控制、ID3、进度和页面 | `MusicFragment`、`AudioFocusManager` |
|
||||
| 通讯录 | PBAP、搜索、本地缓存、Provider | `PhoneBookRepository`、`BluetoothPhonebookProvider` |
|
||||
| 通话记录 | PBAP 通话记录、本地缓存、列表 | `CallRecordRepository`、`PhoneRecordFragment` |
|
||||
| 外部联动 | 语音、ITS、仪表、本地音乐、休眠 | 多个 Receiver、AIDL Service、`music_sdk` |
|
||||
|
||||
## 5. 设备管理功能
|
||||
|
||||
### 5.1 蓝牙开关
|
||||
|
||||
```text
|
||||
Setting UI
|
||||
→ BluetoothSettingPresenter.openBluetooth()/closeBluetooth()
|
||||
→ SwitchBluetooth UseCase
|
||||
→ BtAdapterManager.setBluetoothPower(on, callback)
|
||||
→ GocJar.setBtEnable(boolean)
|
||||
→ onEnableChanged(boolean)
|
||||
→ handleBluetoothOpened()/handleBluetoothClosed()
|
||||
→ 设置图标、广播、扫描和 UI 回调
|
||||
```
|
||||
|
||||
问题在于 `setBluetoothPower()` 将 `GocJar.setBtEnable()` 的命令受理结果当作最终状态,立即回调成功并执行 opened/closed 处理,没有等待 `onEnableChanged()`。真实回调到达时还可能再次执行,形成成功回调重复、语音回复成功但底层状态未完成等问题。
|
||||
|
||||
### 5.2 扫描
|
||||
|
||||
```text
|
||||
Presenter.inquiryDevice()
|
||||
→ InquiryDevice
|
||||
→ BtAdapterManager.inquiry()
|
||||
→ GocJar.startBtDiscovery()/cancelBtDiscovery()
|
||||
→ onAdapterDiscoveryStarted/Finished
|
||||
→ onDeviceFound
|
||||
→ LinkedInfoRepository
|
||||
→ EventBus 刷新附近设备列表
|
||||
```
|
||||
|
||||
扫描状态保存在 `YXApplication.mBlueToothInquiryStatus`。设备数据同时存在于 GOC 实际列表、Repository 内存和 LitePal 数据库中。
|
||||
|
||||
### 5.3 配对和取消配对
|
||||
|
||||
附近设备点击后,Presenter 当前代码倾向于先发起 HFP 连接,再根据状态决定是否显式配对。由于连接结果异步,检查时 `mBlueToothConnect` 通常尚未变化,显式 `requestBtPair()` 可能不会执行。该时序依赖底层“连接自动触发配对”的容错能力。
|
||||
|
||||
取消配对先删除本地数据,再异步下发远端 unpair,失败时没有回滚,会造成车机列表与底层配对列表不一致。
|
||||
|
||||
### 5.4 连接和断开
|
||||
|
||||
```text
|
||||
ConnectMacDevice
|
||||
→ 设置 mConnectingMac / mLinkingMac
|
||||
→ requestHfpConnect(mac)
|
||||
→ onHfpStateChanged(CONNECT_SUCCESSED)
|
||||
→ 更新 mLinkedMac、Repository、广播、回调
|
||||
```
|
||||
|
||||
总连接状态主要由 HFP 成功置位,因此 A2DP-only 情况不能可靠代表“已连接”。断开操作依赖 `mLinkedMac`,但该值主要在 HFP 成功后写入;进程恢复、状态丢失或仅媒体连接时可能为空或过期。
|
||||
|
||||
### 5.5 名称和 PIN
|
||||
|
||||
代码支持读取/设置本机蓝牙名称和 PIN。当前实现中:
|
||||
|
||||
- 已有名称偏好时,部分 `getBluetoothName()` 路径返回空串而不是偏好值。
|
||||
- `setBluetoothPassWord()` 未可靠透传远端执行结果,调用侧可能收到伪成功。
|
||||
|
||||
## 6. HFP 蓝牙电话
|
||||
|
||||
### 6.1 页面与功能
|
||||
|
||||
- `KeyBoardFragment`:号码输入、删除、拨号、通话记录回拨。
|
||||
- `PhoningActivity`:来电、去电、通话中 UI,计时、接听、拒接、挂断、MIC、手机/车机音频切换、DTMF、小键盘、三方提示。
|
||||
- `PhoningPresenter`:通过 UseCase 把 UI 命令放入后台执行。
|
||||
- `RingManager`:铃声音频焦点和系统来电标记;但 `startRing()` 未发现业务调用,本地铃声并未真正启动。
|
||||
|
||||
### 6.2 电话调用链
|
||||
|
||||
拨号:
|
||||
|
||||
```text
|
||||
KeyBoardFragment
|
||||
→ Dial UseCase
|
||||
→ BtAdapterManager.dial(number)
|
||||
→ GocJar.requestHfpDialCall(mac, number)
|
||||
```
|
||||
|
||||
来电/去电状态:
|
||||
|
||||
```text
|
||||
GOC onHfpCallChanged(mac, call)
|
||||
→ 保存 Settings.System.BT_CALL_STATE
|
||||
→ 映射为项目旧 phoneStatus
|
||||
→ processCallStatus()
|
||||
→ actionStart()
|
||||
→ 启动/刷新 PhoningActivity
|
||||
→ EventBus、广播、仪表状态上报
|
||||
```
|
||||
|
||||
控制操作:
|
||||
|
||||
| 功能 | 底层命令 |
|
||||
|---|---|
|
||||
| 接听 | HFP accept/pick up |
|
||||
| 挂断 | HFP hang up |
|
||||
| 拒接 | HFP reject |
|
||||
| DTMF | `requestHfpSendDtmf(mac, value)` |
|
||||
| MIC 静音 | `GocJar.muteHfpMic()` |
|
||||
| 转手机 | `requestHfpAudioTransferToPhone()` |
|
||||
| 转车机 | `requestHfpAudioTransferToCarkit()` |
|
||||
|
||||
SCO(通话音频链路)状态通过回调更新 `scoState`。通话激活和结束还会调用 GPIO 服务切换物理 MIC 通道。
|
||||
|
||||
### 6.3 电话状态模型
|
||||
|
||||
新 GOC 回调大致映射为:
|
||||
|
||||
```text
|
||||
IDLE / TERMINATED
|
||||
├─ DIALING → phoneStatus=4 → 去电
|
||||
└─ INCOMING → phoneStatus=5 → 来电
|
||||
|
||||
DIALING / INCOMING
|
||||
→ ACTIVE → phoneStatus=6 → 通话中
|
||||
|
||||
ACTIVE
|
||||
→ TERMINATED → phoneStatus=3 → 挂断
|
||||
```
|
||||
|
||||
通话 UI 同时使用另一套 `CurrentCalls` 语义:
|
||||
|
||||
| 值 | 含义 |
|
||||
|---|---|
|
||||
| 0 | ACTIVE |
|
||||
| 1 | HELD |
|
||||
| 2 | DIALING |
|
||||
| 3 | ALERTING |
|
||||
| 4 | INCOMING |
|
||||
| 5 | WAITING |
|
||||
|
||||
此外还有 `isPhoneActive`、`isMultiCallBuild`、`hfpState`、`scoState`、系统 Settings、EventBus 消息和外部广播,多套状态并存,没有统一状态机。
|
||||
|
||||
## 7. 三方通话专项结论
|
||||
|
||||
### 7.1 是否有三方通话功能
|
||||
|
||||
**代码中有三方通话意图、UI 和部分控制,但现行 GOC 链路不完整,不能确认产品具备可靠可用的三方通话功能。**
|
||||
|
||||
代码证据包括:
|
||||
|
||||
- `isMultiCallBuild` 三方状态标志。
|
||||
- `SECOND_PHONE_IN` 第二路来电事件。
|
||||
- `CurrentCalls` 中 ACTIVE、HELD、WAITING 等状态。
|
||||
- `PhoningActivity` 的第二路号码、等待提示、三方计时和处理 UI。
|
||||
- `enhancedCallCtrl(int action)` 增强通话控制入口。
|
||||
- `GocJar.setThreePartyCallEnable(true)` 三方开关命令。
|
||||
|
||||
### 7.2 为什么不能认定完整支持
|
||||
|
||||
1. `enhancedCallCtrl(int action)` 完全忽略 `action`,无论调用方请求释放当前、保持当前、接听等待或合并,最终都只执行 `setThreePartyCallEnable(true)`。
|
||||
2. `isMultiCallBuild=true` 和 `SECOND_PHONE_IN` 的明确设置点位于旧 `CallWaitingCallback()`;旧 NF2311 适配器初始化已被注释,现行 GOC 回调没有对应闭环。
|
||||
3. 新 `onHfpCallChanged()` 使用 `prePreStatus == status` 直接去重。第二路电话如果状态值与第一路相同,但号码或 `isMultiParty` 变化,回调可能被丢弃。
|
||||
4. 三方 ACTIVE 的映射存在被当成旧状态“普通来电”处理的可能,可能重复拉起或错误刷新通话页面。
|
||||
5. 未找到针对不同增强通话 action 的底层命令映射,也没有三方通话自动化或真机验证证据。
|
||||
|
||||
### 7.3 三方通话验收前置条件
|
||||
|
||||
必须在支持呼叫等待的手机和 SIM 卡上完成以下真机矩阵:
|
||||
|
||||
- 通话 A 中收到 B 来电,显示第二路号码。
|
||||
- 保持 A 并接听 B。
|
||||
- 挂断 A 并接听 B。
|
||||
- 在 A/B 间切换。
|
||||
- 合并会议。
|
||||
- 单独挂断其中一路。
|
||||
- 手机端主动保持、切换、合并后,车机 UI 同步。
|
||||
- 每一步验证 SCO、MIC、通话计时、仪表、本地音乐暂停恢复。
|
||||
|
||||
## 8. 蓝牙音乐
|
||||
|
||||
### 8.1 职责边界
|
||||
|
||||
应用自身不解码音乐:
|
||||
|
||||
- A2DP:手机音乐音频流传输到车机。
|
||||
- AVRCP:播放、暂停、停止、上一首、下一首、播放状态、进度和 ID3 元数据。
|
||||
- GOC:提供 A2DP/AVRCP 命令和回调。
|
||||
- `MusicFragment`:展示和控制 UI。
|
||||
- `AudioFocusManager`:车机音频焦点、音量和其他音源协调。
|
||||
|
||||
### 8.2 控制链
|
||||
|
||||
```text
|
||||
MusicFragment / 语音 / MODE 按键
|
||||
→ PlayControls / ModePlayControls
|
||||
→ BtAdapterManager.playControl(command)
|
||||
→ GocJar AVRCP Play/Pause/Stop/Next/Prev
|
||||
→ 手机端播放器
|
||||
→ AVRCP 播放状态回调
|
||||
→ mPlaying / UI / 仪表 / 音频通道
|
||||
```
|
||||
|
||||
MODE 场景会在首次 Play 后约 1.5 秒重试一次,以适配手机或协议栈尚未就绪的情况。
|
||||
|
||||
### 8.3 播放状态
|
||||
|
||||
1. A2DP 连接后设置 `a2dpState=CONNECTED`;未取得播放条件时先将 A2DP 本地音量置 0。
|
||||
2. AVRCP 连接后设置 `avrcpState=CONNECTED`。
|
||||
3. 蓝牙主 Activity 进入前台时注册 `BTMusic/MusicBluetooth` 音频资源并申请媒体焦点。
|
||||
4. UI/语音发送 AVRCP 命令。
|
||||
5. 实际播放状态以 AVRCP 回调确认:`1=播放、2=暂停、0=停止`。
|
||||
6. 播放且持有焦点时恢复 A2DP 音量、更新 UI 和仪表。
|
||||
7. 未持有焦点却收到播放时,代码静音并连续发送 Pause。
|
||||
8. 暂停、停止或 A2DP 断开时,更新 UI 并注销/释放相关音频资源。
|
||||
|
||||
### 8.4 ID3 和进度
|
||||
|
||||
AVRCP 1.3 元数据写入静态 `ID3Repository`:
|
||||
|
||||
- 标题
|
||||
- 歌手
|
||||
- 专辑
|
||||
- 总时长
|
||||
- 当前进度
|
||||
|
||||
更新后通过 EventBus 刷新 `MusicFragment`。SeekBar 只展示进度,不允许用户拖动定位。
|
||||
|
||||
### 8.5 音频焦点和本地音乐联动
|
||||
|
||||
蓝牙音乐与 `music_sdk` 没有直接 Gradle 依赖,主要通过以下方式协调:
|
||||
|
||||
- Android `AudioFocus`
|
||||
- VoiceSwitcher AIDL 音频资源
|
||||
- `com.yaxon.ACTION_MEDIA_STATUS_CHANGE` 广播
|
||||
- `media.type` / `yaxon_media` 等系统状态
|
||||
- `IMusicStateService` 仪表音乐状态
|
||||
|
||||
媒体类型约定:
|
||||
|
||||
- 本地音乐:`request_value=1`、`local`
|
||||
- 蓝牙音乐:`request_value=2`、`blue`
|
||||
|
||||
`music_sdk` 还监听蓝牙来电、拨号、挂断和拒接广播,在电话期间暂停本地音乐,通话结束后恢复。
|
||||
|
||||
### 8.6 蓝牙音乐主要风险
|
||||
|
||||
- `VoiceSwitchReceiver` 中 Pause/Stop 处理被注释,Resume 也不直接下发 AVRCP Play,音源抢占后不保证手机端真正暂停/恢复。
|
||||
- 旧 ID3 长度解析高字节掩码错误,长度超过 255 时可能解析错误;部分边界检查发生在读取之后,存在越界风险。
|
||||
- 首次只收到时长/进度回调时,新建的 `ID3Info` 没有放回 Repository,页面可能继续显示 `00:00`。
|
||||
- 播放状态同时存在于 GOC、`mPlaying`、Fragment、SharedPreferences、AudioFocus 和系统媒体类型,乱序回调可能造成“图标播放但无声”。
|
||||
- 永久丢失焦点路径可能重复发送 Pause。
|
||||
- 方向盘音乐广播注释声称支持播放/暂停/快进快退,但实际只处理上一首、下一首。
|
||||
- 蓝牙音乐语音资源注册标志未正确置为 true,重复注册依赖远端服务容错。
|
||||
|
||||
## 9. PBAP 通讯录与通话记录
|
||||
|
||||
### 9.1 数据模型
|
||||
|
||||
LitePal 数据库名为 `bluetooth`,版本 2,主要表:
|
||||
|
||||
- `Linkedinfo`:配对/连接设备
|
||||
- `PhoneBook`:通讯录
|
||||
- `CallRecord`:通话记录
|
||||
|
||||
数据层采用 Repository 组合:
|
||||
|
||||
```text
|
||||
UI / Presenter
|
||||
→ Repository
|
||||
├─ 内存缓存
|
||||
├─ LitePal 本地数据源
|
||||
└─ GOC/PBAP 远端数据源
|
||||
```
|
||||
|
||||
### 9.2 通讯录功能
|
||||
|
||||
- 手机通讯录下载
|
||||
- SIM 通讯录下载
|
||||
- 本地缓存和数据库持久化
|
||||
- 中文姓名、拼音首字母、全拼和电话号码搜索
|
||||
- 下载完成后刷新缓存、落库
|
||||
- 广播 `ACTION_BLUETOOTH_CONTACT` 给语音模块
|
||||
- 通过 `BluetoothPhonebookProvider` 给外部模块查询联系人
|
||||
|
||||
### 9.3 Provider 风险
|
||||
|
||||
`BluetoothPhonebookProvider` 在 Manifest 中导出,但未配置访问权限,外部应用可查询完整通讯录。其 `insert/delete/update` 直接抛出 `UnsupportedOperationException`,因此它实际上是未显式声明契约的只读 Provider。
|
||||
|
||||
建议后续明确:
|
||||
|
||||
- 配置 signature/system 权限。
|
||||
- 或设置 `exported=false`,由受控 AIDL 提供查询。
|
||||
- 对所有 query 参数、投影和返回字段做白名单限制。
|
||||
|
||||
## 10. 外部系统联动
|
||||
|
||||
### 10.1 语音
|
||||
|
||||
`VoiceRecognizeReceiver` 支持:
|
||||
|
||||
- 打开/关闭蓝牙
|
||||
- 打开蓝牙音乐
|
||||
- 上一首、下一首
|
||||
- 暂停、继续
|
||||
- 联系人查询与拨号相关命令
|
||||
|
||||
语音执行前会检查连接状态和当前媒体类型。蓝牙开关当前存在“命令受理即回复成功”的问题,可能导致语音说成功但实际状态未变化。
|
||||
|
||||
### 10.2 ITS AIDL
|
||||
|
||||
`ItsAutoBtService` 对外提供蓝牙与电话控制,但存在明显测试/逻辑残留:
|
||||
|
||||
- `type == 2` 关闭蓝牙时,把 `BOOLEAN_SETTING_BLUETOOTH_STATE` 写成了 `true`,应为 `false`。
|
||||
- `type == 5` 不是单纯打开通话界面,而是硬编码拨打 `10000`。
|
||||
|
||||
### 10.3 状态栏和仪表
|
||||
|
||||
- 主界面 `StatusBarIconReceiver` 监听 `com.yaxon.bluetooth.BLUETOOTH` 更新蓝牙图标。
|
||||
- `IMusicStateService` 接收蓝牙音乐类型、播放状态和电话状态,用于仪表显示。
|
||||
- 多条系统 Settings 和广播同时承担状态同步,缺少统一协议版本和来源优先级。
|
||||
|
||||
### 10.4 音频和 GPIO
|
||||
|
||||
- 音频服务提供 Bluetooth 输入通道和蓝牙音量控制。
|
||||
- HFP 通话激活/结束时通过 GPIO `SwitchMicChannel()` 切换 MIC。
|
||||
- 蓝牙音乐使用 `STREAM_MUSIC` 和永久 AudioFocus。
|
||||
- 电话铃声使用 `STREAM_RING` 焦点,但本地铃声启动链不完整。
|
||||
|
||||
### 10.5 EasyConn 和休眠唤醒
|
||||
|
||||
外部广播和服务负责系统休眠/唤醒时关闭、恢复蓝牙,也与 EasyConn 等车机互联场景协调。关闭服务会先清理电话、音乐和全局状态,再调用底层关闭,因此异常中断时可能出现“状态已清理、底层未关闭”的短暂不一致。
|
||||
|
||||
## 11. 全局状态与并发模型
|
||||
|
||||
`YXApplication` 保存大量全局静态状态:
|
||||
|
||||
- 蓝牙总开关和扫描状态
|
||||
- `mBlueToothConnect`
|
||||
- `hfpState/a2dpState/avrcpState/scoState`
|
||||
- `mLinkedMac/mConnectingMac/mLinkingMac`
|
||||
- `mPlaying`
|
||||
- `isPhoneActive/isMultiCallBuild`
|
||||
- 通讯录下载状态
|
||||
|
||||
同时还有:
|
||||
|
||||
- SharedPreferences
|
||||
- Settings.System
|
||||
- LitePal/Repository 缓存
|
||||
- EventBus
|
||||
- Android 广播
|
||||
- AIDL 服务状态
|
||||
- GOC 实际状态
|
||||
|
||||
这些状态会被 AIDL 回调线程、HandlerThread、UseCase 线程池、IntentService 和主线程共同读写,大多没有 `volatile`、锁或统一 reducer/state machine。`BtAdapterManager` 又只保存单个 open/connect/disconnect 等结果回调,并发请求时后一个请求可能覆盖前一个请求。
|
||||
|
||||
`UseCaseScheduler` 的线程池队列容量只有 2,拒绝策略为 `DiscardPolicy`。高频扫描、连接或控制命令可能无日志静默丢弃。
|
||||
|
||||
## 12. 重点风险清单
|
||||
|
||||
### 12.1 高风险
|
||||
|
||||
| 编号 | 风险 | 影响 |
|
||||
|---|---|---|
|
||||
| H1 | 蓝牙开关未等待最终回调,成功回调可能重复 | UI/语音与实际状态不一致 |
|
||||
| H2 | GOC 冷启动绑定无可靠排队重试 | 开机自动开蓝牙偶发失败 |
|
||||
| H3 | 未配对设备连接/配对时序反向 | 配对失败或依赖底层隐式行为 |
|
||||
| H4 | 连接失败不清理旧回调 | 后续请求可能误完成旧操作 |
|
||||
| H5 | 三方电话主要依赖已停用旧回调 | 第二路来电和增强控制可能失效 |
|
||||
| H6 | `enhancedCallCtrl()` 忽略 action | 保持、释放、切换、合并语义不成立 |
|
||||
| H7 | Provider 导出且无访问权限 | 通讯录隐私泄露 |
|
||||
| H8 | ITS 关闭状态写反、硬编码拨号 10000 | 外部控制状态错误或误拨号 |
|
||||
| H9 | ID3 长度/边界解析错误 | 元数据错乱、潜在数组越界 |
|
||||
|
||||
### 12.2 中风险
|
||||
|
||||
| 编号 | 风险 | 影响 |
|
||||
|---|---|---|
|
||||
| M1 | 多套全局状态、无统一状态机 | UI、仪表、语音与底层乱序 |
|
||||
| M2 | UseCase 队列满时静默丢任务 | 用户操作无响应且无日志 |
|
||||
| M3 | 断开依赖可能为空的 `mLinkedMac` | 断开失败、状态残留 |
|
||||
| M4 | MAC 有冒号/无冒号混用 | 部分底层命令兼容性不确定 |
|
||||
| M5 | `Linkedinfo.equals()` 契约错误且无 `hashCode()` | 设备误去重、集合行为异常 |
|
||||
| M6 | 音源抢占 Pause/Resume 不完整 | 蓝牙音乐与本地音乐互抢或不恢复 |
|
||||
| M7 | `PhoningActivity singleTask` 无 `onNewIntent()` | 第二次来电号码/方向不刷新 |
|
||||
| M8 | 挂断/拒接忽略底层结果,6 秒强关 UI | 手机仍通话但车机页面消失 |
|
||||
| M9 | 外部 Receiver 广播普遍缺权限保护 | 非受信应用可注入控制命令 |
|
||||
| M10 | `music_sdk` 中蓝牙 Activity 类名错误 | 前台页面判断失败 |
|
||||
|
||||
`music_sdk/src/futian/.../ProjectConstant.java` 中写的是:
|
||||
|
||||
```text
|
||||
com.yaxon.bluetooth.activity.PhoningActivity
|
||||
com.yaxon.bluetooth.activity.MainActivity
|
||||
```
|
||||
|
||||
实际类名是:
|
||||
|
||||
```text
|
||||
com.yaxon.bluetooth.keyboard.PhoningActivity
|
||||
com.yaxon.bluetooth.main.MainActivity
|
||||
```
|
||||
|
||||
## 13. 建议整改顺序
|
||||
|
||||
### P0:先保证真实状态闭环
|
||||
|
||||
1. 蓝牙开关只在 `onEnableChanged()` 确认后完成请求;增加超时、幂等和单次回调保护。
|
||||
2. GOC 服务绑定期间建立命令队列或明确重试状态。
|
||||
3. 重做连接/配对流程:先判断 bond,未配对先 pair,bonded 后 connect。
|
||||
4. 连接、断开和开关请求使用 request token,超时后清除回调,拒绝陈旧回调。
|
||||
5. 修复 ITS 关闭状态与硬编码 `10000`。
|
||||
|
||||
### P1:补齐三方电话和隐私安全
|
||||
|
||||
1. 根据 GOC 当前 SDK 能力明确 `CHLD=0/1/2/3` 或对应 API 映射。
|
||||
2. 新 GOC 回调按“号码 + 状态 + multiparty + index”去重,不能只看状态值。
|
||||
3. 在新链路中明确生成第二路来电、保持、激活、会议状态事件。
|
||||
4. 为 Provider 和控制广播添加系统签名权限。
|
||||
5. 修复 `PhoningActivity.onNewIntent()` 和挂断结果确认。
|
||||
|
||||
### P2:统一音乐与全局状态
|
||||
|
||||
1. 建立单一蓝牙状态模型,区分 Desired、Command、Confirmed、Error。
|
||||
2. 音乐状态以 AVRCP confirmed state 为准,AudioFocus 只控制是否允许出声。
|
||||
3. 修复 ID3 解析、首次进度对象未落 Repository、重复 Pause。
|
||||
4. 完善 VoiceSwitcher Pause/Resume 的真实 AVRCP 操作。
|
||||
5. 将线程池拒绝策略改为可观测策略,至少记录任务名、队列和调用来源。
|
||||
|
||||
## 14. 测试与验收建议
|
||||
|
||||
### 14.1 设备管理
|
||||
|
||||
- GOC 服务未绑定、延迟绑定、绑定断开、服务重启。
|
||||
- 连续快速开关蓝牙,验证回调只完成一次。
|
||||
- 扫描中关蓝牙、休眠、切换页面。
|
||||
- 新手机首次配对、PIN 错误、用户取消、超时。
|
||||
- 已配对连接、连接另一设备、HFP-only、A2DP-only。
|
||||
- 进程被杀后恢复连接并断开。
|
||||
|
||||
### 14.2 电话
|
||||
|
||||
- 去电、来电、接听、拒接、挂断。
|
||||
- 手机端与车机端分别操作,验证双向同步。
|
||||
- SCO 手机/车机切换,MIC 静音,DTMF。
|
||||
- 空号码、隐藏号码、超长号码和特殊字符。
|
||||
- 来电时本地音乐、蓝牙音乐、收音机的暂停与恢复。
|
||||
- 三方通话按第 7.3 节完整矩阵测试。
|
||||
|
||||
### 14.3 蓝牙音乐
|
||||
|
||||
- Play/Pause/Stop/Prev/Next。
|
||||
- Android 与 iPhone,多种播放器。
|
||||
- 无 ID3、长标题、中文、emoji、元数据超过 255 字节。
|
||||
- 只先到进度回调、回调乱序、A2DP/AVRCP 分别断开。
|
||||
- 导航播报、语音、电话、收音机抢占与恢复。
|
||||
- 方向盘、MODE、语音和页面按键连续快速操作。
|
||||
|
||||
### 14.4 通讯录
|
||||
|
||||
- 0、1、1000、5000 条联系人。
|
||||
- 同名、多号码、中文、英文、特殊字符。
|
||||
- PBAP 拒绝授权、中途断开、重复同步、换手机。
|
||||
- Provider 未授权应用访问验证。
|
||||
|
||||
## 15. 可追踪的关键代码位置
|
||||
|
||||
| 主题 | 文件/位置 |
|
||||
|---|---|
|
||||
| 总初始化 | `YXApplication.java:115` |
|
||||
| Manager 初始化与回调注册 | `BtAdapterManager.java:174` |
|
||||
| 蓝牙开关 | `BtAdapterManager.java:1923` |
|
||||
| 扫描 | `BtAdapterManager.java:1983` |
|
||||
| 连接 | `BtAdapterManager.java:2023`、`:3479` |
|
||||
| 三方控制 | `BtAdapterManager.java:619` |
|
||||
| 旧等待电话回调 | `BtAdapterManager.java:1903` |
|
||||
| 新 GOC 电话回调 | `BtAdapterManager.java:3801` |
|
||||
| AVRCP 播放状态 | `BtAdapterManager.java:4090` |
|
||||
| 音乐页面 | `music/MusicFragment.java:42` |
|
||||
| 电话页面 | `keyboard/PhoningActivity.java:266` |
|
||||
| Provider | `phonebook/model/BluetoothPhonebookProvider.java:39` |
|
||||
| ITS 服务 | `aidl/its/ItsAutoBtService.java:95` |
|
||||
| 线程池 | `UseCaseScheduler.java:23` |
|
||||
|
||||
## 16. 分析边界
|
||||
|
||||
本结论来自应用层 Java、Manifest、Gradle 配置及跨模块静态调用分析。以下内容未获得运行时证据:
|
||||
|
||||
- `goc.jar` 内部实现和外部 GOC 蓝牙服务源码。
|
||||
- 蓝牙芯片固件及其协议兼容性。
|
||||
- 实车音频路由、GPIO MIC 和仪表服务行为。
|
||||
- Android/iPhone 真机连接、PBAP、SCO、A2DP 和三方通话结果。
|
||||
|
||||
因此,“代码入口存在”不等于“硬件环境已验证可用”。三方通话尤其需要按真机矩阵重新确认。
|
||||
@@ -0,0 +1,194 @@
|
||||
# HFP 通话音频切换无声问题分析与修复验证
|
||||
|
||||
## 1. 问题现象
|
||||
|
||||
蓝牙通话过程中,先将通话音频切换到手机端,等待车机扬声器输出进入 standby 后,再切回车机端,车机喇叭没有声音。
|
||||
|
||||
## 2. 分析资料
|
||||
|
||||
- 异常日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_28_15_59_31.log`
|
||||
- 修复后日志:`C:\Users\Administrator\Desktop\同力日志\tongli_2026_07_28_19_50_02.log`
|
||||
- 蓝牙 SCO PCM:`cq_blink_soc_read.pcm`
|
||||
- AudioTrack 写入前 PCM:`cq_blink_play.pcm`
|
||||
- 系统源码:`Y:\tongli\android14.0_rk3562`
|
||||
|
||||
## 3. 蓝牙侧确认
|
||||
|
||||
蓝牙厂商提供的两份 PCM 数据均正常:
|
||||
|
||||
- `cq_blink_soc_read.pcm`:蓝牙 SCO 声卡读取到的下行通话数据正常。
|
||||
- `cq_blink_play.pcm`:经过蓝牙侧处理后,在调用 `AudioTrack.write()` 前的数据正常。
|
||||
|
||||
日志显示 AudioTrack 能正常创建、写入并累计 delivered frames。采样率和声道转换关系也符合当前实现:
|
||||
|
||||
```text
|
||||
蓝牙 SCO 8000 Hz / 单声道 / S16LE
|
||||
AudioTrack 16000 Hz / 双声道 / PCM 16-bit
|
||||
Audio HAL 48000 Hz / 双声道 / S16LE
|
||||
```
|
||||
|
||||
因此,蓝牙 PCM 输入和 AudioTrack 写入前的数据不是本次无声问题的主要原因,排查范围应落在 AudioTrack 后面的系统音频输出链路:
|
||||
|
||||
```text
|
||||
AudioTrack
|
||||
-> AudioFlinger
|
||||
-> AudioPolicy
|
||||
-> Rockchip Audio HAL
|
||||
-> Speaker PCM
|
||||
-> Codec / 功放 / 喇叭
|
||||
```
|
||||
|
||||
## 4. 异常日志时间线
|
||||
|
||||
```text
|
||||
16:00:15.147 HAL通过start_output_stream打开Speaker,device=0x2
|
||||
16:00:18.464 Telecom设置adev_set_mode=2,即AUDIO_MODE_IN_CALL
|
||||
16:00:27.886 音频切到手机端,蓝牙AudioTrack停止
|
||||
16:00:30.915 AudioFlinger空闲约3秒,HAL进入standby并关闭Speaker PCM
|
||||
16:00:36.907 音频切回车机,蓝牙重新创建AudioTrack
|
||||
16:00:38.054 AudioTrack音量恢复为1.0
|
||||
16:00:36.420
|
||||
至16:01:24 HAL持续高频执行do_out_standby/close device,喇叭无声
|
||||
```
|
||||
|
||||
异常期间,HAL每秒约90次执行以下流程:
|
||||
|
||||
```text
|
||||
get_presentation_position: Operation not permitted
|
||||
do_out_standby, device=0x2
|
||||
close device
|
||||
```
|
||||
|
||||
与此同时,蓝牙AudioTrack仍持续写入且帧数增长。这表明HAL虽然向上层返回了“数据已消费”,但数据没有真正送入Speaker PCM。
|
||||
|
||||
## 5. 系统源码根因
|
||||
|
||||
源码文件:
|
||||
|
||||
```text
|
||||
Y:\tongli\android14.0_rk3562\hardware\rockchip\audio\tinyalsa_hal\audio_hw.c
|
||||
```
|
||||
|
||||
目标函数:
|
||||
|
||||
```c
|
||||
static int start_output_stream(struct stream_out *out)
|
||||
```
|
||||
|
||||
原代码约位于第1374行:
|
||||
|
||||
```c
|
||||
if (adev->mode == AUDIO_MODE_IN_CALL) {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
该代码只要发现系统处于 `AUDIO_MODE_IN_CALL`,就直接返回成功,但没有打开扬声器 PCM。
|
||||
|
||||
切换到手机端后,原有Speaker PCM会在空闲超时后关闭。再次切回车机时,`start_output_stream()` 因上述判断直接返回,导致 `out->pcm[]` 中没有有效的Speaker PCM。
|
||||
|
||||
后续 `out_write()` 的行为如下:
|
||||
|
||||
1. 没有找到有效的 `out->pcm[i]`,`ret` 保持为 `-1`。
|
||||
2. 错误恢复分支调用 `do_out_standby()`,再次关闭输出流。
|
||||
3. HAL最终仍向AudioTrack返回 `bytes`,所以上层认为写入成功。
|
||||
4. 下一次写入重复同样过程,形成高频 `standby/close` 循环。
|
||||
|
||||
当前HFP Client通话下行链路为:
|
||||
|
||||
```text
|
||||
蓝牙SCO PCM -> 蓝牙处理 -> AudioTrack -> Speaker PCM
|
||||
```
|
||||
|
||||
该链路不属于Rockchip基带Voice Session。`AUDIO_MODE_IN_CALL` 只表示Framework处于通话模式,不能单独证明基带Voice Session正在占用声卡。
|
||||
|
||||
同一文件中的 `out_write()` 和 `do_out_standby()` 已使用 `voice_is_in_call(adev)` 判断真实Voice Session,只有 `start_output_stream()` 的判断条件不一致。
|
||||
|
||||
## 6. 修复方案
|
||||
|
||||
修改前:
|
||||
|
||||
```c
|
||||
if (adev->mode == AUDIO_MODE_IN_CALL) {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
修改后:
|
||||
|
||||
```c
|
||||
if ((adev->mode == AUDIO_MODE_IN_CALL) && voice_is_in_call(adev)) {
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
修改含义:
|
||||
|
||||
- 系统处于通话模式,且真实Rockchip Voice Session正在运行时,保持原逻辑,不重复打开声卡。
|
||||
- 系统处于通话模式,但HFP Client通过AudioTrack播放时,允许重新打开Speaker PCM。
|
||||
|
||||
## 7. 修复后验证
|
||||
|
||||
修复后已完成多轮“手机端/车机端”音频切换,实车确认切回车机后喇叭声音能够恢复。
|
||||
|
||||
关键验证时间线一:
|
||||
|
||||
```text
|
||||
19:51:19.207 切到手机端,AudioTrack停止
|
||||
19:51:22.237 Speaker输出进入standby
|
||||
19:51:22.263 通话模式下成功执行start_output_stream,device=0x2
|
||||
19:51:22.804 蓝牙重新创建AudioTrack
|
||||
19:51:23.946 AudioTrack音量恢复为1.0
|
||||
```
|
||||
|
||||
关键验证时间线二,Speaker关闭后等待约9秒再切回:
|
||||
|
||||
```text
|
||||
19:52:40.288 adev_set_mode=2,即AUDIO_MODE_IN_CALL
|
||||
19:52:44.501 切到手机端,AudioTrack停止
|
||||
19:52:47.501 Speaker输出进入standby
|
||||
19:52:56.608 成功重新执行start_output_stream,device=0x2
|
||||
19:52:57.115 蓝牙重新创建AudioTrack
|
||||
19:52:58.262 AudioTrack音量恢复为1.0
|
||||
```
|
||||
|
||||
修复前后日志统计对比:
|
||||
|
||||
| 日志 | `do_out_standby` | `get_presentation_position`告警 | 完整`start_output_stream` |
|
||||
| --- | ---------------: | ----------------------------: | ----------------------: |
|
||||
| 修复前 | 18648次 | 19645次 | 19次 |
|
||||
| 修复后 | 13次 | 13次 | 13次 |
|
||||
|
||||
修复后,高频约90次/秒的 `standby/close` 失败循环已经消失。剩余 `get_presentation_position` 告警只在输出流启动阶段出现一次,不再持续刷屏。
|
||||
|
||||
日志中的 `pcm_read error: File descriptor in bad state` 均出现在SCO关闭、蓝牙音频切到手机端之后,属于停止读取过程中的收尾现象,不影响本次修复结论。
|
||||
|
||||
## 8. 责任归属
|
||||
|
||||
本问题主责建议归属Android BSP/Rockchip Audio HAL层:
|
||||
|
||||
- Framework/Telecom设置 `AUDIO_MODE_IN_CALL` 符合通话流程。
|
||||
- AudioTrack能够正常创建和写入。
|
||||
- AudioPolicy输出设备为 `AUDIO_DEVICE_OUT_SPEAKER (0x2)`。
|
||||
- 实际故障发生在Audio HAL重新打开Speaker PCM的状态判断中。
|
||||
|
||||
蓝牙厂商需继续配合提供SCO PCM、AudioTrack写入数据及切换时序,但本次无需修改蓝牙PCM处理逻辑。
|
||||
|
||||
## 9. 回归测试建议
|
||||
|
||||
1. 通话中执行至少5轮“车机端 -> 手机端 -> 等待超过3秒 -> 车机端”。
|
||||
2. 确认每次切回车机后均出现 `start_output_stream device=0x2`。
|
||||
3. 确认不再出现高频 `do_out_standby/close device` 循环。
|
||||
4. 验证通话下行声音、上行麦克风和音量调节。
|
||||
5. 回归蓝牙音乐、系统提示音和其他媒体播放。
|
||||
6. 如果产品支持真实基带通话,回归基带Voice Session,确认 `voice_is_in_call(adev) == true` 时原有声卡独占逻辑不变。
|
||||
|
||||
## 10. 版本管理注意事项
|
||||
|
||||
当前可见的以下文件仍显示为修改前代码,Git状态也为干净:
|
||||
|
||||
```text
|
||||
Y:\tongli\android14.0_rk3562\hardware\rockchip\audio\tinyalsa_hal\audio_hw.c
|
||||
```
|
||||
|
||||
本次验证镜像可能来自另一份源码或构建目录,或者已验证修改尚未同步回正式仓库。正式提交和后续重新编译前,应确认实际构建源码路径,并将上述一行条件修改同步到目标仓库,避免修复丢失。
|
||||
@@ -0,0 +1,430 @@
|
||||
# 蓝牙业务迁移梳理
|
||||
|
||||
日期:2026-07-09
|
||||
|
||||
## 1. 背景
|
||||
|
||||
目标是让新项目 `F:\zdCode\tongli` 的蓝牙业务交互、需求和 UI,与旧项目 `F:\zdCode\tongli_old` 保持一致。
|
||||
|
||||
用户强调:
|
||||
|
||||
```text
|
||||
业务逻辑一样,代码可以不参考。
|
||||
```
|
||||
|
||||
因此本次迁移不能只看代码能不能跑,而要按旧项目和截图确认:
|
||||
|
||||
- 用户入口是否一致
|
||||
- 页面结构是否一致
|
||||
- 操作路径是否一致
|
||||
- 空状态/加载状态/错误状态是否一致
|
||||
- 通话状态机是否一致
|
||||
- 蓝牙音乐、设置、桌面卡片、状态栏是否联动一致
|
||||
|
||||
## 2. 已确认截图范围
|
||||
|
||||
截图里覆盖了以下 UI 和业务状态:
|
||||
|
||||
| 模块 | 页面/状态 |
|
||||
|---|---|
|
||||
| 蓝牙拨号 | 左侧菜单、数字键盘、拨号按钮、右侧通话记录列表 |
|
||||
| 通话记录 | 正常列表、暂无记录、未同步、一键同步、正在同步 |
|
||||
| 联系人 | 联系人列表、搜索、下载/同步、重新下载联系人弹窗 |
|
||||
| 通话态 | 来电中、正在拨号、通话中、通话中键盘 |
|
||||
| 三方通话 | 第二路来电、保持中、三方通话中 |
|
||||
| 蓝牙音乐 | 歌曲信息、封面、进度、上一首、播放/暂停、下一首 |
|
||||
| 蓝牙设置 | 自动接听、蓝牙开关、蓝牙名称、已配对设备、可用设备、修改名称 |
|
||||
| 桌面联动 | 蓝牙电话卡片、蓝牙音乐卡片、状态栏蓝牙状态 |
|
||||
|
||||
## 3. 旧项目业务基线
|
||||
|
||||
旧项目路径:
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli_old
|
||||
```
|
||||
|
||||
旧蓝牙模块:
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli_old\YX_BlueTooth
|
||||
```
|
||||
|
||||
旧项目主入口:
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli_old\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\MainActivity.java
|
||||
```
|
||||
|
||||
旧项目四个一级入口:
|
||||
|
||||
| Tab | 常量 | 业务 |
|
||||
|---|---|---|
|
||||
| 蓝牙拨号 | `TAB_KEYBOARD = 0` | 数字拨号、拨出电话 |
|
||||
| 联系人 | `TAB_PHONEBOOK = 1` | 联系人列表、搜索、同步 |
|
||||
| 蓝牙音乐 | `TAB_BLUETOOTHMUSIC = 2` | A2DP/AVRCP 音乐播放 |
|
||||
| 蓝牙设置 | `TAB_SETTING = 3` | 蓝牙开关、名称、配对、扫描 |
|
||||
| 通话记录 | `TAB_PHONERECORD = 4` | 通话记录列表和分类 |
|
||||
|
||||
旧项目 Manifest 关键入口:
|
||||
|
||||
| 类型 | 名称 |
|
||||
|---|---|
|
||||
| 主界面 | `MainActivity` |
|
||||
| 通话界面 | `PhoningActivity` |
|
||||
| 语音/外部控制 | `VoiceSwitchReceiver`、`VoiceRecognizeReceiver`、`ExternalControlReceiver` |
|
||||
| 蓝牙音乐控制 | `BluetoothMusicReceiver` |
|
||||
| 联系人数据 | `BluetoothPhonebookProvider` |
|
||||
|
||||
旧通话状态:
|
||||
|
||||
| 常量 | 值 | 含义 |
|
||||
|---|---:|---|
|
||||
| `STATUS_ACTIVE` | 0 | 通话中 |
|
||||
| `STATUS_HELD` | 1 | 保持中 |
|
||||
| `STATUS_DIALING` | 2 | 正在拨号 |
|
||||
| `STATUS_ALERTING` | 3 | 对方振铃 |
|
||||
| `STATUS_INCOMING` | 4 | 来电 |
|
||||
| `STATUS_WAITING` | 5 | 等待来电/三方来电 |
|
||||
|
||||
旧通话动作:
|
||||
|
||||
- 拨号
|
||||
- 挂断
|
||||
- 接听
|
||||
- 拒接
|
||||
- 麦克风静音
|
||||
- 车机/手机音频切换
|
||||
- DTMF 小键盘
|
||||
- 三方增强通话控制
|
||||
|
||||
旧联系人/通话记录能力:
|
||||
|
||||
- 联系人同步
|
||||
- 重新下载联系人确认弹窗
|
||||
- 联系人搜索
|
||||
- 通话记录同步
|
||||
- 通话记录分类:全部、拨出、已接、未接
|
||||
- 点击联系人/通话记录拨号
|
||||
|
||||
旧音乐能力:
|
||||
|
||||
- 标题
|
||||
- 歌手
|
||||
- 时长
|
||||
- 当前进度
|
||||
- 上一首
|
||||
- 播放/暂停
|
||||
- 下一首
|
||||
|
||||
旧设置能力:
|
||||
|
||||
- 自动接听
|
||||
- 蓝牙开关
|
||||
- 蓝牙名称
|
||||
- 已配对设备
|
||||
- 附近/可用设备
|
||||
- 搜索设备
|
||||
- 连接/断开/删除设备
|
||||
|
||||
## 4. 新项目承接点
|
||||
|
||||
新项目路径:
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli
|
||||
```
|
||||
|
||||
### 4.1 蓝牙电话 App
|
||||
|
||||
模块:
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_BlueTooth
|
||||
```
|
||||
|
||||
关键文件:
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `YX_BlueTooth\src\main\AndroidManifest.xml` | 蓝牙电话模块入口 |
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\MainActivity.java` | 主拨号/通话记录/联系人页面 |
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothPhoneActivity.java` | 通话界面 |
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\CallLogsFragment.java` | 通话记录 |
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\ContactsFragment.java` | 联系人 |
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\dialog\BtSettingDialog.java` | 蓝牙设置弹窗 |
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\service\BluetoothService.java` | 后台服务和来电窗口联动 |
|
||||
|
||||
新项目主界面当前 Tab:
|
||||
|
||||
| Tab | 常量 | 当前业务 |
|
||||
|---|---|---|
|
||||
| 通话记录 | `TAB_CALL_LOGS = 0` | 右侧通话记录 |
|
||||
| 联系人 | `TAB_CONTACTS = 1` | 右侧通讯录 |
|
||||
| 拨号键盘 | `TAB_KEYBOARD = 2` | 左侧拨号键盘 |
|
||||
|
||||
说明:
|
||||
|
||||
新项目主界面已经不是旧项目四个一级 Tab 结构,而是左侧拨号键盘 + 右侧通话记录/联系人切换。后续要确认 UI 是否必须完全恢复旧截图里的左侧菜单结构:
|
||||
|
||||
```text
|
||||
蓝牙拨号 / 联系人 / 蓝牙音乐 / 蓝牙设置
|
||||
```
|
||||
|
||||
### 4.2 通话状态
|
||||
|
||||
新项目通话状态:
|
||||
|
||||
| 新常量 | 值 | 对应旧状态 |
|
||||
|---|---:|---|
|
||||
| `CALL_STATE_ACTIVE` | 0 | `STATUS_ACTIVE` |
|
||||
| `CALL_STATE_HELD` | 1 | `STATUS_HELD` |
|
||||
| `CALL_STATE_DIALING` | 2 | `STATUS_DIALING` |
|
||||
| `CALL_STATE_ALERTING` | 3 | `STATUS_ALERTING` |
|
||||
| `CALL_STATE_INCOMING` | 4 | `STATUS_INCOMING` |
|
||||
| `CALL_STATE_WAITING` | 5 | `STATUS_WAITING` |
|
||||
| `CALL_STATE_TERMINATED` | 7 | 旧项目无显式同名常量,表示结束 |
|
||||
|
||||
关键承接文件:
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `BluetoothPhoneActivity.java` | 全屏通话界面 |
|
||||
| `BluetoothPhoneViewModel.java` | 通话状态和动作控制 |
|
||||
| `TelephoneWindowControl.java` | 通话窗口控制 |
|
||||
| `ThirdCallingControl.java` | 三方通话/等待来电控制 |
|
||||
| `ThirdCallControlView.java` | 三方通话控制视图 |
|
||||
|
||||
### 4.3 蓝牙 SDK
|
||||
|
||||
模块:
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_BluetoothSDK
|
||||
```
|
||||
|
||||
关键管理类:
|
||||
|
||||
| 类 | 作用 |
|
||||
|---|---|
|
||||
| `BtPhoneManager` | HFP 电话状态管理 |
|
||||
| `BtContactsManager` | 联系人/PBAP |
|
||||
| `BtCallRecordsManager` | 通话记录 |
|
||||
| `BtMusicManager` | 蓝牙音乐 |
|
||||
| `BtSettingManager` | 蓝牙开关、名称、设备状态 |
|
||||
| `BtInfoPushManager` | 状态推送 |
|
||||
| `BtVoiceManager` | 语音控制 |
|
||||
|
||||
关键控制类:
|
||||
|
||||
| 类 | 作用 |
|
||||
|---|---|
|
||||
| `BtPhoneControlManager` | 拨号、接听、挂断、拒接、DTMF 等 |
|
||||
| `BtContactsControlManager` | 联系人控制 |
|
||||
| `BtCallRecordsControlManager` | 通话记录控制 |
|
||||
| `BtMusicControlManager` | 播放、暂停、上一首、下一首 |
|
||||
| `BtSettingControlManager` | 蓝牙开关、连接、断开、配对 |
|
||||
| `BtPairedControlManager` | 已配对设备 |
|
||||
|
||||
结论:新平台应优先使用 SDK 的 manager/control manager,不建议搬旧项目底层实现。
|
||||
|
||||
### 4.4 蓝牙音乐
|
||||
|
||||
新项目有两个承接点:
|
||||
|
||||
| 模块 | 文件 | 说明 |
|
||||
|---|---|---|
|
||||
| `YX_BtMusic` | `YX_BtMusic\src\main\java\com\yaxon\btmusic\main\MainActivity.java` | 独立蓝牙音乐 App |
|
||||
| `YX_BlueTooth` | `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothMusicActivity.java` | 蓝牙电话模块内也存在音乐 Activity |
|
||||
|
||||
当前需要确认:
|
||||
|
||||
```text
|
||||
点击截图左侧“蓝牙音乐”时,是在蓝牙 App 内切换,还是跳转独立 YX_BtMusic?
|
||||
```
|
||||
|
||||
### 4.5 Launcher / SystemUI 联动
|
||||
|
||||
桌面卡片:
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `YX_Launcher\src\main\res\layout\card_bluetooth.xml` | 蓝牙电话卡片 |
|
||||
| `YX_Launcher\src\main\res\layout\card_bluetoothmusic.xml` | 蓝牙音乐卡片 |
|
||||
| `YX_Launcher\src\main\java\com\yaxon\launcher\card\BluetoothCardView.java` | 蓝牙电话状态卡片 |
|
||||
| `YX_Launcher\src\main\java\com\yaxon\launcher\card\BluetoothMusicCardView.java` | 蓝牙音乐卡片 |
|
||||
|
||||
状态栏/快捷设置:
|
||||
|
||||
| 文件/模块 | 作用 |
|
||||
|---|---|
|
||||
| `YX_SystemUI` | 状态栏蓝牙图标、快捷开关 |
|
||||
| `YX_SystemUISDK` | SystemUI 控制器 |
|
||||
|
||||
需要验收:
|
||||
|
||||
- 蓝牙开关状态是否同步到下拉快捷栏。
|
||||
- 已连接/未连接状态是否同步到桌面卡片。
|
||||
- 蓝牙音乐播放状态是否同步到桌面音乐卡片。
|
||||
|
||||
## 5. 业务交互基线表
|
||||
|
||||
| 模块 | 页面/状态 | 用户动作 | 期望结果 | 新项目承接 | 风险 |
|
||||
|---|---|---|---|---|---|
|
||||
| 蓝牙拨号 | 数字键盘 | 点击数字 | 输入框追加号码 | `MainActivity` + `KeyBoardView` | UI 结构需对齐截图 |
|
||||
| 蓝牙拨号 | 删除号码 | 点击删除 | 删除最后一位 | `KeyBoardView` | 长按清空是否支持待查 |
|
||||
| 蓝牙拨号 | 发起呼叫 | 点击绿色拨号 | 进入正在拨号页 | `BtPhoneControlManager` / `BluetoothPhoneActivity` | 未连接时提示需统一 |
|
||||
| 通话记录 | 正常列表 | 点击记录 | 使用该号码拨号 | `CallLogsFragment` | 记录合并/分类需验收 |
|
||||
| 通话记录 | 暂无记录 | 无记录 | 显示暂无记录 | `CallLogsFragment` | 空状态文案/图标需对齐 |
|
||||
| 通话记录 | 未同步 | 未下载记录 | 显示一键同步 | `CallHistoryViewModel` | 是否自动同步需确认 |
|
||||
| 通话记录 | 正在同步 | 点击同步 | 显示同步中 | `CallLogsFragment` | 超时/失败状态需补齐 |
|
||||
| 联系人 | 正常列表 | 点击联系人 | 拨打联系人号码 | `ContactsFragment` | 多号码联系人行为待确认 |
|
||||
| 联系人 | 搜索 | 输入关键字 | 按名称/号码/拼音搜索 | `ContactListViewModel` | 需验中文、拼音、数字 |
|
||||
| 联系人 | 重新下载 | 点击下载图标 | 弹出确认框 | `ContactsFragment` | 弹窗样式需对齐截图 |
|
||||
| 来电 | 来电中 | 接听 | 进入通话中 | `BluetoothPhoneActivity` | 倒车/AVM 场景窗口策略需验收 |
|
||||
| 来电 | 来电中 | 拒接 | 结束来电 | `BluetoothPhoneViewModel` | 单路/三方状态不同 |
|
||||
| 拨出 | 正在拨号 | 挂断 | 结束拨号 | `BluetoothPhoneActivity` | alerting/dialing 显示需统一 |
|
||||
| 通话中 | 静音 | 点击麦克风 | 麦克风静音/取消静音 | `BluetoothPhoneViewModel` | 状态图标需对齐 |
|
||||
| 通话中 | 音频切换 | 点击车机/手机 | SCO 在车机/手机间切换 | `BluetoothPhoneViewModel` | 需实车或蓝牙设备验证 |
|
||||
| 通话中 | DTMF | 打开小键盘 | 发送 DTMF 按键 | `BluetoothPhoneViewModel` | DTMF 兼容性需验收 |
|
||||
| 三方通话 | 第二路来电 | 接听/拒接/保持 | 状态正确切换 | `ThirdCallingControl` | 高风险,必须实测 |
|
||||
| 蓝牙音乐 | 播放页 | 播放/暂停 | AVRCP 控制手机音乐 | `BtMusicViewModel` | 有两个音乐 Activity,入口需确认 |
|
||||
| 蓝牙音乐 | 歌曲信息 | 手机切歌 | 标题/歌手/进度刷新 | `BtMusicControlManager` | 部分手机元数据为空 |
|
||||
| 蓝牙设置 | 蓝牙开关 | 打开/关闭 | 蓝牙状态变化 | `BtSettingDialog` | 截图是页面式,当前是弹窗式 |
|
||||
| 蓝牙设置 | 修改名称 | 输入新名称 | 本机蓝牙名更新 | `BtSettingViewModel` | 修改名称入口需对齐 |
|
||||
| 蓝牙设置 | 可用设备 | 搜索/点击设备 | 配对或连接 | `BtSettingViewModel` | 连接超时/失败提示需统一 |
|
||||
| 桌面卡片 | 蓝牙电话卡片 | 点击卡片 | 进入蓝牙电话 | `BluetoothCardView` | 状态同步需验收 |
|
||||
| 桌面卡片 | 蓝牙音乐卡片 | 点击卡片 | 进入蓝牙音乐 | `BluetoothMusicCardView` | 包名/入口需确认 |
|
||||
| 状态栏 | 快捷蓝牙 | 点击开关 | 打开/关闭蓝牙 | `YX_SystemUI` | 与蓝牙 App 状态需同步 |
|
||||
|
||||
## 6. 当前初步缺口
|
||||
|
||||
### 6.1 入口结构缺口
|
||||
|
||||
旧项目左侧一级入口是:
|
||||
|
||||
```text
|
||||
蓝牙拨号 / 联系人 / 蓝牙音乐 / 蓝牙设置
|
||||
```
|
||||
|
||||
新项目主界面当前更像:
|
||||
|
||||
```text
|
||||
拨号键盘 + 通话记录/通讯录二级切换
|
||||
```
|
||||
|
||||
要确认是否需要把新项目 UI 改回截图结构,至少视觉上应保持一致。
|
||||
|
||||
### 6.2 设置形态缺口
|
||||
|
||||
截图里的蓝牙设置是完整页面区域:
|
||||
|
||||
- 自动接听
|
||||
- 蓝牙开关
|
||||
- 蓝牙名称
|
||||
- 已配对设备
|
||||
- 可用设备
|
||||
|
||||
新项目目前主要是 `BtSettingDialog` 弹窗式承接。
|
||||
|
||||
需要确认:
|
||||
|
||||
```text
|
||||
设置必须做成截图里的完整页面,还是弹窗也可接受?
|
||||
```
|
||||
|
||||
### 6.3 蓝牙音乐入口缺口
|
||||
|
||||
新项目同时存在:
|
||||
|
||||
- `YX_BtMusic`
|
||||
- `YX_BlueTooth\BluetoothMusicActivity`
|
||||
|
||||
必须统一入口和状态来源,避免一个入口播放状态正常,另一个入口不同步。
|
||||
|
||||
### 6.4 三方通话高风险
|
||||
|
||||
三方通话是最容易出现状态错乱的地方:
|
||||
|
||||
- 单路来电
|
||||
- 通话中第二路来电
|
||||
- 一路保持一路激活
|
||||
- 接听第二路并挂断第一路
|
||||
- 挂断当前保留保持中通话
|
||||
|
||||
这一块必须实测,不能只靠 UI 静态对齐。
|
||||
|
||||
## 7. 建议开发顺序
|
||||
|
||||
### 第一阶段:冻结需求和页面资产
|
||||
|
||||
输出并确认:
|
||||
|
||||
- 页面清单
|
||||
- 弹窗清单
|
||||
- 图标清单
|
||||
- 状态清单
|
||||
- 入口跳转清单
|
||||
- Launcher/SystemUI 联动清单
|
||||
|
||||
### 第二阶段:主流程 UI 对齐
|
||||
|
||||
优先对齐:
|
||||
|
||||
1. 蓝牙拨号
|
||||
2. 通话记录
|
||||
3. 联系人
|
||||
4. 来电/拨号/通话中
|
||||
|
||||
### 第三阶段:高风险业务对齐
|
||||
|
||||
重点处理:
|
||||
|
||||
1. 三方通话
|
||||
2. 联系人/通话记录同步状态
|
||||
3. 蓝牙设置设备连接状态
|
||||
4. 蓝牙音乐播放状态同步
|
||||
|
||||
### 第四阶段:系统级联动
|
||||
|
||||
验证:
|
||||
|
||||
1. 桌面蓝牙电话卡片
|
||||
2. 桌面蓝牙音乐卡片
|
||||
3. 状态栏蓝牙图标
|
||||
4. 下拉快捷蓝牙开关
|
||||
5. 开机自启动服务
|
||||
|
||||
## 8. 明天继续时优先做什么
|
||||
|
||||
建议下一步直接做两件事:
|
||||
|
||||
1. 输出《蓝牙页面资产清单》
|
||||
- 页面
|
||||
- 弹窗
|
||||
- 空状态
|
||||
- 加载状态
|
||||
- 错误状态
|
||||
- 按钮状态
|
||||
- 图标状态
|
||||
|
||||
2. 输出《新旧项目功能缺口表》
|
||||
- 已具备,需验收
|
||||
- 已具备,但 UI 不一致
|
||||
- 业务可能缺失
|
||||
- 高风险必须实测
|
||||
- 可以延后处理
|
||||
|
||||
## 9. 后续开发前需要确认的问题
|
||||
|
||||
1. 新项目蓝牙主界面是否必须恢复旧截图的四个左侧入口?
|
||||
2. 蓝牙设置是做完整页面,还是保留当前弹窗式?
|
||||
3. 蓝牙音乐入口以 `YX_BtMusic` 为准,还是以 `YX_BlueTooth\BluetoothMusicActivity` 为准?
|
||||
4. 是否需要完全复刻截图背景、图标和尺寸,还是只对齐交互和业务?
|
||||
5. 是否需要兼容方向盘按键、语音控制、ITS/EasyConn 外部控制?
|
||||
|
||||
## 10. 重要提醒
|
||||
|
||||
进入开发阶段前,需要再次确认是否备份。
|
||||
|
||||
当前文档只是梳理,不代表已经完成代码修改。
|
||||
@@ -0,0 +1,203 @@
|
||||
# 蓝牙主界面开发进展_2026-07-09
|
||||
|
||||
## 范围
|
||||
|
||||
本次只专注新项目 `F:\zdCode\tongli` 的 `YX_BlueTooth` 蓝牙模块。
|
||||
|
||||
不处理:
|
||||
|
||||
- SystemUI
|
||||
- Launcher
|
||||
- MCU
|
||||
- 其他系统联动模块
|
||||
|
||||
## 用户要求
|
||||
|
||||
- UI 和需求要跟老项目 `F:\zdCode\tongli_old` 一致。
|
||||
- 业务逻辑一样。
|
||||
- 代码可以不参考老项目。
|
||||
- 不需要备份。
|
||||
- 资源优先直接拷贝老项目,不从截图切图。
|
||||
|
||||
## 本次完成
|
||||
|
||||
主界面已从新项目原来的:
|
||||
|
||||
```text
|
||||
键盘 + 通话记录/联系人横向页签
|
||||
```
|
||||
|
||||
调整为老项目式:
|
||||
|
||||
```text
|
||||
左侧四菜单 + 右侧页面容器
|
||||
```
|
||||
|
||||
四个菜单:
|
||||
|
||||
```text
|
||||
蓝牙拨号
|
||||
联系人
|
||||
蓝牙音乐
|
||||
蓝牙设置
|
||||
```
|
||||
|
||||
## 代码结构
|
||||
|
||||
主入口:
|
||||
|
||||
```text
|
||||
YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.java
|
||||
```
|
||||
|
||||
主界面现在只负责:
|
||||
|
||||
- 左侧菜单显示
|
||||
- 右侧 Fragment 切换
|
||||
- 联系人搜索/重新同步入口
|
||||
- 蓝牙连接状态同步给拨号页和联系人页
|
||||
|
||||
新增页面:
|
||||
|
||||
```text
|
||||
YX_BlueTooth/src/main/java/com/yaxon/bluetooth/fragment/DialHomeFragment.java
|
||||
YX_BlueTooth/src/main/java/com/yaxon/bluetooth/fragment/BtMusicFragment.java
|
||||
YX_BlueTooth/src/main/java/com/yaxon/bluetooth/fragment/BtSettingFragment.java
|
||||
```
|
||||
|
||||
复用页面:
|
||||
|
||||
```text
|
||||
YX_BlueTooth/src/main/java/com/yaxon/bluetooth/fragment/ContactsFragment.java
|
||||
YX_BlueTooth/src/main/java/com/yaxon/bluetooth/fragment/CallLogsFragment.java
|
||||
```
|
||||
|
||||
## 页面说明
|
||||
|
||||
### 蓝牙拨号
|
||||
|
||||
文件:
|
||||
|
||||
```text
|
||||
fragment_dial_home.xml
|
||||
DialHomeFragment.java
|
||||
```
|
||||
|
||||
结构:
|
||||
|
||||
```text
|
||||
左侧:KeyBoardView
|
||||
右侧:CallLogsFragment
|
||||
输入号码后:显示联系人匹配列表
|
||||
```
|
||||
|
||||
### 联系人
|
||||
|
||||
继续复用:
|
||||
|
||||
```text
|
||||
ContactsFragment
|
||||
fragment_contacts.xml
|
||||
```
|
||||
|
||||
主界面右上角保留:
|
||||
|
||||
- 搜索
|
||||
- 重新同步
|
||||
|
||||
### 蓝牙音乐
|
||||
|
||||
新增:
|
||||
|
||||
```text
|
||||
BtMusicFragment
|
||||
fragment_bt_music.xml
|
||||
```
|
||||
|
||||
复用:
|
||||
|
||||
```text
|
||||
BtMusicViewModel
|
||||
BtMusicControlManager
|
||||
```
|
||||
|
||||
### 蓝牙设置
|
||||
|
||||
新增:
|
||||
|
||||
```text
|
||||
BtSettingFragment
|
||||
fragment_bt_setting.xml
|
||||
```
|
||||
|
||||
复用:
|
||||
|
||||
```text
|
||||
BtSettingViewModel
|
||||
BtDevForumAdapter
|
||||
BtDevScanedAdapter
|
||||
```
|
||||
|
||||
它相当于把原来的 `BtSettingDialog` 弹窗式设置迁成主界面里的页面式设置。
|
||||
|
||||
## 资源复用
|
||||
|
||||
已从老项目拷贝到新项目:
|
||||
|
||||
```text
|
||||
activity_bg.png
|
||||
common_bg_line.png
|
||||
title_head_background.png
|
||||
bluetooth_tabvertical.xml
|
||||
text_click_selector.xml
|
||||
icon_tabvertical_selected.png
|
||||
```
|
||||
|
||||
新增/修改资源:
|
||||
|
||||
```text
|
||||
activity_main.xml
|
||||
listitem_main.xml
|
||||
fragment_dial_home.xml
|
||||
fragment_bt_music.xml
|
||||
fragment_bt_setting.xml
|
||||
colors.xml
|
||||
dimens.xml
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
默认 Java 11 会失败,原因是当前 Android Gradle Plugin 需要 Java 17。
|
||||
|
||||
使用 Android Studio JBR 临时编译:
|
||||
|
||||
```powershell
|
||||
$env:JAVA_HOME='C:\Program Files\Android\Android Studio\jbr'
|
||||
$env:Path="$env:JAVA_HOME\bin;$env:Path"
|
||||
.\gradlew.bat :YX_BlueTooth:assembleDebug --no-daemon --console=plain
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
```text
|
||||
BUILD SUCCESSFUL
|
||||
```
|
||||
|
||||
## 已知风险
|
||||
|
||||
- 当前完成的是主界面四入口骨架,尚未做上车/模拟器视觉验收。
|
||||
- 蓝牙拨号页的号码显示、删除键、点击通话记录拨号等细节需要继续对齐老项目。
|
||||
- 蓝牙设置页目前复用现有设置逻辑,名称修改、自动接听等细节还需要继续核对老项目。
|
||||
- 蓝牙音乐页复用现有音乐逻辑,播放状态、歌曲信息、未连接页还需要实测。
|
||||
- 三方通话还未进入本轮实现,后续需要专项验收。
|
||||
|
||||
## 下一步
|
||||
|
||||
优先顺序:
|
||||
|
||||
1. 实测主界面四入口切换。
|
||||
2. 对齐蓝牙拨号页 UI 和交互。
|
||||
3. 对齐联系人页搜索/同步/空状态。
|
||||
4. 对齐蓝牙音乐页播放控制和未连接状态。
|
||||
5. 对齐蓝牙设置页扫描/连接/断开/删除/蓝牙名称。
|
||||
6. 再做通话页和三方通话专项。
|
||||
@@ -0,0 +1,504 @@
|
||||
# 蓝牙开发实施方案
|
||||
|
||||
日期:2026-07-09
|
||||
|
||||
## 1. 开发目标
|
||||
|
||||
在新项目 `F:\zdCode\tongli` 上开发蓝牙功能,使蓝牙业务交互、需求和 UI 与旧项目 `F:\zdCode\tongli_old` 保持一致。
|
||||
|
||||
用户要求:
|
||||
|
||||
```text
|
||||
业务逻辑一样,UI 和需求跟老项目一致,代码可以不参考。
|
||||
```
|
||||
|
||||
因此开发策略是:
|
||||
|
||||
```text
|
||||
旧项目 = 需求基线和验收标准
|
||||
新项目 = 实现架构和平台能力
|
||||
```
|
||||
|
||||
不建议搬旧代码,不建议重写底层蓝牙栈。应优先使用新项目已有 `YX_BluetoothSDK`、ViewModel、Activity、Service 架构补齐业务和 UI。
|
||||
|
||||
## 2. 总体开发原则
|
||||
|
||||
### 2.1 老项目只做业务参考
|
||||
|
||||
老项目重点参考:
|
||||
|
||||
- 页面入口
|
||||
- 操作路径
|
||||
- 通话状态机
|
||||
- 联系人/通话记录同步流程
|
||||
- 蓝牙音乐控制
|
||||
- 蓝牙设置和设备管理
|
||||
- 三方通话/第二路来电行为
|
||||
- 空状态、加载状态、弹窗和提示
|
||||
|
||||
不直接复制:
|
||||
|
||||
- 老项目 `BtAdapterManager` 底层实现
|
||||
- 老项目老式 Activity/Fragment 结构
|
||||
- 老项目 Android 4.4 平台兼容写法
|
||||
|
||||
### 2.2 新项目保持现有架构
|
||||
|
||||
新项目优先使用:
|
||||
|
||||
| 能力 | 新项目承接 |
|
||||
| -------- | ---------------------------------------------------- |
|
||||
| 蓝牙电话 | `YX_BlueTooth` |
|
||||
| 蓝牙 SDK | `YX_BluetoothSDK` |
|
||||
| 蓝牙音乐 | `YX_BtMusic` 或 `YX_BlueTooth\BluetoothMusicActivity` |
|
||||
| 桌面卡片 | `YX_Launcher` |
|
||||
| 状态栏/快捷开关 | `YX_SystemUI` |
|
||||
|
||||
核心调用应走:
|
||||
|
||||
| 功能 | 控制类 |
|
||||
| ---------------- | ----------------------------- |
|
||||
| 拨号/接听/挂断/拒接/DTMF | `BtPhoneControlManager` |
|
||||
| 联系人 | `BtContactsControlManager` |
|
||||
| 通话记录 | `BtCallRecordsControlManager` |
|
||||
| 蓝牙音乐 | `BtMusicControlManager` |
|
||||
| 蓝牙设置/设备连接 | `BtSettingControlManager` |
|
||||
| 已配对设备 | `BtPairedControlManager` |
|
||||
|
||||
### 2.3 先页面后底层
|
||||
|
||||
开发顺序不应从底层 SDK 开始大改,而应:
|
||||
|
||||
```text
|
||||
页面结构对齐
|
||||
-> 状态和交互对齐
|
||||
-> ViewModel 调用补齐
|
||||
-> SDK 缺口验证
|
||||
-> 系统联动验收
|
||||
```
|
||||
|
||||
## 3. 模块拆分
|
||||
|
||||
蓝牙功能按以下模块推进:
|
||||
|
||||
| 模块 | 目标 |
|
||||
|---|---|
|
||||
| 主界面导航 | 对齐旧项目/截图入口结构 |
|
||||
| 蓝牙拨号 | 数字输入、拨号、删除、未连接提示 |
|
||||
| 通话记录 | 记录列表、分类、同步、空状态、点击拨号 |
|
||||
| 联系人 | 同步、搜索、重新下载、点击拨号 |
|
||||
| 通话界面 | 来电、拨出、通话中、静音、音频切换、DTMF |
|
||||
| 三方通话 | 第二路来电、等待、保持、释放当前接听另一通 |
|
||||
| 蓝牙音乐 | 歌曲信息、进度、播放控制、未连接状态 |
|
||||
| 蓝牙设置 | 自动接听、开关、名称、配对、扫描、连接/断开 |
|
||||
| Launcher 联动 | 蓝牙电话卡片、蓝牙音乐卡片 |
|
||||
| SystemUI 联动 | 状态栏图标、快捷蓝牙开关 |
|
||||
|
||||
## 4. 推荐开发顺序
|
||||
|
||||
### 阶段 1:冻结页面资产和需求
|
||||
|
||||
先产出并确认以下清单:
|
||||
|
||||
- 页面清单
|
||||
- 弹窗清单
|
||||
- 图标清单
|
||||
- 按钮清单
|
||||
- 空状态清单
|
||||
- 加载状态清单
|
||||
- 错误状态清单
|
||||
- 路由/入口清单
|
||||
- Launcher/SystemUI 联动清单
|
||||
|
||||
阶段目标:
|
||||
|
||||
```text
|
||||
开发前知道每个页面长什么样、有哪些状态、点击后去哪里。
|
||||
```
|
||||
|
||||
### 阶段 2:主界面 UI 结构对齐
|
||||
|
||||
旧项目和截图里的主入口更接近:
|
||||
|
||||
```text
|
||||
蓝牙拨号
|
||||
联系人
|
||||
蓝牙音乐
|
||||
蓝牙设置
|
||||
```
|
||||
|
||||
新项目当前主界面更接近:
|
||||
|
||||
```text
|
||||
左侧拨号键盘 + 右侧通话记录/通讯录二级切换
|
||||
```
|
||||
|
||||
这里需要先做产品决策:
|
||||
|
||||
1. 如果要求 UI 完全跟老项目/截图一致,应把新项目 `MainActivity` 的入口视觉改成四项左侧菜单。
|
||||
2. 如果只要求业务一致,当前结构可以保留,但必须补齐蓝牙音乐和蓝牙设置入口。
|
||||
|
||||
建议:
|
||||
|
||||
```text
|
||||
优先按截图恢复四个左侧入口。
|
||||
通话记录可以作为蓝牙拨号页右侧区域或拨号页内二级状态。
|
||||
```
|
||||
|
||||
涉及文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\MainActivity.java` | 主界面控制 |
|
||||
| `YX_BlueTooth\src\main\res\layout\activity_main.xml` | 主界面布局 |
|
||||
| `YX_BlueTooth\src\main\res\layout\fragment_call_logs.xml` | 通话记录 |
|
||||
| `YX_BlueTooth\src\main\res\layout\fragment_contacts.xml` | 联系人 |
|
||||
|
||||
### 阶段 3:电话核心流程
|
||||
|
||||
先保证最基础电话链路可用:
|
||||
|
||||
```text
|
||||
输入号码
|
||||
-> 点击拨号
|
||||
-> 进入拨号中
|
||||
-> 对方接听
|
||||
-> 通话中
|
||||
-> 挂断
|
||||
-> 返回/关闭通话页
|
||||
```
|
||||
|
||||
再补:
|
||||
|
||||
- 来电接听
|
||||
- 来电拒接
|
||||
- 通话中静音
|
||||
- 车机/手机音频切换
|
||||
- DTMF 小键盘
|
||||
- 未连接时禁止拨号并提示
|
||||
|
||||
涉及文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `BluetoothPhoneActivity.java` | 通话全屏页 |
|
||||
| `BluetoothPhoneViewModel.java` | 电话动作和状态 |
|
||||
| `TelephoneWindowControl.java` | 电话窗口 |
|
||||
| `view_keyboard_dial.xml` | 拨号键盘 |
|
||||
| `view_keyboard_dtmf.xml` | DTMF 键盘 |
|
||||
| `view_control_calling.xml` | 通话控制按钮 |
|
||||
|
||||
验收重点:
|
||||
|
||||
- 来电显示号码/联系人名称
|
||||
- 拨出显示号码/联系人名称
|
||||
- 通话计时正确
|
||||
- 静音图标状态正确
|
||||
- 车机/手机音频图标状态正确
|
||||
- DTMF 按键能发出
|
||||
- 断开蓝牙时通话页正确关闭或提示
|
||||
|
||||
### 阶段 4:联系人和通话记录
|
||||
|
||||
联系人:
|
||||
|
||||
- 未同步状态
|
||||
- 一键同步
|
||||
- 正在同步
|
||||
- 同步成功
|
||||
- 同步失败
|
||||
- 空联系人
|
||||
- 搜索联系人
|
||||
- 搜索无结果
|
||||
- 重新下载联系人确认弹窗
|
||||
- 点击联系人拨号
|
||||
|
||||
通话记录:
|
||||
|
||||
- 未同步状态
|
||||
- 一键同步
|
||||
- 正在同步
|
||||
- 同步成功
|
||||
- 暂无记录
|
||||
- 全部/拨出/已接/未接分类
|
||||
- 点击通话记录拨号
|
||||
|
||||
涉及文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `ContactsFragment.java` | 联系人 UI |
|
||||
| `ContactListViewModel.java` | 联系人业务 |
|
||||
| `CallLogsFragment.java` | 通话记录 UI |
|
||||
| `CallHistoryViewModel.java` | 通话记录业务 |
|
||||
| `listitem_phonebook.xml` | 联系人条目 |
|
||||
| `listitem_phonerecord.xml` | 通话记录条目 |
|
||||
|
||||
验收重点:
|
||||
|
||||
- 蓝牙未连接时显示正确状态
|
||||
- 手机未授权 PBAP 时提示合理
|
||||
- 同步中不能重复触发多次
|
||||
- 搜索支持名称、号码、拼音/首字母
|
||||
- 点击条目拨号行为一致
|
||||
|
||||
### 阶段 5:三方通话专项
|
||||
|
||||
老项目明确支持第二路来电/三方通话相关行为:
|
||||
|
||||
- `SECOND_PHONE_IN`
|
||||
- `SECOND_PHONE_PICKUP_OR_REJECT`
|
||||
- `STATUS_WAITING`
|
||||
- `STATUS_HELD`
|
||||
- `EnhancedCallCtrl`
|
||||
- `RELEASE_ACTIVE_AND_ACCEPT_OTHER`
|
||||
|
||||
新项目已有承接:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `ThirdCallingControl.java` | 三方通话/第二路来电控制 |
|
||||
| `ThirdCallControlView.java` | 三方通话控制视图 |
|
||||
| `BluetoothPhoneViewModel.java` | 多通话状态处理 |
|
||||
| `BluetoothPhoneActivity.java` | 通话状态显示 |
|
||||
|
||||
必须单独验收以下场景:
|
||||
|
||||
1. 无通话时来电。
|
||||
2. 通话中第二路来电。
|
||||
3. 拒接第二路来电。
|
||||
4. 接听第二路并挂断当前。
|
||||
5. 接听第二路并保持当前。
|
||||
6. 当前一路 active,另一路 held。
|
||||
7. 挂断 active 后 held 是否恢复。
|
||||
8. 第二路来电号码/名称显示是否正确。
|
||||
|
||||
风险:
|
||||
|
||||
```text
|
||||
三方通话最依赖手机、HFP 协议栈和平台蓝牙实现,不能只靠静态代码判断。
|
||||
必须用真实手机或稳定蓝牙测试设备验证。
|
||||
```
|
||||
|
||||
### 阶段 6:蓝牙音乐
|
||||
|
||||
需要先统一入口:
|
||||
|
||||
```text
|
||||
蓝牙音乐入口到底打开 YX_BtMusic,还是 YX_BlueTooth 内的 BluetoothMusicActivity?
|
||||
```
|
||||
|
||||
建议:
|
||||
|
||||
- 如果项目已有独立 `YX_BtMusic` 桌面入口,主入口可以跳转 `YX_BtMusic`。
|
||||
- 如果要求完全贴近老项目“蓝牙 App 内四项菜单”,可以在 `YX_BlueTooth` 内保留音乐页面或 Activity。
|
||||
- 不建议两个入口长期并存不同逻辑,应统一 ViewModel 和 SDK 状态来源。
|
||||
|
||||
功能验收:
|
||||
|
||||
- 未连接 A2DP 时显示未连接状态
|
||||
- 点击连接按钮进入设置或连接流程
|
||||
- 标题/歌手/封面刷新
|
||||
- 播放/暂停状态刷新
|
||||
- 上一首/下一首可用
|
||||
- 进度和总时长显示
|
||||
- 手机端切歌后车机 UI 同步
|
||||
|
||||
涉及文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `YX_BtMusic\src\main\java\com\yaxon\btmusic\main\MainActivity.java` | 独立蓝牙音乐页 |
|
||||
| `YX_BtMusic\src\main\java\com\yaxon\btmusic\mvvm\BtMusicViewModel.java` | 独立音乐业务 |
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothMusicActivity.java` | 蓝牙模块内音乐页 |
|
||||
| `BtMusicControlManager` | 蓝牙音乐控制 |
|
||||
|
||||
### 阶段 7:蓝牙设置和设备管理
|
||||
|
||||
截图里的蓝牙设置是页面式:
|
||||
|
||||
- 自动接听
|
||||
- 蓝牙开关
|
||||
- 蓝牙名称
|
||||
- 已配对设备
|
||||
- 可用设备
|
||||
- 修改名称
|
||||
|
||||
新项目当前主要承接是 `BtSettingDialog`。
|
||||
|
||||
建议:
|
||||
|
||||
```text
|
||||
如果 UI 必须跟截图一致,应做成完整设置页面或主界面右侧设置页。
|
||||
如果只要求功能一致,可以先保留 BtSettingDialog,再统一视觉。
|
||||
```
|
||||
|
||||
功能验收:
|
||||
|
||||
- 蓝牙开关可打开/关闭
|
||||
- 本机蓝牙名称显示正确
|
||||
- 本机蓝牙名称可修改
|
||||
- 已配对设备显示正确
|
||||
- 可用设备扫描和刷新
|
||||
- 点击设备可配对/连接
|
||||
- 已连接设备可断开
|
||||
- 连接中、连接失败、连接成功状态正确
|
||||
- 自动接听开关生效
|
||||
|
||||
涉及文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `BtSettingDialog.java` | 设置 UI |
|
||||
| `BtSettingViewModel.java` | 设置业务 |
|
||||
| `dialog_bt_setting.xml` | 设置弹窗布局 |
|
||||
| `item_btdev_forum.xml` | 已配对设备条目 |
|
||||
| `item_btdev_scaned.xml` | 可用设备条目 |
|
||||
|
||||
### 阶段 8:Launcher/SystemUI 联动
|
||||
|
||||
需要纳入最终验收,而不是蓝牙 App 做完就结束。
|
||||
|
||||
Launcher:
|
||||
|
||||
- 蓝牙电话卡片显示连接/未连接
|
||||
- 蓝牙音乐卡片显示播放状态
|
||||
- 点击卡片打开正确 App
|
||||
|
||||
SystemUI:
|
||||
|
||||
- 状态栏蓝牙图标显示已连接/未连接
|
||||
- 下拉快捷蓝牙开关可打开/关闭
|
||||
- 快捷开关状态和蓝牙 App 状态一致
|
||||
|
||||
涉及文件:
|
||||
|
||||
| 文件 | 说明 |
|
||||
|---|---|
|
||||
| `YX_Launcher\src\main\res\layout\card_bluetooth.xml` | 蓝牙电话卡片 |
|
||||
| `YX_Launcher\src\main\res\layout\card_bluetoothmusic.xml` | 蓝牙音乐卡片 |
|
||||
| `YX_Launcher\src\main\java\com\yaxon\launcher\card\BluetoothCardView.java` | 蓝牙卡片 |
|
||||
| `YX_Launcher\src\main\java\com\yaxon\launcher\card\BluetoothMusicCardView.java` | 蓝牙音乐卡片 |
|
||||
| `YX_SystemUI` | 状态栏和快捷开关 |
|
||||
|
||||
## 5. 关键风险
|
||||
|
||||
| 风险 | 说明 | 建议 |
|
||||
|---|---|---|
|
||||
| UI 入口结构不一致 | 新项目入口结构和旧项目不同 | 先确认四项左侧菜单是否必须恢复 |
|
||||
| 蓝牙音乐双入口 | `YX_BtMusic` 和 `BluetoothMusicActivity` 并存 | 统一入口和状态来源 |
|
||||
| 设置形态不一致 | 截图是页面式,新项目是弹窗式 | 先确认产品要求 |
|
||||
| 三方通话复杂 | 依赖手机、协议栈、平台实现 | 单独实测 |
|
||||
| PBAP 授权 | 手机可能未授权联系人/通话记录 | 做未授权和同步失败提示 |
|
||||
| SystemUI 状态同步 | 蓝牙开关和连接状态可能不同步 | 最后做系统级联动验收 |
|
||||
| 旧项目编码/架构老 | 直接搬代码风险高 | 只抽业务行为 |
|
||||
|
||||
## 6. 开发验收清单
|
||||
|
||||
### 6.1 蓝牙拨号
|
||||
|
||||
- 输入号码显示正确
|
||||
- 删除号码正确
|
||||
- 点击拨号进入拨出页
|
||||
- 未连接时不拨号并提示
|
||||
- 联系人匹配显示正确
|
||||
|
||||
### 6.2 通话流程
|
||||
|
||||
- 来电可接听
|
||||
- 来电可拒接
|
||||
- 拨出可挂断
|
||||
- 通话中可挂断
|
||||
- 通话中计时正确
|
||||
- 静音可切换
|
||||
- 车机/手机音频可切换
|
||||
- DTMF 可发送
|
||||
|
||||
### 6.3 三方通话
|
||||
|
||||
- 第二路来电能显示
|
||||
- 第二路来电可拒接
|
||||
- 第二路来电可接听
|
||||
- 当前通话可保持
|
||||
- 当前通话可释放并接听另一通
|
||||
- held/active 状态显示正确
|
||||
|
||||
### 6.4 联系人
|
||||
|
||||
- 未同步状态正确
|
||||
- 同步中状态正确
|
||||
- 同步成功列表正确
|
||||
- 同步失败提示正确
|
||||
- 搜索正确
|
||||
- 重新下载弹窗正确
|
||||
- 点击联系人拨号正确
|
||||
|
||||
### 6.5 通话记录
|
||||
|
||||
- 未同步状态正确
|
||||
- 同步中状态正确
|
||||
- 暂无记录状态正确
|
||||
- 记录列表正确
|
||||
- 记录类型图标正确
|
||||
- 点击记录拨号正确
|
||||
|
||||
### 6.6 蓝牙音乐
|
||||
|
||||
- 未连接状态正确
|
||||
- 播放/暂停正确
|
||||
- 上一首/下一首正确
|
||||
- 标题/歌手/封面正确
|
||||
- 进度/总时长正确
|
||||
- 手机端状态变化能同步
|
||||
|
||||
### 6.7 蓝牙设置
|
||||
|
||||
- 蓝牙开关正确
|
||||
- 蓝牙名称显示正确
|
||||
- 蓝牙名称可修改
|
||||
- 已配对设备列表正确
|
||||
- 可用设备扫描正确
|
||||
- 连接/断开正确
|
||||
- 连接失败/超时提示正确
|
||||
- 自动接听生效
|
||||
|
||||
### 6.8 系统联动
|
||||
|
||||
- 桌面蓝牙卡片状态正确
|
||||
- 桌面蓝牙音乐卡片状态正确
|
||||
- 状态栏蓝牙图标正确
|
||||
- 下拉快捷蓝牙开关正确
|
||||
- 开机后蓝牙服务正常启动
|
||||
|
||||
## 7. 建议下一步
|
||||
|
||||
下一步不要直接改代码,建议先输出两份更细文档:
|
||||
|
||||
1. `蓝牙页面资产清单`
|
||||
- 页面
|
||||
- 弹窗
|
||||
- 图标
|
||||
- 按钮
|
||||
- 空状态
|
||||
- 加载状态
|
||||
- 错误状态
|
||||
|
||||
2. `蓝牙新旧功能缺口表`
|
||||
- 老项目能力
|
||||
- 新项目现状
|
||||
- 缺口
|
||||
- 影响文件
|
||||
- 优先级
|
||||
- 验收方式
|
||||
|
||||
确认后再进入代码开发。
|
||||
|
||||
## 8. 开发前必须确认
|
||||
|
||||
进入代码修改前,需要再次确认:
|
||||
|
||||
1. 是否备份。
|
||||
2. 是否要求 UI 像素级复刻截图。
|
||||
3. 蓝牙音乐入口以哪个模块为准。
|
||||
4. 蓝牙设置使用页面式还是弹窗式。
|
||||
5. 是否必须覆盖方向盘、语音、ITS/EasyConn 外部控制。
|
||||
@@ -0,0 +1,840 @@
|
||||
# 通力蓝牙模块整体流程与开发指南
|
||||
|
||||
> 代码仓库:`F:\zdCode\tongli`
|
||||
> 文档目的:帮助开发者理解“蓝牙模块”和“蓝牙音乐”的真实关系,熟悉整体功能链路,后续能独立开发、定位和修改问题。
|
||||
> 当前版本重点:先讲清楚蓝牙模块底座,再讲蓝牙音乐为什么依赖蓝牙模块。
|
||||
|
||||
---
|
||||
|
||||
## 0. 先给结论
|
||||
|
||||
### 0.1 蓝牙音乐和蓝牙模块是什么关系?
|
||||
|
||||
**蓝牙音乐是蓝牙模块里的一个媒体子功能。**
|
||||
|
||||
它看起来有独立 App 入口 `YX_BtMusic`,也在主蓝牙 App `YX_BlueTooth` 里有音乐页,但底层不是独立播放器,而是依赖蓝牙模块提供的连接能力。
|
||||
|
||||
```text
|
||||
蓝牙模块 = 蓝牙能力底座
|
||||
├─ 蓝牙开关
|
||||
├─ 设备扫描
|
||||
├─ 设备配对
|
||||
├─ 手机连接/断开
|
||||
├─ HFP 蓝牙电话
|
||||
├─ PBAP 联系人
|
||||
├─ MAP 消息
|
||||
└─ A2DP + AVRCP 蓝牙音乐
|
||||
```
|
||||
|
||||
蓝牙音乐必须依赖:
|
||||
|
||||
| 依赖 | 作用 |
|
||||
|---|---|
|
||||
| 蓝牙连接 | 手机必须先连上 |
|
||||
| A2DP Sink | 手机音乐声音通过 A2DP 进车机 |
|
||||
| AVRCP | 控制手机音乐播放/暂停/上一首/下一首,接收歌名 |
|
||||
| AudioFocus | 和收音机、导航、电话、语音等抢音频焦点 |
|
||||
| DataCenter | 把音乐状态同步给 Launcher/媒体卡片 |
|
||||
|
||||
所以开发时要这样定性:
|
||||
|
||||
```text
|
||||
蓝牙设置模块:负责“手机能不能连上”
|
||||
蓝牙电话模块:负责“HFP 电话能不能用”
|
||||
蓝牙音乐模块:负责“连上后手机音乐能不能播”
|
||||
音频焦点模块:负责“当前该谁出声”
|
||||
DataCenter:负责“状态给谁看、谁能反控”
|
||||
```
|
||||
|
||||
### 0.2 为什么容易误解为两个模块?
|
||||
|
||||
因为代码里有两个入口:
|
||||
|
||||
| 入口 | 说明 |
|
||||
|---|---|
|
||||
| `YX_BlueTooth\...\BluetoothMusicActivity.java` | 主蓝牙 App 内置蓝牙音乐页 |
|
||||
| `YX_BtMusic\...\MainActivity.java` | 独立蓝牙音乐 App |
|
||||
|
||||
但它们底层都走同一套 SDK:
|
||||
|
||||
```text
|
||||
BtMusicViewModel
|
||||
-> BtMusicControlManager
|
||||
-> BtMusicManager
|
||||
-> BluetoothAvrcpClient / BluetoothA2dpSinkClient
|
||||
```
|
||||
|
||||
所以是:
|
||||
|
||||
```text
|
||||
入口分开
|
||||
底层同源
|
||||
能力依赖蓝牙模块
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. 整体架构
|
||||
|
||||
### 1.1 一张小图看全局
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["页面/服务层<br/>YX_BlueTooth / YX_BtMusic"]
|
||||
B["SDK 门面层<br/>ControlManager"]
|
||||
C["业务模块层<br/>Setting / Phone / Music"]
|
||||
D["平台封装层<br/>HFP / A2DP / AVRCP / PBAP / MAP"]
|
||||
E["Android 蓝牙栈"]
|
||||
F["DataCenter<br/>DataManager / DataUtils"]
|
||||
G["Launcher / 媒体卡片 / 其他模块"]
|
||||
|
||||
A --> B --> C --> D --> E
|
||||
C --> F --> G
|
||||
```
|
||||
|
||||
### 1.2 分层职责
|
||||
|
||||
| 层级 | 代码位置 | 作用 |
|
||||
|---|---|---|
|
||||
| 页面/服务层 | `YX_BlueTooth`、`YX_BtMusic` | Activity、Fragment、ViewModel、Service、Receiver |
|
||||
| SDK 门面层 | `YX_BluetoothSDK\...\manager` | 给 App 层提供统一 API |
|
||||
| 业务模块层 | `YX_BluetoothSDK\...\modules` | 组织蓝牙业务逻辑 |
|
||||
| 平台封装层 | `YX_BluetoothSDK\...\platform` | 封装 Android 蓝牙 Profile |
|
||||
| 音频焦点层 | `YX_BluetoothSDK\...\audio` | 蓝牙音乐音频焦点、静音、反控 |
|
||||
| 状态总线层 | `YX_Support` / `YX_DataCenterService` | 跨模块共享蓝牙状态 |
|
||||
|
||||
### 1.3 关键工程模块
|
||||
|
||||
| Module | 定位 |
|
||||
|---|---|
|
||||
| `YX_BlueTooth` | 主蓝牙应用,包含设置、电话、联系人、通话记录、蓝牙音乐页和后台服务 |
|
||||
| `YX_BtMusic` | 独立蓝牙音乐入口 |
|
||||
| `YX_BluetoothSDK` | 蓝牙核心 SDK |
|
||||
| `YX_Support` | `DataUtils`、`DataManager` 等共享工具 |
|
||||
| `YX_DataCenterService` | DataCenter 状态服务 |
|
||||
| `YX_Launcher` | 使用蓝牙状态、展示蓝牙卡片、可能反控音乐 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 蓝牙模块启动主线
|
||||
|
||||
### 2.1 启动流程
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["BluetoothApplication.onCreate"]
|
||||
B["BluetoothSdkApplication.onCreate"]
|
||||
C["初始化 ControlManager"]
|
||||
D["启动 BluetoothService"]
|
||||
E["BootReceiver 开机启动兜底"]
|
||||
|
||||
A --> B --> C
|
||||
A --> D
|
||||
E --> D
|
||||
```
|
||||
|
||||
### 2.2 步骤说明
|
||||
|
||||
| 步骤 | 文件/类 | 做什么 |
|
||||
|---|---|---|
|
||||
| 1 | `BluetoothApplication` | 主蓝牙 App 启动 |
|
||||
| 2 | `BluetoothSdkApplication` | 初始化 SDK 管理器 |
|
||||
| 3 | `BtContactsControlManager` | 初始化联系人门面 |
|
||||
| 4 | `BtSettingControlManager` | 初始化蓝牙设置门面 |
|
||||
| 5 | `BtPhoneControlManager` | 初始化蓝牙电话门面 |
|
||||
| 6 | `BtCallRecordsControlManager` | 初始化通话记录门面 |
|
||||
| 7 | `BtMusicControlManager` | 初始化蓝牙音乐门面 |
|
||||
| 8 | `BtVoiceControlManager` | 初始化语音蓝牙控制门面 |
|
||||
| 9 | `BluetoothService` | 处理电话、外部命令、悬浮窗 |
|
||||
| 10 | `BootReceiver` | 开机后兜底拉起蓝牙服务 |
|
||||
|
||||
### 2.3 `BluetoothService` 的角色
|
||||
|
||||
`BluetoothService` 不是普通空服务,它是蓝牙电话和外部命令的编排中心。
|
||||
|
||||
它负责:
|
||||
|
||||
- 注册电话模块监听。
|
||||
- 注册设置模块监听。
|
||||
- 注册联系人弹窗相关监听。
|
||||
- 接收外部拨号、挂断、接听命令。
|
||||
- 接收方向盘按键。
|
||||
- 处理来电时电话页/悬浮窗显示。
|
||||
- 处理自动接听。
|
||||
- 避让倒车、AVM 等特殊页面。
|
||||
|
||||
---
|
||||
|
||||
## 3. 蓝牙连接底座
|
||||
|
||||
### 3.1 为什么先看连接?
|
||||
|
||||
因为其他功能都依赖连接状态。
|
||||
|
||||
```text
|
||||
手机没连上
|
||||
-> HFP 电话不可用
|
||||
-> PBAP 联系人不可同步
|
||||
-> A2DP 音乐没声音
|
||||
-> AVRCP 控制拿不到歌名/播放状态
|
||||
```
|
||||
|
||||
### 3.2 设置连接模块职责
|
||||
|
||||
核心类:`BtSettingManager`
|
||||
|
||||
| 功能 | 说明 |
|
||||
|---|---|
|
||||
| 蓝牙开关 | 打开/关闭 BluetoothAdapter |
|
||||
| 设备扫描 | 搜索附近手机或蓝牙设备 |
|
||||
| 设备配对 | 处理绑定、配对请求、配对状态 |
|
||||
| 连接手机 | 建立 HFP、A2DP、PBAP、MAP 等 Profile |
|
||||
| 断开手机 | 断开 HFP、A2DP、PBAP、MAP |
|
||||
| 查询状态 | 当前是否打开、是否连接、是否连接中 |
|
||||
| 耳机 A2DP | 支持外部蓝牙耳机相关连接 |
|
||||
|
||||
### 3.3 连接手机小流程
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["用户点击设备"]
|
||||
B["SettingViewModel"]
|
||||
C["BtSettingControlManager"]
|
||||
D["BtSettingManager.connectPhoneDevice"]
|
||||
E["连接 A2DP + HFP"]
|
||||
F["连接状态广播/回调"]
|
||||
G["页面与 DataCenter 刷新"]
|
||||
|
||||
A --> B --> C --> D --> E --> F --> G
|
||||
```
|
||||
|
||||
### 3.4 连接手机详细链路
|
||||
|
||||
```text
|
||||
MainActivity / 设置弹窗
|
||||
-> SettingViewModel 或 BtSettingViewModel
|
||||
-> BtSettingControlManager.connectPhoneDevice(linkedInfo)
|
||||
-> BtSettingManager.connectPhoneDevice(linkedInfo)
|
||||
-> BluetoothA2dpSinkClient.connectA2dp(mac)
|
||||
-> BluetoothHfpClient.connectHfp(mac)
|
||||
-> PBAP/MAP 后续连接或同步
|
||||
-> BluetoothConnectListenerClient 收到状态
|
||||
-> IBtSettingModuleListener 回调
|
||||
-> ViewModel LiveData 刷新页面
|
||||
```
|
||||
|
||||
### 3.5 多 Profile 的含义
|
||||
|
||||
连接手机不是一个单点动作,而是多个蓝牙 Profile 共同组成。
|
||||
|
||||
| Profile | 作用 | 对应功能 |
|
||||
|---|---|---|
|
||||
| HFP | Hands-Free Profile | 蓝牙电话、通话音频 |
|
||||
| A2DP Sink | Advanced Audio Distribution Profile | 手机音乐音频流输入车机 |
|
||||
| AVRCP | Audio/Video Remote Control Profile | 控制手机音乐、读取歌曲信息 |
|
||||
| PBAP | Phone Book Access Profile | 联系人、电话本 |
|
||||
| MAP | Message Access Profile | 消息、短信相关能力 |
|
||||
|
||||
### 3.6 断开手机链路
|
||||
|
||||
```text
|
||||
BtSettingControlManager.disconnectPhoneDevice(mac)
|
||||
-> BtSettingManager.disconnectPhoneDevice(mac)
|
||||
-> BluetoothHfpClient.disconnectHfp(mac)
|
||||
-> BluetoothPbapClient.disconnectPbab(mac)
|
||||
-> BluetoothMapConnectClient.disconnectMap(mac)
|
||||
-> BluetoothA2dpSinkClient.disconnectA2dp(mac)
|
||||
-> 连接状态回调
|
||||
-> 页面刷新
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 蓝牙电话功能
|
||||
|
||||
### 4.1 电话依赖什么?
|
||||
|
||||
蓝牙电话依赖 HFP。
|
||||
|
||||
```text
|
||||
手机 HFP 连接成功
|
||||
-> 车机可以拨号/接听/挂断
|
||||
-> 可以拿到当前通话状态
|
||||
-> 可以处理通话音频
|
||||
```
|
||||
|
||||
核心链路:
|
||||
|
||||
```text
|
||||
BluetoothHfpClient
|
||||
-> BtPhoneManager
|
||||
-> BtPhoneControlManager
|
||||
-> BluetoothPhoneViewModel / BluetoothService
|
||||
-> BluetoothPhoneActivity / 电话悬浮窗
|
||||
```
|
||||
|
||||
### 4.2 拨号流程
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["电话页/联系人/通话记录/外部命令"]
|
||||
B["BluetoothPhoneViewModel 或 BluetoothService"]
|
||||
C["BtPhoneControlManager"]
|
||||
D["BtPhoneManager"]
|
||||
E["BluetoothHfpClient.dial"]
|
||||
F["HFP 通话状态回调"]
|
||||
G["电话界面刷新"]
|
||||
|
||||
A --> B --> C --> D --> E --> F --> G
|
||||
```
|
||||
|
||||
### 4.3 来电流程
|
||||
|
||||
```text
|
||||
手机来电
|
||||
-> BluetoothHfpClient 收到 HFP 状态
|
||||
-> BtPhoneManager 转换通话状态
|
||||
-> BluetoothService.onHfpCallChanged()
|
||||
-> 判断是否倒车/AVM/前台/自动接听
|
||||
-> 显示 BluetoothPhoneActivity 或电话悬浮窗
|
||||
-> BtInfoPushManager 推送 BT_INFO
|
||||
```
|
||||
|
||||
### 4.4 电话开发要重点看哪里?
|
||||
|
||||
| 场景 | 优先看 |
|
||||
|---|---|
|
||||
| 电话页面 UI | `BluetoothPhoneActivity` |
|
||||
| 电话页面状态 | `BluetoothPhoneViewModel` |
|
||||
| 来电弹窗/外部命令 | `BluetoothService` |
|
||||
| 拨号/接听/挂断 API | `BtPhoneControlManager` |
|
||||
| 电话业务核心 | `BtPhoneManager` |
|
||||
| HFP 底层状态 | `BluetoothHfpClient` |
|
||||
|
||||
---
|
||||
|
||||
## 5. 联系人与通话记录
|
||||
|
||||
### 5.1 联系人依赖什么?
|
||||
|
||||
联系人主要依赖 PBAP。
|
||||
|
||||
```text
|
||||
手机连接成功
|
||||
-> PBAP 可用
|
||||
-> 拉取手机电话本
|
||||
-> 本地整理
|
||||
-> 联系人页面展示
|
||||
```
|
||||
|
||||
核心链路:
|
||||
|
||||
```text
|
||||
BluetoothPbapClient
|
||||
-> BtContactsManager
|
||||
-> BtContactsControlManager
|
||||
-> ContactListViewModel
|
||||
-> ContactsFragment
|
||||
```
|
||||
|
||||
### 5.2 通话记录依赖什么?
|
||||
|
||||
通话记录依赖 PBAP/HFP 相关数据。
|
||||
|
||||
```text
|
||||
手机连接成功
|
||||
-> 通话记录数据可用
|
||||
-> BtCallRecordsManager 拉取/整理
|
||||
-> CallHistoryViewModel
|
||||
-> CallLogsFragment
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 蓝牙音乐功能
|
||||
|
||||
### 6.1 蓝牙音乐的本质
|
||||
|
||||
蓝牙音乐不是车机本地播放器,而是“车机控制手机音乐 + 接收手机音频流”。
|
||||
|
||||
```text
|
||||
手机负责播放音乐
|
||||
车机负责:
|
||||
1. 通过 A2DP 接收声音
|
||||
2. 通过 AVRCP 控制播放
|
||||
3. 通过 AVRCP 获取歌曲信息
|
||||
4. 通过 AudioFocus 决定是否出声
|
||||
```
|
||||
|
||||
### 6.2 蓝牙音乐必须满足的前置条件
|
||||
|
||||
| 前置条件 | 不满足时表现 |
|
||||
|---|---|
|
||||
| 手机已连接 | 蓝牙音乐不可用 |
|
||||
| A2DP Sink 已连接 | 没有手机音乐声音输入 |
|
||||
| AVRCP 可用 | 按钮可能无效,歌名可能不刷新 |
|
||||
| AudioFocus 成功 | 有状态但可能没声音 |
|
||||
| 未被电话/语音/导航抢占 | 可能暂停、静音或降低音量 |
|
||||
|
||||
### 6.3 蓝牙音乐两个入口
|
||||
|
||||
| 入口 | 文件 | 说明 |
|
||||
|---|---|---|
|
||||
| 主蓝牙 App 音乐页 | `YX_BlueTooth\...\BluetoothMusicActivity.java` | 集成在主蓝牙应用 |
|
||||
| 独立音乐 App | `YX_BtMusic\...\MainActivity.java` | 独立启动入口 |
|
||||
|
||||
底层调用一致:
|
||||
|
||||
```text
|
||||
BtMusicViewModel
|
||||
-> BtMusicControlManager
|
||||
-> BtMusicManager
|
||||
-> BluetoothAvrcpClient / BluetoothA2dpSinkClient / BtMusicAudioManager
|
||||
```
|
||||
|
||||
### 6.4 播放控制流程
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["用户点击播放/暂停/上一首/下一首"]
|
||||
B["BtMusicViewModel"]
|
||||
C["BtMusicControlManager"]
|
||||
D["BtMusicManager"]
|
||||
E["BtMusicAudioManager 请求焦点"]
|
||||
F["BluetoothAvrcpClient 控制手机"]
|
||||
G["手机音乐 App"]
|
||||
|
||||
A --> B --> C --> D --> E --> F --> G
|
||||
```
|
||||
|
||||
### 6.5 播放控制详细链路
|
||||
|
||||
```text
|
||||
BluetoothMusicActivity / YX_BtMusic MainActivity
|
||||
-> BtMusicViewModel.playBtMusic/pauseBtMusic/preBtMusic/nextBtMusic
|
||||
-> BtMusicControlManager
|
||||
-> BtMusicManager
|
||||
-> 播放前 requestAudioFocus()
|
||||
-> BluetoothAvrcpClient.play/pause/previous/next
|
||||
-> 手机音乐 App 执行
|
||||
```
|
||||
|
||||
### 6.6 歌曲信息流程
|
||||
|
||||
```text
|
||||
手机音乐元数据变化
|
||||
-> BluetoothAvrcpClient.onMetadataChanged()
|
||||
-> BtMusicManager.onMetadataChanged()
|
||||
-> MusicInfo(title/artist/album/albumUrl/duration)
|
||||
-> BtMusicViewModel.mLiveDataMusicInfo
|
||||
-> 蓝牙音乐页面刷新
|
||||
-> BtInfoPushManager.sendMusicInfoToLauncher()
|
||||
-> DataManager 写 BT_MUSIC_INFO
|
||||
-> Launcher 蓝牙音乐卡片刷新
|
||||
```
|
||||
|
||||
### 6.7 播放状态流程
|
||||
|
||||
```text
|
||||
手机播放/暂停状态变化
|
||||
-> BluetoothAvrcpClient.onPlaybackStateChanged()
|
||||
-> BtMusicManager.onPlaybackStateChanged()
|
||||
-> BtMusicViewModel.mLiveDataPlayState
|
||||
-> 播放按钮刷新
|
||||
-> BtInfoPushManager.sendMusicStateToLauncher()
|
||||
-> DataManager 写 BT_MUSIC_INFO
|
||||
```
|
||||
|
||||
### 6.8 A2DP 连接状态流程
|
||||
|
||||
```text
|
||||
BluetoothConnectListenerClient 收到 A2DP 连接
|
||||
-> BtMusicManager.onA2dpConnect()
|
||||
-> 如果未获得音频焦点,先 setA2dpMute()
|
||||
-> BtMusicViewModel.mLiveDataA2dpConnectStatus = true
|
||||
-> 页面显示已连接
|
||||
```
|
||||
|
||||
断开时:
|
||||
|
||||
```text
|
||||
A2DP 断开
|
||||
-> BtMusicManager.onA2dpDisconnect()
|
||||
-> 清理音乐状态
|
||||
-> BtMusicViewModel.mLiveDataA2dpConnectStatus = false
|
||||
-> 页面显示未连接
|
||||
```
|
||||
|
||||
### 6.9 音频焦点流程
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["准备播放蓝牙音乐"]
|
||||
B["BtMusicAudioManager.requestAudioFocus"]
|
||||
C{"是否拿到焦点"}
|
||||
D["A2DP unmute"]
|
||||
E["允许 AVRCP play"]
|
||||
F["A2DP mute / pause"]
|
||||
G["清理或更新 BT_MUSIC_INFO"]
|
||||
|
||||
A --> B --> C
|
||||
C -->|是| D --> E
|
||||
C -->|否| F --> G
|
||||
```
|
||||
|
||||
焦点事件:
|
||||
|
||||
| 事件 | 处理 |
|
||||
|---|---|
|
||||
| `AUDIOFOCUS_GAIN` | A2DP unmute,必要时恢复播放 |
|
||||
| `AUDIOFOCUS_LOSS` | 释放焦点、A2DP mute、暂停、清理音乐信息 |
|
||||
| `AUDIOFOCUS_LOSS_TRANSIENT` | 临时 mute、暂停 |
|
||||
| `AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK` | 降低 A2DP 音量 |
|
||||
|
||||
### 6.10 电话与蓝牙音乐的关系
|
||||
|
||||
电话和蓝牙音乐都属于蓝牙模块,但使用不同 Profile:
|
||||
|
||||
| 功能 | Profile |
|
||||
|---|---|
|
||||
| 蓝牙电话 | HFP |
|
||||
| 蓝牙音乐音频 | A2DP Sink |
|
||||
| 蓝牙音乐控制/歌名 | AVRCP |
|
||||
|
||||
来电或通话时,电话通常会影响蓝牙音乐:
|
||||
|
||||
```text
|
||||
来电/通话
|
||||
-> 电话或系统音频焦点抢占
|
||||
-> 蓝牙音乐 AUDIOFOCUS_LOSS 或 TRANSIENT
|
||||
-> A2DP mute / pause
|
||||
-> 通话结束后按策略恢复
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. DataCenter 状态同步
|
||||
|
||||
### 7.1 为什么要有 DataCenter?
|
||||
|
||||
蓝牙模块不只给自己页面用,还要给 Launcher、媒体卡片、语音、其他系统模块用。
|
||||
|
||||
所以蓝牙状态会写入 DataCenter。
|
||||
|
||||
### 7.2 关键状态
|
||||
|
||||
定义位置:`YX_Support\src\main\java\com\yaxon\support\utils\DataUtils.java`
|
||||
|
||||
| Key | 作用 |
|
||||
|---|---|
|
||||
| `BT_INFO` | 蓝牙开关、连接、电话等综合状态 |
|
||||
| `BT_MUSIC_INFO` | 蓝牙音乐歌名、歌手、播放状态、进度、时长 |
|
||||
| `MUSIC_BT_CONTROL_STATE` | Launcher 反控蓝牙音乐 |
|
||||
| `SOURCE_ID` | 当前音源标识 |
|
||||
|
||||
### 7.3 电话状态推送
|
||||
|
||||
```text
|
||||
BtPhoneManager / BtSettingManager
|
||||
-> BtInfoPushManager.sendBtInfo()
|
||||
-> DataManager.putObject(MEDIA_URI, BT_INFO, btInfo)
|
||||
-> Launcher / 其他模块读取
|
||||
```
|
||||
|
||||
### 7.4 音乐状态推送
|
||||
|
||||
```text
|
||||
BluetoothAvrcpClient 收到播放状态或歌曲信息
|
||||
-> BtInfoPushManager.sendMusicStateToLauncher()
|
||||
-> BtInfoPushManager.sendMusicInfoToLauncher()
|
||||
-> DataManager.putObject(MEDIA_URI, BT_MUSIC_INFO, btMusicInfo)
|
||||
-> Launcher 蓝牙音乐卡片刷新
|
||||
```
|
||||
|
||||
### 7.5 Launcher 反控蓝牙音乐
|
||||
|
||||
```text
|
||||
Launcher 写 MUSIC_BT_CONTROL_STATE
|
||||
-> BtMusicAudioManager.ContentObserver 触发
|
||||
-> 读取命令 PRE/NEXT/PLAY/PAUSE
|
||||
-> 调用 BtMusicManager
|
||||
-> BluetoothAvrcpClient 控制手机
|
||||
-> 重置 MUSIC_BT_CONTROL_STATE = -1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 开发入口速查
|
||||
|
||||
### 8.1 蓝牙设置/连接
|
||||
|
||||
| 目标 | 优先文件 |
|
||||
|---|---|
|
||||
| 页面入口 | `YX_BlueTooth\...\main\MainActivity.java` |
|
||||
| 设置状态 | `YX_BlueTooth\...\viewmodel\SettingViewModel.java` |
|
||||
| 设置弹窗状态 | `YX_BlueTooth\...\viewmodel\BtSettingViewModel.java` |
|
||||
| SDK 门面 | `YX_BluetoothSDK\...\manager\BtSettingControlManager.java` |
|
||||
| 连接核心 | `YX_BluetoothSDK\...\modules\BtSettingManager.java` |
|
||||
| 连接广播 | `YX_BluetoothSDK\...\platform\BluetoothConnectListenerClient.java` |
|
||||
| 扫描配对 | `YX_BluetoothSDK\...\platform\BluetoothDeviceClient.java` |
|
||||
|
||||
### 8.2 蓝牙电话
|
||||
|
||||
| 目标 | 优先文件 |
|
||||
|---|---|
|
||||
| 电话页面 | `YX_BlueTooth\...\main\BluetoothPhoneActivity.java` |
|
||||
| 电话状态 | `YX_BlueTooth\...\viewmodel\BluetoothPhoneViewModel.java` |
|
||||
| 来电/外部命令 | `YX_BlueTooth\...\service\BluetoothService.java` |
|
||||
| SDK 门面 | `YX_BluetoothSDK\...\manager\BtPhoneControlManager.java` |
|
||||
| 电话核心 | `YX_BluetoothSDK\...\modules\BtPhoneManager.java` |
|
||||
| HFP 底层 | `YX_BluetoothSDK\...\platform\BluetoothHfpClient.java` |
|
||||
|
||||
### 8.3 蓝牙音乐
|
||||
|
||||
| 目标 | 优先文件 |
|
||||
|---|---|
|
||||
| 主 App 音乐页 | `YX_BlueTooth\...\main\BluetoothMusicActivity.java` |
|
||||
| 主 App 音乐状态 | `YX_BlueTooth\...\viewmodel\BtMusicViewModel.java` |
|
||||
| 独立音乐页 | `YX_BtMusic\...\main\MainActivity.java` |
|
||||
| 独立音乐状态 | `YX_BtMusic\...\mvvm\BtMusicViewModel.java` |
|
||||
| SDK 门面 | `YX_BluetoothSDK\...\manager\BtMusicControlManager.java` |
|
||||
| 音乐核心 | `YX_BluetoothSDK\...\modules\BtMusicManager.java` |
|
||||
| 音频焦点 | `YX_BluetoothSDK\...\audio\BtMusicAudioManager.java` |
|
||||
| AVRCP | `YX_BluetoothSDK\...\platform\BluetoothAvrcpClient.java` |
|
||||
| A2DP Sink | `YX_BluetoothSDK\...\platform\BluetoothA2dpSinkClient.java` |
|
||||
|
||||
### 8.4 DataCenter / Launcher 卡片
|
||||
|
||||
| 目标 | 优先文件 |
|
||||
|---|---|
|
||||
| 蓝牙状态推送 | `YX_BluetoothSDK\...\modules\BtInfoPushManager.java` |
|
||||
| 状态 key | `YX_Support\...\utils\DataUtils.java` |
|
||||
| 状态读写 | `YX_Support\...\data\DataManager.java` |
|
||||
| 状态服务 | `YX_Support\...\data\DataManagerService.java` |
|
||||
| Launcher 展示 | 搜索 `BT_INFO`、`BT_MUSIC_INFO`、`MUSIC_BT_CONTROL_STATE` |
|
||||
|
||||
---
|
||||
|
||||
## 9. 常见问题定位路径
|
||||
|
||||
### 9.1 蓝牙打不开/关不上
|
||||
|
||||
```text
|
||||
BtSettingControlManager.openBluetooth/closeBluetooth
|
||||
-> BtSettingManager
|
||||
-> BluetoothAdapter.enable/disable
|
||||
-> BluetoothDeviceClient 广播
|
||||
-> IBtSettingModuleListener
|
||||
-> ViewModel LiveData
|
||||
-> BT_INFO
|
||||
```
|
||||
|
||||
### 9.2 手机连接失败
|
||||
|
||||
```text
|
||||
BtSettingManager.connectPhoneDevice()
|
||||
-> 当前是否已有设备
|
||||
-> connectA2dp(mac)
|
||||
-> connectHfp(mac)
|
||||
-> BluetoothConnectListenerClient 状态广播
|
||||
-> PBAP/MAP 后续状态
|
||||
-> 页面连接状态刷新
|
||||
```
|
||||
|
||||
### 9.3 来电不弹窗
|
||||
|
||||
```text
|
||||
BluetoothHfpClient
|
||||
-> BtPhoneManager.onHfpCallChanged
|
||||
-> BluetoothService.onHfpCallChanged
|
||||
-> 判断倒车/AVM/前台/自动接听
|
||||
-> BluetoothPhoneActivity 或悬浮窗
|
||||
```
|
||||
|
||||
### 9.4 蓝牙音乐按钮无效
|
||||
|
||||
```text
|
||||
Activity 点击
|
||||
-> BtMusicViewModel
|
||||
-> BtMusicControlManager
|
||||
-> BtMusicManager
|
||||
-> BluetoothAvrcpClient
|
||||
-> 手机音乐 App
|
||||
```
|
||||
|
||||
重点确认:
|
||||
|
||||
- AVRCP 是否连接。
|
||||
- 手机音乐 App 是否支持 AVRCP。
|
||||
- 当前是否有 AudioFocus。
|
||||
|
||||
### 9.5 蓝牙音乐无声音
|
||||
|
||||
```text
|
||||
A2DP Sink 是否连接
|
||||
-> AudioFocus 是否成功
|
||||
-> 是否被 setA2dpMute()
|
||||
-> 是否被电话/语音/导航抢焦点
|
||||
-> 当前音源 SOURCE_ID 是否正确
|
||||
```
|
||||
|
||||
### 9.6 歌名不刷新
|
||||
|
||||
```text
|
||||
BluetoothAvrcpClient.onMetadataChanged
|
||||
-> BtMusicManager.onMetadataChanged
|
||||
-> BtMusicViewModel.mLiveDataMusicInfo
|
||||
-> 蓝牙音乐页面
|
||||
-> BtInfoPushManager
|
||||
-> BT_MUSIC_INFO
|
||||
-> Launcher 卡片
|
||||
```
|
||||
|
||||
### 9.7 Launcher 反控无效
|
||||
|
||||
```text
|
||||
Launcher 写 MUSIC_BT_CONTROL_STATE
|
||||
-> BtMusicAudioManager.ContentObserver
|
||||
-> 命令值是否正确
|
||||
-> BtMusicManager 是否执行
|
||||
-> AVRCP 是否控制手机
|
||||
-> 是否重置为 -1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 当前代码风险点
|
||||
|
||||
### 10.1 `isA2dpSinkConnected()` 返回值疑似错误
|
||||
|
||||
现象:
|
||||
|
||||
```text
|
||||
BtMusicControlManager.isA2dpSinkConnected()
|
||||
调用了 mBtMusicManager.isA2dpSinkConnected()
|
||||
但没有 return 结果
|
||||
```
|
||||
|
||||
同类问题也出现在 `BtMusicViewModel.isA2dpSinkConnected()`。
|
||||
|
||||
可能影响:
|
||||
|
||||
- 页面一直认为 A2DP 未连接。
|
||||
- 蓝牙音乐连接状态显示异常。
|
||||
- 自动播放逻辑异常。
|
||||
|
||||
### 10.2 `removeIBtMusicModuleListener()` 参数类型疑似错误
|
||||
|
||||
现象:
|
||||
|
||||
```text
|
||||
BtMusicManager.removeIBtMusicModuleListener(IBtSettingModuleListener listener)
|
||||
```
|
||||
|
||||
但音乐监听列表维护的是 `IBtMusicModuleListener`。
|
||||
|
||||
可能影响:
|
||||
|
||||
- 音乐监听无法正确移除。
|
||||
- 页面销毁后仍收到回调。
|
||||
- 重复回调或内存泄漏。
|
||||
|
||||
### 10.3 `BtMusicAudioManager` 的 `ContentObserver` 生命周期需要核对
|
||||
|
||||
现象:
|
||||
|
||||
```text
|
||||
注册 Launcher 反控 ContentObserver
|
||||
但需要确认 destroy() 中是否完整 unregister
|
||||
```
|
||||
|
||||
可能影响:
|
||||
|
||||
- 内存泄漏。
|
||||
- 反控命令重复触发。
|
||||
- 销毁后仍收到状态变化。
|
||||
|
||||
### 10.4 `BluetoothService` 的 `ContentObserver` 注销路径需要核对
|
||||
|
||||
现象:
|
||||
|
||||
```text
|
||||
onDestroy() 中 unregisterContentObserver(mContentObserver)
|
||||
但注册路径需要确认是否一定执行
|
||||
```
|
||||
|
||||
可能影响:
|
||||
|
||||
- 未注册就注销导致异常。
|
||||
- 服务销毁异常。
|
||||
|
||||
---
|
||||
|
||||
## 11. 开发建议
|
||||
|
||||
### 11.1 保持调用层级
|
||||
|
||||
推荐:
|
||||
|
||||
```text
|
||||
Activity / Fragment
|
||||
-> ViewModel
|
||||
-> ControlManager
|
||||
-> Manager
|
||||
-> Platform Client
|
||||
```
|
||||
|
||||
不推荐:
|
||||
|
||||
```text
|
||||
Activity 直接调用 BluetoothHfpClient
|
||||
Activity 直接调用 BluetoothAvrcpClient
|
||||
```
|
||||
|
||||
### 11.2 修改蓝牙音乐时同时确认三件事
|
||||
|
||||
| 检查项 | 原因 |
|
||||
|---|---|
|
||||
| AVRCP 是否可控 | 决定按钮和歌名 |
|
||||
| A2DP 是否连接 | 决定有没有手机音频流 |
|
||||
| AudioFocus 是否成功 | 决定车机是否允许出声 |
|
||||
|
||||
### 11.3 修改电话时必须同时看 Service
|
||||
|
||||
电话页不是唯一入口,下面这些都会走 `BluetoothService`:
|
||||
|
||||
- 来电
|
||||
- 挂断
|
||||
- 自动接听
|
||||
- 方向盘按键
|
||||
- 外部拨号
|
||||
- 电话悬浮窗
|
||||
- 倒车/AVM 避让
|
||||
|
||||
### 11.4 修改状态展示时必须看 DataCenter
|
||||
|
||||
| 状态 | Key |
|
||||
|---|---|
|
||||
| 电话/连接状态 | `BT_INFO` |
|
||||
| 蓝牙音乐状态 | `BT_MUSIC_INFO` |
|
||||
| Launcher 反控命令 | `MUSIC_BT_CONTROL_STATE` |
|
||||
|
||||
---
|
||||
|
||||
## 12. 建议读代码顺序
|
||||
|
||||
| 顺序 | 文件/类 | 读它的目的 |
|
||||
|---|---|---|
|
||||
| 1 | `settings.gradle` | 确认蓝牙相关 module |
|
||||
| 2 | `BluetoothSdkApplication` | 理解 SDK 初始化 |
|
||||
| 3 | `BluetoothApplication` | 理解主蓝牙 App 启动 |
|
||||
| 4 | `BootReceiver` | 理解开机启动 |
|
||||
| 5 | `BluetoothService` | 理解电话和外部命令编排 |
|
||||
| 6 | `BtSettingManager` | 理解连接底座 |
|
||||
| 7 | `BluetoothConnectListenerClient` | 理解连接状态来源 |
|
||||
| 8 | `BtPhoneManager` / `BluetoothHfpClient` | 理解蓝牙电话 |
|
||||
| 9 | `BtMusicManager` / `BtMusicAudioManager` | 理解蓝牙音乐和焦点 |
|
||||
| 10 | `BluetoothAvrcpClient` / `BluetoothA2dpSinkClient` | 理解音乐控制和音频通道 |
|
||||
| 11 | `BtInfoPushManager` / `DataUtils` | 理解跨模块状态同步 |
|
||||
| 12 | 各 Activity / ViewModel | 理解页面如何消费状态 |
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
刷机重启 adb reboot loader
|
||||
|
||||
blink资源修改需要改如下3个地方:
|
||||
|
||||
1.**blink资源替换路径:**
|
||||
Z:\znzc\028_shanxi_tongli\android14.0_rk3562\device\rockchip\rk3562\rk3562_u\firmware\w260\blink
|
||||
|
||||
Y:\tongli\android14.0_rk3562\device\rockchip\rk3562\rk3562_u\firmware\w260\blink
|
||||
|
||||
**2.mk文件路径:**
|
||||
Z:\znzc\028_shanxi_tongli\android14.0_rk3562\device\rockchip\rk3562
|
||||
|
||||
3.bp文件路径:
|
||||
Z:\znzc\028_shanxi_tongli\android14.0_rk3562\device\rockchip\rk3562\rk3562_u\firmware\w260\blink\Android.bp
|
||||
|
||||
adb shell dumpsys appwidget | Select-String "com.yaxon.bluetooth|MusicWidget"
|
||||
|
||||
|
||||
源码git出问题三步走:
|
||||
rm -f .git/index
|
||||
git read-tree HEAD
|
||||
git status
|
||||
|
||||
rm -f .git/refs/notes/ai
|
||||
|
||||
查找源码: find . -name EXEC_KERNEL_IMAGE.bin
|
||||
grep . -rne wcnmodem
|
||||
|
||||
|
||||
|
||||
倒车:
|
||||
adb shell content insert --uri content://com.yaxon.data.provider/state/ --bind BACKCAR_STATE:i:1
|
||||
|
||||
退出倒车:
|
||||
adb shell content insert --uri content://com.yaxon.data.provider/state/ --bind BACKCAR_STATE:i:0
|
||||
|
||||
Z:\znzc\028_shanxi_tongli\android14.0_rk3562\packages\modules\Bluetooth\android\app\src\com\android\bluetooth\btservice\AdapterService.java
|
||||
|
||||
|
||||
1.下载2300条的通讯录数据
|
||||
花费时间 2分40秒
|
||||
去掉头像 2分13秒 2分10秒
|
||||
精简字段: 2分15秒
|
||||
|
||||
app优化后:
|
||||
允辉:1分48秒
|
||||
|
||||
红米手机优化前 3分21秒 3分21秒 3分18秒
|
||||
红米手机优化后 2分26秒 2分30秒
|
||||
|
||||
通话列表限制50条 优化后:
|
||||
允辉:1分47秒 1分49秒 1分50秒
|
||||
红米手机: 1分50秒 2分15秒
|
||||
|
||||
2.下载250条通话记录数据
|
||||
花费时间 20秒
|
||||
去掉头像 16秒
|
||||
精简字段: 17秒 15秒
|
||||
|
||||
app 优化 15秒
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
# 00_总览_一页看懂
|
||||
|
||||
## 一句话理解
|
||||
|
||||
```text
|
||||
手机音乐 App 负责播放内容
|
||||
车机蓝牙音乐模块负责:
|
||||
1. 接收手机音乐声音
|
||||
2. 显示歌名、歌手、封面、进度
|
||||
3. 控制手机播放、暂停、上一曲、下一曲
|
||||
4. 和收音机、USB 音乐、电话、导航等音源抢焦点
|
||||
5. 把蓝牙音乐状态同步给 Launcher 卡片
|
||||
```
|
||||
|
||||
## 模块角色
|
||||
|
||||
| 角色 | 简单解释 | 关键类 |
|
||||
| ---------------- | ---------------------- | ------------------------- |
|
||||
| A2DP Sink | 声音通道,手机把音乐声音推给车机。 | `BluetoothA2dpSinkClient` |
|
||||
| AVRCP Controller | 控制通道,车机控制手机音乐,也接收歌曲信息。 | `BluetoothAvrcpClient` |
|
||||
| MediaBrowser | 连接 Android 蓝牙媒体服务。 | `MediaBrowserClient` |
|
||||
| 音频焦点 | 决定蓝牙音乐是否可以出声。 | `BtMusicAudioManager` |
|
||||
| 蓝牙音乐核心 | 汇总连接、控制、歌曲信息、焦点。 | `BtMusicManager` |
|
||||
| 页面 | 给用户看的蓝牙音乐界面。 | `BluetoothMusicActivity` |
|
||||
| Launcher 卡片 | 桌面小组件显示和控制蓝牙音乐。 | `BluetoothMusicCardView` |
|
||||
|
||||
## 总体关系
|
||||
|
||||
```text
|
||||
手机音乐 App
|
||||
│
|
||||
├─ 声音流:A2DP
|
||||
│ ↓
|
||||
│ BluetoothA2dpSinkClient
|
||||
│
|
||||
└─ 控制/歌曲信息:AVRCP
|
||||
↓
|
||||
MediaBrowserClient
|
||||
↓
|
||||
BluetoothAvrcpClient
|
||||
↓
|
||||
BtMusicManager
|
||||
↓
|
||||
BtMusicControlManager
|
||||
↓
|
||||
BtMusicViewModel
|
||||
↓
|
||||
BluetoothMusicActivity
|
||||
|
||||
BtMusicManager
|
||||
├─ 调用 BtMusicAudioManager 管理音频焦点
|
||||
└─ 调用 BtInfoPushManager 推送给 Launcher
|
||||
```
|
||||
|
||||
## 看代码时的方向
|
||||
|
||||
如果你想知道“点击按钮后发生了什么”,从上往下看:
|
||||
|
||||
```text
|
||||
BluetoothMusicActivity
|
||||
↓
|
||||
BtMusicViewModel
|
||||
↓
|
||||
BtMusicControlManager
|
||||
↓
|
||||
BtMusicManager
|
||||
↓
|
||||
BluetoothAvrcpClient
|
||||
```
|
||||
|
||||
如果你想知道“歌名歌手从哪里来”,从底层往页面看:
|
||||
|
||||
```text
|
||||
MediaBrowserClient
|
||||
↓
|
||||
BluetoothAvrcpClient
|
||||
↓
|
||||
BtMusicManager
|
||||
↓
|
||||
BtMusicViewModel
|
||||
↓
|
||||
BluetoothMusicActivity
|
||||
```
|
||||
|
||||
如果你想知道“Launcher 卡片为什么会刷新”,看:
|
||||
|
||||
```text
|
||||
BluetoothAvrcpClient
|
||||
↓
|
||||
BtInfoPushManager
|
||||
↓
|
||||
DataManager.putObject(BT_MUSIC_INFO)
|
||||
↓
|
||||
Id3Info
|
||||
↓
|
||||
BluetoothMusicCardView
|
||||
```
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
# 01_启动初始化流程
|
||||
|
||||
## 目标
|
||||
|
||||
搞清楚蓝牙音乐模块什么时候初始化、初始化了哪些对象。
|
||||
|
||||
## 启动流程
|
||||
|
||||
```text
|
||||
1. 蓝牙 App 进程启动
|
||||
↓
|
||||
2. BluetoothApplication.onCreate()
|
||||
↓
|
||||
3. 调用父类 BluetoothSdkApplication.onCreate()
|
||||
↓
|
||||
4. BluetoothSdkApplication.setPlatform()
|
||||
↓
|
||||
5. 初始化各蓝牙业务 Manager
|
||||
↓
|
||||
6. BtMusicControlManager.getInstance().init(context)
|
||||
↓
|
||||
7. 创建 BtMusicManager
|
||||
↓
|
||||
8. BtMusicManager.init(context)
|
||||
↓
|
||||
9. 初始化 BluetoothAvrcpClient
|
||||
↓
|
||||
10. 初始化 BtMusicAudioManager
|
||||
↓
|
||||
11. BluetoothApplication 启动 BluetoothService
|
||||
```
|
||||
|
||||
## 关键源码
|
||||
|
||||
### 1. 蓝牙 App 启动
|
||||
|
||||
```text
|
||||
文件:
|
||||
F:\zdCode\tongli\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\BluetoothApplication.java
|
||||
|
||||
核心:
|
||||
onCreate()
|
||||
-> super.onCreate()
|
||||
-> SharedPrefsManager.init()
|
||||
-> initBluetoothService()
|
||||
```
|
||||
|
||||
### 2. SDK 初始化
|
||||
|
||||
```text
|
||||
文件:
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\BluetoothSdkApplication.java
|
||||
|
||||
核心:
|
||||
setPlatform()
|
||||
-> BtContactsControlManager.init()
|
||||
-> BtSettingControlManager.init()
|
||||
-> BtPhoneControlManager.init()
|
||||
-> BtCallRecordsControlManager.init()
|
||||
-> BtMusicControlManager.init()
|
||||
-> BtVoiceControlManager.init()
|
||||
```
|
||||
|
||||
### 3. 蓝牙音乐初始化
|
||||
|
||||
```text
|
||||
文件:
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\manager\BtMusicControlManager.java
|
||||
|
||||
核心:
|
||||
init(context)
|
||||
-> new BtMusicManager()
|
||||
-> mBtMusicManager.init(context)
|
||||
-> mBtMusicManager.initMusicInfo()
|
||||
```
|
||||
|
||||
### 4. 音乐核心初始化
|
||||
|
||||
```text
|
||||
文件:
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtMusicManager.java
|
||||
|
||||
核心:
|
||||
init(context)
|
||||
-> initData(context)
|
||||
-> initListener()
|
||||
|
||||
initData(context)
|
||||
-> mMusicInfoBean = new MusicInfo()
|
||||
-> BluetoothAvrcpClient.getInstance().init(context)
|
||||
-> BtMusicAudioManager.getInstance().init(context, this)
|
||||
-> BluetoothA2dpSinkClient.getInstance()
|
||||
```
|
||||
|
||||
## 初始化后已经具备的能力
|
||||
|
||||
| 能力 | 是否已准备 |
|
||||
|---|---|
|
||||
| 接收 AVRCP 歌曲信息 | 是 |
|
||||
| 接收 AVRCP 播放状态 | 是 |
|
||||
| 下发播放/暂停/切歌控制 | 是 |
|
||||
| 监听 Launcher 控制命令 | 是 |
|
||||
| 申请/释放音频焦点 | 是 |
|
||||
| 推送蓝牙音乐信息给 Launcher | 是 |
|
||||
|
||||
## 记忆点
|
||||
|
||||
```text
|
||||
蓝牙音乐模块不是进入 BluetoothMusicActivity 才初始化。
|
||||
只要蓝牙 App 启动,BtMusicManager 和相关 SDK 就已经初始化。
|
||||
```
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# 02_A2DP连接与断开流程
|
||||
|
||||
## A2DP 是什么
|
||||
|
||||
```text
|
||||
A2DP = 蓝牙音乐声音通道
|
||||
|
||||
手机音乐 App 播放出来的声音
|
||||
↓
|
||||
通过 A2DP
|
||||
↓
|
||||
传到车机
|
||||
```
|
||||
|
||||
在这个项目中,车机是 `A2DP Sink`,也就是音频接收端。
|
||||
|
||||
## A2DP 连接成功流程
|
||||
|
||||
```text
|
||||
1. 手机和车机建立 A2DP 连接
|
||||
↓
|
||||
2. Android 系统发送 BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED
|
||||
↓
|
||||
3. BluetoothConnectListenerClient.ConnectStatusReceiver.onReceive()
|
||||
↓
|
||||
4. 判断 action 是 A2DP Sink 连接变化
|
||||
↓
|
||||
5. setA2dpConnectState(linkedInfo, intent)
|
||||
↓
|
||||
6. newState == BluetoothProfile.STATE_CONNECTED
|
||||
↓
|
||||
7. onA2dpConnect(linkedInfo)
|
||||
↓
|
||||
8. BtMusicManager 收到 onA2dpConnect
|
||||
↓
|
||||
9. BtMusicViewModel.onA2dpConnect()
|
||||
↓
|
||||
10. mLiveDataA2dpConnectStatus.postValue(true)
|
||||
↓
|
||||
11. BluetoothMusicActivity 观察到 true
|
||||
↓
|
||||
12. setIsBtConnect(true)
|
||||
↓
|
||||
13. 页面显示蓝牙音乐播放界面
|
||||
```
|
||||
|
||||
## A2DP 断开流程
|
||||
|
||||
```text
|
||||
1. 手机断开 A2DP 或蓝牙关闭
|
||||
↓
|
||||
2. Android 系统发送 A2DP 断开广播
|
||||
↓
|
||||
3. BluetoothConnectListenerClient.setA2dpConnectState()
|
||||
↓
|
||||
4. newState == BluetoothProfile.STATE_DISCONNECTED
|
||||
↓
|
||||
5. onA2dpDisconnect(linkedInfo)
|
||||
↓
|
||||
6. BtMusicManager.clearBtMusic()
|
||||
↓
|
||||
7. resetMusicInfo()
|
||||
↓
|
||||
8. 通知页面播放状态 false
|
||||
↓
|
||||
9. BtMusicViewModel.onA2dpDisconnect()
|
||||
↓
|
||||
10. mLiveDataA2dpConnectStatus.postValue(false)
|
||||
↓
|
||||
11. BluetoothMusicActivity 显示未连接界面
|
||||
↓
|
||||
12. BtInfoPushManager.cleanBtMusic()
|
||||
↓
|
||||
13. Launcher 卡片恢复默认状态
|
||||
```
|
||||
|
||||
## 页面进入时的补连逻辑
|
||||
|
||||
这个逻辑在 `BluetoothMusicActivity.btMusicInfo()`。
|
||||
|
||||
```text
|
||||
1. 进入蓝牙音乐页
|
||||
↓
|
||||
2. BluetoothMusicActivity.onResume()
|
||||
↓
|
||||
3. btMusicInfo()
|
||||
↓
|
||||
4. getConnectedPhoneDevice()
|
||||
↓
|
||||
5. 如果当前有手机设备
|
||||
↓
|
||||
6. 判断 A2DP 是否未连接且未处于连接中
|
||||
↓
|
||||
7. connectA2dp(device.getAddress())
|
||||
```
|
||||
|
||||
这个逻辑的意义:
|
||||
|
||||
```text
|
||||
如果手机已经和车机有蓝牙关系,但 A2DP 声音通道没有起来,
|
||||
进入蓝牙音乐页时尝试补连 A2DP。
|
||||
```
|
||||
|
||||
## 关键文件
|
||||
|
||||
| 文件 | 重点看 |
|
||||
|---|---|
|
||||
| `BluetoothConnectListenerClient.java` | 系统蓝牙连接广播如何分发。 |
|
||||
| `BluetoothA2dpSinkClient.java` | A2DP 是否连接、连接中、断开中、连接/断开方法。 |
|
||||
| `BtMusicManager.java` | A2DP 连接/断开后如何清状态、通知页面。 |
|
||||
| `BtMusicViewModel.java` | 如何把 A2DP 状态转成 LiveData。 |
|
||||
| `BluetoothMusicActivity.java` | 如何根据 `isBtConnect` 切换界面。 |
|
||||
|
||||
## 页面状态
|
||||
|
||||
| A2DP 状态 | `isBtConnect` | 页面 |
|
||||
|---|---|---|
|
||||
| 已连接 | `true` | 显示蓝牙音乐播放界面 |
|
||||
| 未连接 | `false` | 显示连接蓝牙提示 |
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
# 03_AVRCP歌曲信息与播放状态流程
|
||||
|
||||
## AVRCP 是什么
|
||||
|
||||
```text
|
||||
AVRCP = 蓝牙媒体控制协议
|
||||
|
||||
它负责两类事情:
|
||||
1. 车机控制手机:播放、暂停、上一曲、下一曲
|
||||
2. 手机告诉车机:歌名、歌手、封面、播放状态、进度
|
||||
```
|
||||
|
||||
## MediaBrowser 连接流程
|
||||
|
||||
```text
|
||||
1. BluetoothAvrcpClient.init(context)
|
||||
↓
|
||||
2. initMediaBrowserHelper()
|
||||
↓
|
||||
3. new MediaBrowserClient(context)
|
||||
↓
|
||||
4. MediaBrowserClient 连接系统服务:
|
||||
com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService
|
||||
↓
|
||||
5. 连接成功后创建 MediaControllerCompat
|
||||
↓
|
||||
6. 注册 MediaControllerCompat.Callback
|
||||
↓
|
||||
7. 可以接收 metadata 和 playbackState
|
||||
```
|
||||
|
||||
## 歌曲信息流程
|
||||
|
||||
```text
|
||||
1. 手机音乐 App 播放或切歌
|
||||
↓
|
||||
2. 手机通过 AVRCP 上报 metadata
|
||||
↓
|
||||
3. Android 蓝牙服务接收 metadata
|
||||
↓
|
||||
4. MediaBrowserClient.mediaControlCallback.onMetadataChanged()
|
||||
↓
|
||||
5. BluetoothAvrcpClient.callback.onMetadataChanged()
|
||||
↓
|
||||
6. BtMusicManager.onMetadataChanged()
|
||||
↓
|
||||
7. 解析字段:
|
||||
artist
|
||||
title
|
||||
album
|
||||
albumUrl
|
||||
duration
|
||||
↓
|
||||
8. 保存到 MusicInfo
|
||||
↓
|
||||
9. 通知 BtMusicViewModel.onPlayMusicInfo()
|
||||
↓
|
||||
10. mLiveDataMusicInfo.postValue(musicInfo)
|
||||
↓
|
||||
11. BluetoothMusicActivity 更新 UI
|
||||
```
|
||||
|
||||
## 播放状态流程
|
||||
|
||||
```text
|
||||
1. 手机音乐播放状态变化
|
||||
↓
|
||||
2. MediaBrowserClient.onPlaybackStateChanged()
|
||||
↓
|
||||
3. BluetoothAvrcpClient.onPlaybackStateChanged()
|
||||
↓
|
||||
4. BtMusicManager.onPlaybackStateChanged()
|
||||
↓
|
||||
5. 判断:
|
||||
state == PlaybackStateCompat.STATE_PLAYING
|
||||
↓
|
||||
6. 更新 MusicInfo.isPlaying
|
||||
↓
|
||||
7. 更新 MusicInfo.playPosition
|
||||
↓
|
||||
8. 通知 BtMusicViewModel.onPlayMusicState(isPlaying)
|
||||
↓
|
||||
9. BluetoothMusicActivity 更新播放按钮图标
|
||||
```
|
||||
|
||||
## 同步给 Launcher 的流程
|
||||
|
||||
同样的 AVRCP 数据,还会推给桌面卡片。
|
||||
|
||||
```text
|
||||
BluetoothAvrcpClient.onMetadataChanged()
|
||||
↓
|
||||
BtInfoPushManager.sendMusicInfoToLauncher()
|
||||
↓
|
||||
转换为 BtMusicInfo
|
||||
↓
|
||||
DataManager.putObject(BT_MUSIC_INFO, btInfo)
|
||||
↓
|
||||
发送 ACTION_UPDATE_MUSIC_WIDGET
|
||||
↓
|
||||
Launcher 卡片刷新
|
||||
```
|
||||
|
||||
播放状态也是类似:
|
||||
|
||||
```text
|
||||
BluetoothAvrcpClient.onPlaybackStateChanged()
|
||||
↓
|
||||
BtInfoPushManager.sendMusicStateToLauncher()
|
||||
↓
|
||||
DataManager.putObject(BT_MUSIC_INFO, btInfo)
|
||||
↓
|
||||
Launcher 卡片刷新
|
||||
```
|
||||
|
||||
## MusicInfo 字段
|
||||
|
||||
| 字段 | 含义 | 来源 |
|
||||
|---|---|---|
|
||||
| `artist` | 歌手 | `MediaMetadata.METADATA_KEY_ARTIST` |
|
||||
| `title` | 歌曲名 | `MediaMetadata.METADATA_KEY_TITLE` |
|
||||
| `album` | 专辑 | `MediaMetadata.METADATA_KEY_ALBUM` |
|
||||
| `albumUrl` | 封面 | `MediaMetadata.METADATA_KEY_ALBUM_ART_URI` |
|
||||
| `songLen` | 总时长 | `MediaMetadata.METADATA_KEY_DURATION` |
|
||||
| `playPosition` | 当前进度 | `PlaybackStateCompat.getPosition()` |
|
||||
| `isPlaying` | 是否播放 | `PlaybackStateCompat.STATE_PLAYING` |
|
||||
|
||||
## Unknown 处理
|
||||
|
||||
在 `BtMusicManager.onMetadataChanged()` 中:
|
||||
|
||||
```text
|
||||
如果 artist/title/album 为空、unknow、Not Provided
|
||||
↓
|
||||
替换为 strings.xml 中的 unknow 文案
|
||||
```
|
||||
|
||||
在 `BluetoothMusicActivity` 中:
|
||||
|
||||
```text
|
||||
如果 title 为空或等于 unknow
|
||||
↓
|
||||
setIsOpenApp(true)
|
||||
```
|
||||
|
||||
含义:
|
||||
|
||||
```text
|
||||
A2DP 可能已经连接,但手机音乐 App 没有提供有效歌曲信息。
|
||||
页面可能提示用户打开手机音乐 App。
|
||||
```
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
# 04_播放控制与音频焦点流程
|
||||
|
||||
## 页面按钮控制流程
|
||||
|
||||
```text
|
||||
1. 用户点击蓝牙音乐页面按钮
|
||||
↓
|
||||
2. BluetoothMusicActivity.onClick()
|
||||
↓
|
||||
3. 根据按钮 ID 判断动作
|
||||
↓
|
||||
4. 调用 BtMusicViewModel
|
||||
↓
|
||||
5. BtMusicViewModel 调用 BtMusicControlManager
|
||||
↓
|
||||
6. BtMusicControlManager 调用 BtMusicManager
|
||||
↓
|
||||
7. BtMusicManager 调用 BluetoothAvrcpClient
|
||||
↓
|
||||
8. BluetoothAvrcpClient 调用 MediaController.TransportControls
|
||||
↓
|
||||
9. 手机音乐 App 响应
|
||||
```
|
||||
|
||||
## 页面按钮映射
|
||||
|
||||
| 控件 ID | 方法 | 最终 AVRCP 控制 |
|
||||
|---|---|---|
|
||||
| `iv_bt_music_pre` | `preBtMusic()` | `skipToPrevious()` |
|
||||
| `iv_bt_music_state` | `playBtMusic()` / `pauseBtMusic()` | `play()` / `pause()` |
|
||||
| `iv_bt_music_next` | `nextBtMusic()` | `skipToNext()` |
|
||||
|
||||
## 播放命令详细链路
|
||||
|
||||
```text
|
||||
BluetoothMusicActivity.onClick()
|
||||
↓
|
||||
mViewModel.playBtMusic()
|
||||
↓
|
||||
BtMusicViewModel.playBtMusic()
|
||||
↓
|
||||
BtMusicControlManager.playBtMusic()
|
||||
↓
|
||||
BtMusicManager.requestFocus()
|
||||
↓
|
||||
BtMusicAudioManager.requestAudioFocus()
|
||||
↓
|
||||
BtMusicManager.playBtMusic()
|
||||
↓
|
||||
BluetoothAvrcpClient.play()
|
||||
↓
|
||||
TransportControls.play()
|
||||
```
|
||||
|
||||
## 暂停命令详细链路
|
||||
|
||||
```text
|
||||
BluetoothMusicActivity.onClick()
|
||||
↓
|
||||
mViewModel.pauseBtMusic()
|
||||
↓
|
||||
BtMusicViewModel.pauseBtMusic()
|
||||
↓
|
||||
BtMusicControlManager.pauseBtMusic()
|
||||
↓
|
||||
BtMusicManager.pauseBtMusic()
|
||||
↓
|
||||
BluetoothAvrcpClient.pause()
|
||||
↓
|
||||
TransportControls.pause()
|
||||
```
|
||||
|
||||
## 切歌命令详细链路
|
||||
|
||||
```text
|
||||
上一曲:
|
||||
BluetoothMusicActivity
|
||||
↓
|
||||
BtMusicViewModel.preBtMusic()
|
||||
↓
|
||||
BtMusicControlManager.preBtMusic()
|
||||
↓
|
||||
BtMusicManager.preBtMusic()
|
||||
↓
|
||||
BluetoothAvrcpClient.previous()
|
||||
↓
|
||||
TransportControls.skipToPrevious()
|
||||
|
||||
下一曲:
|
||||
BluetoothMusicActivity
|
||||
↓
|
||||
BtMusicViewModel.nextBtMusic()
|
||||
↓
|
||||
BtMusicControlManager.nextBtMusic()
|
||||
↓
|
||||
BtMusicManager.nextBtMusic()
|
||||
↓
|
||||
BluetoothAvrcpClient.next()
|
||||
↓
|
||||
TransportControls.skipToNext()
|
||||
```
|
||||
|
||||
## 音频焦点是什么
|
||||
|
||||
```text
|
||||
音频焦点 = 当前谁有资格出声
|
||||
|
||||
车机里可能同时有:
|
||||
蓝牙音乐
|
||||
收音机
|
||||
USB 音乐
|
||||
视频
|
||||
导航播报
|
||||
蓝牙电话
|
||||
|
||||
所以蓝牙音乐播放前必须申请焦点。
|
||||
```
|
||||
|
||||
## 焦点申请流程
|
||||
|
||||
```text
|
||||
1. 用户点击播放
|
||||
↓
|
||||
2. BtMusicManager.requestFocus()
|
||||
↓
|
||||
3. BtMusicAudioManager.requestAudioFocus()
|
||||
↓
|
||||
4. AudioManager.requestAudioFocus()
|
||||
↓
|
||||
5. 如果返回 AUDIOFOCUS_REQUEST_GRANTED
|
||||
↓
|
||||
6. setA2dpUnMute()
|
||||
↓
|
||||
7. isFocus = true
|
||||
↓
|
||||
8. setMediaButtonEvent()
|
||||
↓
|
||||
9. 允许执行 AVRCP play()
|
||||
```
|
||||
|
||||
## 焦点变化处理
|
||||
|
||||
| 焦点变化 | 处理 |
|
||||
|---|---|
|
||||
| `AUDIOFOCUS_GAIN` | 恢复 A2DP 出声,必要时恢复播放,通知语音当前是蓝牙音乐。 |
|
||||
| `AUDIOFOCUS_LOSS` | 释放焦点,暂停蓝牙音乐,清空 Launcher 蓝牙音乐信息。 |
|
||||
| `AUDIOFOCUS_LOSS_TRANSIENT` | 临时失焦,A2DP 静音,暂停蓝牙音乐。 |
|
||||
| `AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK` | 降低蓝牙音乐音量。 |
|
||||
|
||||
## 失焦流程
|
||||
|
||||
```text
|
||||
1. 其他音源抢焦点
|
||||
↓
|
||||
2. BtMusicAudioManager.mAudioFocusListener.onAudioFocusChange()
|
||||
↓
|
||||
3. 收到 AUDIOFOCUS_LOSS 或 AUDIOFOCUS_LOSS_TRANSIENT
|
||||
↓
|
||||
4. 记录当前是否正在播放
|
||||
↓
|
||||
5. setA2dpMute()
|
||||
↓
|
||||
6. BtMusicManager.pauseBtMusic()
|
||||
↓
|
||||
7. 手机音乐暂停
|
||||
```
|
||||
|
||||
## 恢复焦点流程
|
||||
|
||||
```text
|
||||
1. 蓝牙音乐重新获得焦点
|
||||
↓
|
||||
2. 收到 AUDIOFOCUS_GAIN
|
||||
↓
|
||||
3. isFocus = true
|
||||
↓
|
||||
4. setA2dpUnMute()
|
||||
↓
|
||||
5. 如果 isPlayingLossFocus == true
|
||||
↓
|
||||
6. BtMusicManager.playBtMusic()
|
||||
↓
|
||||
7. 手机音乐恢复播放
|
||||
```
|
||||
|
||||
## 注意点
|
||||
|
||||
`BluetoothA2dpSinkClient` 中这两个方法底层调用目前被注释:
|
||||
|
||||
```text
|
||||
setFluorideAudioTrackGain()
|
||||
setFluorideStreaming()
|
||||
```
|
||||
|
||||
所以焦点切换时,A2DP 静音/恢复是否真正生效,需要真机验证。
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
# 05_Launcher卡片流程
|
||||
|
||||
## Launcher 卡片做什么
|
||||
|
||||
Launcher 蓝牙音乐卡片负责:
|
||||
|
||||
```text
|
||||
1. 显示蓝牙音乐标题
|
||||
2. 显示歌手
|
||||
3. 显示封面
|
||||
4. 显示播放状态
|
||||
5. 显示当前进度和总时长
|
||||
6. 提供上一曲、播放/暂停、下一曲按钮
|
||||
```
|
||||
|
||||
核心文件:
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_Launcher\src\main\java\com\yaxon\launcher\card\BluetoothMusicCardView.java
|
||||
F:\zdCode\tongli\YX_Launcher\src\main\res\layout\card_bluetoothmusic.xml
|
||||
```
|
||||
|
||||
## 卡片显示流程
|
||||
|
||||
```text
|
||||
1. BluetoothAvrcpClient 收到歌曲信息或播放状态
|
||||
↓
|
||||
2. BtInfoPushManager.sendMusicInfoToLauncher()
|
||||
或 sendMusicStateToLauncher()
|
||||
↓
|
||||
3. 组装 BtMusicInfo
|
||||
↓
|
||||
4. DataManager.putObject(BT_MUSIC_INFO, btInfo)
|
||||
↓
|
||||
5. 发送 ACTION_UPDATE_MUSIC_WIDGET 广播
|
||||
↓
|
||||
6. Launcher 的 Id3Info 读取 BT_MUSIC_INFO
|
||||
↓
|
||||
7. Id3Info 回调 BluetoothMusicCardView.onBtMusicInfo()
|
||||
↓
|
||||
8. BluetoothMusicCardView.bindInfo()
|
||||
↓
|
||||
9. 更新卡片 UI
|
||||
```
|
||||
|
||||
## 卡片显示字段
|
||||
|
||||
| 卡片内容 | 数据字段 |
|
||||
|---|---|
|
||||
| 歌曲名 | `BtMusicInfo.mTitle` |
|
||||
| 歌手 | `BtMusicInfo.mArtist` |
|
||||
| 封面 | `BtMusicInfo.mAlbumUrl` |
|
||||
| 播放状态 | `BtMusicInfo.mPlayState` |
|
||||
| 当前进度 | `BtMusicInfo.mPlayPosition` |
|
||||
| 总时长 | `BtMusicInfo.mSongLen` |
|
||||
|
||||
## 卡片进度条逻辑
|
||||
|
||||
```text
|
||||
1. 收到 BtMusicInfo
|
||||
↓
|
||||
2. 保存 mLastPositionMs
|
||||
↓
|
||||
3. 保存 mLastDurationMs
|
||||
↓
|
||||
4. 保存 mLastUpdateUptimeMs
|
||||
↓
|
||||
5. 如果是播放中
|
||||
↓
|
||||
6. 每秒用本地时间估算进度
|
||||
↓
|
||||
7. updateProgress()
|
||||
```
|
||||
|
||||
这意味着:
|
||||
|
||||
```text
|
||||
Launcher 卡片不是每秒都等蓝牙栈回调,
|
||||
而是在播放中自己按时间推进进度条。
|
||||
```
|
||||
|
||||
## 卡片控制流程
|
||||
|
||||
```text
|
||||
1. 用户点击 Launcher 卡片按钮
|
||||
↓
|
||||
2. BluetoothMusicCardView.onClick()
|
||||
↓
|
||||
3. notifyBtSourceActive()
|
||||
↓
|
||||
4. DataManager.putInt(SOURCE_ID, SOURCE_BT_MUSIC)
|
||||
↓
|
||||
5. sendBtControl(cmd)
|
||||
↓
|
||||
6. DataManager.putInt(MUSIC_BT_CONTROL_STATE, cmd)
|
||||
↓
|
||||
7. BtMusicAudioManager 的 ContentObserver 收到变化
|
||||
↓
|
||||
8. 如果蓝牙音乐没有焦点,先 requestAudioFocus()
|
||||
↓
|
||||
9. 根据 cmd 调用 BtMusicManager
|
||||
↓
|
||||
10. BtMusicManager 调用 BluetoothAvrcpClient
|
||||
↓
|
||||
11. 手机音乐 App 响应
|
||||
↓
|
||||
12. MUSIC_BT_CONTROL_STATE 重置为 -1
|
||||
```
|
||||
|
||||
## 卡片按钮命令
|
||||
|
||||
| 卡片按钮 | 写入命令 |
|
||||
|---|---|
|
||||
| 上一曲 | `StateUtils.MUSIC_BT_CONTROL_PRE` |
|
||||
| 下一曲 | `StateUtils.MUSIC_BT_CONTROL_NEXT` |
|
||||
| 播放 | `StateUtils.MUSIC_BT_CONTROL_PLAY` |
|
||||
| 暂停 | `StateUtils.MUSIC_BT_CONTROL_PAUSE` |
|
||||
|
||||
## 为什么要重置为 -1
|
||||
|
||||
```text
|
||||
如果连续点击同一个按钮,比如连续点下一曲:
|
||||
|
||||
第一次写入 MUSIC_BT_CONTROL_NEXT
|
||||
↓
|
||||
ContentObserver 触发
|
||||
↓
|
||||
处理完成后写回 -1
|
||||
↓
|
||||
下一次再写 MUSIC_BT_CONTROL_NEXT
|
||||
↓
|
||||
值发生变化,ContentObserver 可以再次触发
|
||||
```
|
||||
|
||||
如果不重置,同一个值可能不会再次触发观察。
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
# 06_开发入口_风险点_测试清单
|
||||
|
||||
## 开发入口速查
|
||||
|
||||
### 改蓝牙音乐页面
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_BlueTooth\src\main\res\layout\activity_bluetooth_music.xml
|
||||
F:\zdCode\tongli\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothMusicActivity.java
|
||||
```
|
||||
|
||||
常见改动:
|
||||
|
||||
```text
|
||||
改封面
|
||||
改标题/歌手显示
|
||||
改进度条
|
||||
改播放按钮
|
||||
改未连接页面
|
||||
增加音效入口
|
||||
增加跳转蓝牙设置
|
||||
```
|
||||
|
||||
### 改播放控制
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\BtMusicViewModel.java
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\manager\BtMusicControlManager.java
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtMusicManager.java
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothAvrcpClient.java
|
||||
```
|
||||
|
||||
常见改动:
|
||||
|
||||
```text
|
||||
播放前是否强制申请焦点
|
||||
暂停后是否释放焦点
|
||||
切歌时是否清默认封面
|
||||
页面 onResume 是否自动恢复播放
|
||||
手机端主动播放时是否允许车机出声
|
||||
```
|
||||
|
||||
### 改 A2DP 连接
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothConnectListenerClient.java
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothA2dpSinkClient.java
|
||||
F:\zdCode\tongli\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\BtMusicViewModel.java
|
||||
```
|
||||
|
||||
常见改动:
|
||||
|
||||
```text
|
||||
A2DP 自动连接
|
||||
A2DP 断开清理
|
||||
连接中状态判断
|
||||
未连接页面状态
|
||||
蓝牙关闭后的数据清理
|
||||
```
|
||||
|
||||
### 改歌曲信息
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtMusicManager.java
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtInfoPushManager.java
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\model\MusicInfo.java
|
||||
```
|
||||
|
||||
常见改动:
|
||||
|
||||
```text
|
||||
Unknown 兜底文案
|
||||
封面为空兜底
|
||||
不同手机音乐 App 兼容
|
||||
总时长为 0 的处理
|
||||
播放进度刷新策略
|
||||
```
|
||||
|
||||
### 改 Launcher 卡片
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_Launcher\src\main\java\com\yaxon\launcher\card\BluetoothMusicCardView.java
|
||||
F:\zdCode\tongli\YX_Launcher\src\main\res\layout\card_bluetoothmusic.xml
|
||||
F:\zdCode\tongli\YX_Launcher\src\main\java\com\yaxon\launcher\data\Id3Info.java
|
||||
```
|
||||
|
||||
常见改动:
|
||||
|
||||
```text
|
||||
卡片 UI
|
||||
卡片点击进入蓝牙音乐页
|
||||
卡片控制按钮
|
||||
卡片进度条
|
||||
卡片默认态
|
||||
```
|
||||
|
||||
### 改音频焦点
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\audio\BtMusicAudioManager.java
|
||||
```
|
||||
|
||||
常见改动:
|
||||
|
||||
```text
|
||||
蓝牙音乐和收音机抢焦点
|
||||
蓝牙音乐和 USB 音乐抢焦点
|
||||
来电暂停/恢复
|
||||
导航播报降音
|
||||
媒体按键处理
|
||||
亿连 A2DP 焦点处理
|
||||
```
|
||||
|
||||
## 当前代码风险点
|
||||
|
||||
| 风险 | 文件 | 说明 |
|
||||
|---|---|---|
|
||||
| `isA2dpSinkConnected()` 总返回 false | `BtMusicViewModel`、`BtMusicControlManager` | 调了底层但没有 return 结果。 |
|
||||
| 音效按钮未闭环 | `activity_bluetooth_music.xml` | 有 `iv_sound_effects`,但 Activity 没处理点击。 |
|
||||
| 连接蓝牙按钮未闭环 | `BluetoothMusicActivity` | `tv_connect_btn` 点击逻辑被注释。 |
|
||||
| A2DP 静音底层调用被注释 | `BluetoothA2dpSinkClient` | `setFluorideAudioTrackGain()`、`setFluorideStreaming()` 没真正调底层。 |
|
||||
| 监听移除方法参数疑似错误 | `BtMusicManager` | `removeIBtMusicModuleListener()` 参数是 `IBtSettingModuleListener`。 |
|
||||
|
||||
## 真机测试清单
|
||||
|
||||
### 连接类
|
||||
|
||||
| 用例 | 预期 |
|
||||
|---|---|
|
||||
| 手机首次配对并连接 | HFP/A2DP 正常连接,页面显示播放界面。 |
|
||||
| 只连 HFP 未连 A2DP 后进入蓝牙音乐页 | 页面尝试补连 A2DP。 |
|
||||
| 播放中断开手机蓝牙 | 页面切未连接,Launcher 清空蓝牙音乐信息。 |
|
||||
| 蓝牙开关关闭 | 页面切未连接,音乐信息清空。 |
|
||||
|
||||
### 播放控制类
|
||||
|
||||
| 用例 | 预期 |
|
||||
|---|---|
|
||||
| 点击播放 | 申请音频焦点,手机音乐开始播放。 |
|
||||
| 点击暂停 | 手机音乐暂停,页面按钮切为播放图标。 |
|
||||
| 点击上一曲 | 手机音乐切上一曲,页面和 Launcher 更新。 |
|
||||
| 点击下一曲 | 手机音乐切下一曲,页面和 Launcher 更新。 |
|
||||
| Launcher 点击播放/暂停 | 蓝牙音乐响应,页面和卡片状态同步。 |
|
||||
|
||||
### 歌曲信息类
|
||||
|
||||
| 用例 | 预期 |
|
||||
|---|---|
|
||||
| 普通歌曲 | 显示标题、歌手、封面、进度、总时长。 |
|
||||
| 标题为空 | 显示 Unknown 或打开手机音乐 App 状态。 |
|
||||
| 封面为空 | 显示默认封面。 |
|
||||
| 总时长为 0 | 进度条安全显示,不崩溃。 |
|
||||
|
||||
### 音频焦点类
|
||||
|
||||
| 用例 | 预期 |
|
||||
|---|---|
|
||||
| 蓝牙音乐播放时打开收音机 | 蓝牙音乐暂停或静音,收音机出声。 |
|
||||
| 收音机播放时手机点蓝牙音乐播放 | 如果蓝牙音乐无焦点,应暂停或不出声。 |
|
||||
| 蓝牙音乐播放时来电 | 蓝牙音乐暂停,电话优先。 |
|
||||
| 电话结束 | 根据之前播放状态决定是否恢复蓝牙音乐。 |
|
||||
|
||||
## 日志关键词
|
||||
|
||||
```text
|
||||
BtMusicManager
|
||||
BluetoothAvrcpClient
|
||||
BluetoothA2dpSinkClient
|
||||
BtMusicAudioManager
|
||||
BtInfoPushManager
|
||||
BluetoothMusicActivity
|
||||
onPlaybackStateChanged
|
||||
onMetadataChanged
|
||||
requestAudioFocus
|
||||
AUDIOFOCUS_GAIN
|
||||
AUDIOFOCUS_LOSS
|
||||
onA2dpConnect
|
||||
onA2dpDisconnect
|
||||
MUSIC_BT_CONTROL_STATE
|
||||
```
|
||||
|
||||
## 排查顺序
|
||||
|
||||
```text
|
||||
1. 先看是否 A2DP connected
|
||||
2. 再看 MediaBrowserClient 是否连接成功
|
||||
3. 再看是否收到 onMetadataChanged
|
||||
4. 再看是否收到 onPlaybackStateChanged
|
||||
5. 再看点击播放后是否 requestAudioFocus 成功
|
||||
6. 再看是否调用 BluetoothAvrcpClient.play/pause/next/previous
|
||||
7. 再看 Launcher 是否写入 MUSIC_BT_CONTROL_STATE
|
||||
8. 最后看 BtInfoPushManager 是否写入 BT_MUSIC_INFO
|
||||
```
|
||||
|
||||
@@ -0,0 +1,748 @@
|
||||
# 蓝牙音乐模块功能流程梳理
|
||||
|
||||
更新时间:2026-07-13
|
||||
工程路径:`F:\zdCode\tongli`
|
||||
当前蓝牙音乐提交:`8ec79048 modify(YX_BlueTooth):新增蓝牙音乐功能`
|
||||
|
||||
## 1. 一句话结论
|
||||
|
||||
蓝牙音乐模块不是本地音乐播放器,而是 **车机作为 A2DP Sink 接收手机音频 + 作为 AVRCP Controller 控制手机播放器**。
|
||||
|
||||
也就是说:
|
||||
|
||||
- 音频从手机通过蓝牙 A2DP 传到车机。
|
||||
- 播放、暂停、上一首、下一首通过 AVRCP 控制手机端播放器。
|
||||
- 歌名、歌手、播放进度、总时长来自手机蓝牙媒体会话回调。
|
||||
- 当前 UI 按老项目效果固定显示默认蓝牙音乐封面,不加载手机专辑图。
|
||||
|
||||
## 2. 模块边界
|
||||
|
||||
### 2.1 负责的功能
|
||||
|
||||
| 功能 | 当前实现 |
|
||||
|---|---|
|
||||
| 蓝牙音乐入口 | 主界面左侧“蓝牙音乐”菜单进入 `BtMusicFragment` |
|
||||
| 独立音乐页 | `BluetoothMusicActivity` 已注册,但当前主要入口是主界面 Fragment |
|
||||
| A2DP 状态 | 判断 A2DP 是否连接,未连接时显示连接提示 |
|
||||
| A2DP 自动连接 | 已有手机设备且 A2DP 未连接时,尝试连接 A2DP |
|
||||
| 播放控制 | 播放、暂停、上一首、下一首 |
|
||||
| 歌曲信息 | 歌名、歌手、当前时间、总时长、进度 |
|
||||
| 音频焦点 | 播放前申请蓝牙音乐焦点,焦点丢失时暂停/静音 |
|
||||
| UI 对齐 | 已按老项目蓝牙音乐页 UI 对齐 |
|
||||
|
||||
### 2.2 不负责的功能
|
||||
|
||||
| 不负责项 | 说明 |
|
||||
|---|---|
|
||||
| 本地音乐扫描 | 不扫描 U 盘、本地文件或歌单 |
|
||||
| 本地解码播放 | 不直接解码 mp3/flac 等媒体文件 |
|
||||
| 手机歌单浏览 | 当前只做基础播放控制,不展示手机媒体库列表 |
|
||||
| 专辑封面展示 | 当前按老项目固定显示默认蓝牙音乐图 |
|
||||
| 蓝牙配对搜索完整流程 | 属于蓝牙设置模块 |
|
||||
|
||||
## 3. 关键代码入口
|
||||
|
||||
| 层级 | 文件 | 作用 |
|
||||
|---|---|---|
|
||||
| 主入口 | `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.java` | 左侧菜单切换到蓝牙音乐页 |
|
||||
| Fragment UI | `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/fragment/BtMusicFragment.java` | 主界面内的蓝牙音乐页面 |
|
||||
| 独立 Activity | `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/BluetoothMusicActivity.java` | 独立蓝牙音乐页面 |
|
||||
| ViewModel | `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/viewmodel/BtMusicViewModel.java` | UI 与 SDK 之间的桥接层 |
|
||||
| 控制门面 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtMusicControlManager.java` | 对外提供蓝牙音乐控制 API |
|
||||
| 音乐管理 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtMusicManager.java` | 处理 AVRCP、A2DP、回调分发 |
|
||||
| AVRCP 客户端 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/BluetoothAvrcpClient.java` | 连接系统蓝牙媒体服务,发送播放控制 |
|
||||
| MediaBrowser | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/MediaBrowserClient.java` | 连接 `com.android.bluetooth.avrcpcontroller.BluetoothMediaBrowserService` |
|
||||
| 音频焦点 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/audio/BtMusicAudioManager.java` | 申请/释放音频焦点,处理焦点变化 |
|
||||
| A2DP 设置 | `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtSettingControlManager.java` | 判断/连接 A2DP |
|
||||
|
||||
## 4. 总体架构流程
|
||||
|
||||
这一节先看分层,再看两条主链路:**控制链路** 和 **回调链路**。这样比一张横向大图更清楚。
|
||||
|
||||
### 4.1 分层架构图
|
||||
|
||||
图里只保留层级关系,详细类名放到下方表格,避免图过大。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["页面层"]
|
||||
B["ViewModel"]
|
||||
C["SDK 管理层"]
|
||||
D["系统蓝牙服务"]
|
||||
E["手机端"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
| 层级 | 关键类 | 作用 |
|
||||
|---|---|---|
|
||||
| UI | `BtMusicFragment` / `BluetoothMusicActivity` | 显示界面,接收按钮点击 |
|
||||
| ViewModel | `BtMusicViewModel` | 连接 UI 和 SDK,分发 LiveData |
|
||||
| SDK 控制 | `BtMusicControlManager` / `BtSettingControlManager` | 对外提供播放控制、A2DP 连接接口 |
|
||||
| 业务管理 | `BtMusicManager` | 组织 AVRCP、A2DP、焦点和回调 |
|
||||
| AVRCP 控制 | `BluetoothAvrcpClient` / `MediaBrowserClient` | 控制手机播放器,接收媒体状态 |
|
||||
| A2DP 音频 | `BluetoothA2dpSinkClient` | 接收手机蓝牙音频 |
|
||||
| 音频焦点 | `BtMusicAudioManager` / `AudioManager` | 申请焦点、静音/解除静音、处理焦点丢失 |
|
||||
| 系统蓝牙 | `BluetoothMediaBrowserService` / A2DP Sink Service | Android 蓝牙系统服务 |
|
||||
| 手机 | 手机音乐播放器 / 手机蓝牙音频 | 真正播放音乐并输出 A2DP 音频 |
|
||||
|
||||
SDK 管理层内部再分三条线:
|
||||
|
||||
| 内部链路 | 关键类 | 解决的问题 |
|
||||
|---|---|---|
|
||||
| AVRCP 控制 | `BtMusicManager` / `BluetoothAvrcpClient` / `MediaBrowserClient` | 播放、暂停、上一首、下一首 |
|
||||
| A2DP 音频 | `BtSettingControlManager` / `BluetoothA2dpSinkClient` | 建立手机到车机的音频通道 |
|
||||
| 音频焦点 | `BtMusicAudioManager` / `AudioManager` | 决定蓝牙音乐能不能出声、是否要暂停或静音 |
|
||||
|
||||
### 4.2 播放控制链路
|
||||
|
||||
用户点按钮时,命令从上往下走,最后控制手机播放器。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["用户操作"]
|
||||
B["页面处理"]
|
||||
C["ViewModel 转发"]
|
||||
D["SDK 控制"]
|
||||
E["系统媒体控制"]
|
||||
F["手机播放器"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
E --> F
|
||||
```
|
||||
|
||||
| 步骤 | 代码定位 | 说明 |
|
||||
|---|---|---|
|
||||
| 用户操作 | `iv_bt_music_state` / `iv_bt_music_pre` / `iv_bt_music_next` | 用户点击播放控制按钮 |
|
||||
| 页面处理 | `BtMusicFragment.onClick` / `BluetoothMusicActivity.onClick` | 判断操作类型 |
|
||||
| ViewModel 转发 | `BtMusicViewModel.playBtMusic / pauseBtMusic / preBtMusic / nextBtMusic` | UI 不直接碰 SDK |
|
||||
| SDK 控制 | `BtMusicControlManager` / `BtMusicManager` | 申请焦点并组织 AVRCP 命令 |
|
||||
| 系统媒体控制 | `BluetoothAvrcpClient` / `MediaBrowserClient` / `TransportControls` | 调用系统蓝牙媒体控制 |
|
||||
| 手机播放器 | 手机音乐 App | 最终执行播放、暂停、切歌 |
|
||||
|
||||
### 4.3 状态回调链路
|
||||
|
||||
手机端播放状态变化后,状态从下往上回到 UI。
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["手机播放器"]
|
||||
B["系统媒体服务"]
|
||||
C["SDK 回调处理"]
|
||||
D["ViewModel LiveData"]
|
||||
E["页面刷新"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
| 步骤 | 代码定位 | 说明 |
|
||||
|---|---|---|
|
||||
| 手机播放器 | 手机音乐 App | 歌曲、进度、播放状态变化 |
|
||||
| 系统媒体服务 | `BluetoothMediaBrowserService` | Android 蓝牙 AVRCP Controller 媒体服务 |
|
||||
| SDK 回调处理 | `MediaBrowserClient` / `BluetoothAvrcpClient` / `BtMusicManager` | 接收并转换媒体状态 |
|
||||
| ViewModel LiveData | `BtMusicViewModel.mLiveDataMusicInfo` / `mLiveDataPlayState` | 把状态推给 UI |
|
||||
| 页面刷新 | `BtMusicFragment` / `BluetoothMusicActivity` | 刷新按钮、歌名、时间、进度 |
|
||||
|
||||
### 4.4 A2DP 与音频焦点链路
|
||||
|
||||
这条链路决定车机是否能真正出声。
|
||||
|
||||
先看 A2DP 音频通道:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["进入音乐页"]
|
||||
B["检查 A2DP"]
|
||||
C["连接手机 A2DP"]
|
||||
D["音频通道就绪"]
|
||||
E["车机扬声器输出"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
再看音频焦点:
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["点击播放"]
|
||||
B["申请音频焦点"]
|
||||
C["解除 A2DP 静音"]
|
||||
D["发送播放命令"]
|
||||
E["手机开始播放"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
| 链路 | 成功条件 | 失败表现 |
|
||||
|---|---|---|
|
||||
| A2DP 音频通道 | 手机 A2DP 已连接或连接成功 | UI 保持未连接,车机无蓝牙音乐声 |
|
||||
| 音频焦点 | `requestAudioFocus` 成功 | 手机可能在播,但车机可能无声或被其他音源压住 |
|
||||
| AVRCP 控制 | `TransportControls` 可用 | 播放/暂停/切歌命令可能无效 |
|
||||
|
||||
核心理解:
|
||||
|
||||
- UI 不直接操作系统蓝牙。
|
||||
- UI 只调用 `BtMusicViewModel`。
|
||||
- `BtMusicViewModel` 调 `BtMusicControlManager` 和 `BtSettingControlManager`。
|
||||
- 真正播放控制走 `BluetoothAvrcpClient -> MediaBrowserClient -> TransportControls`。
|
||||
- 真正音频通道走 A2DP Sink。
|
||||
- 真正能不能出声,还取决于 A2DP 是否连接和音频焦点是否申请成功。
|
||||
|
||||
## 5. 初始化流程
|
||||
|
||||
蓝牙 SDK 初始化在应用启动时完成。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["应用启动"]
|
||||
B["初始化蓝牙 SDK"]
|
||||
C["创建音乐管理器"]
|
||||
D["准备控制与音频能力"]
|
||||
E["注册媒体回调"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `BluetoothApplication.java`
|
||||
- `BluetoothSdkApplication.java`
|
||||
- `BtMusicControlManager.java`
|
||||
- `BtMusicManager.java`
|
||||
- `BluetoothAvrcpClient.java`
|
||||
- `MediaBrowserClient.java`
|
||||
- `BtMusicAudioManager.java`
|
||||
|
||||
初始化细节:
|
||||
|
||||
| 步骤 | 代码定位 | 结果 |
|
||||
|---|---|---|
|
||||
| 应用启动 | `BluetoothApplication` | 进入蓝牙 SDK 应用基类 |
|
||||
| 初始化蓝牙 SDK | `BluetoothSdkApplication.setPlatform` | 初始化各蓝牙业务模块 |
|
||||
| 创建音乐管理器 | `BtMusicControlManager.init` | 持有 `BtMusicManager` |
|
||||
| 准备控制与音频能力 | `BluetoothAvrcpClient` / `BtMusicAudioManager` / `BluetoothA2dpSinkClient` | 准备 AVRCP、焦点、A2DP |
|
||||
| 注册媒体回调 | `MediaBrowserClient` | 连接系统媒体服务并监听状态 |
|
||||
|
||||
## 6. 入口切换流程
|
||||
|
||||
主界面左侧菜单有四个入口:
|
||||
|
||||
| 位置 | 入口 |
|
||||
|---|---|
|
||||
| 0 | 蓝牙拨号 |
|
||||
| 1 | 联系人 |
|
||||
| 2 | 蓝牙音乐 |
|
||||
| 3 | 蓝牙设置 |
|
||||
|
||||
`Constant.TAB_BLUETOOTH_MUSIC = 2`。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["点击蓝牙音乐"]
|
||||
B["切换菜单位置"]
|
||||
C["更新左侧选中态"]
|
||||
D["切到音乐 Fragment"]
|
||||
E["显示音乐页面"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
关键代码:
|
||||
|
||||
- `MainActivity.setPositionFragment`
|
||||
- `Constant.TAB_BLUETOOTH_MUSIC`
|
||||
- `BtMusicFragment`
|
||||
|
||||
入口代码定位:
|
||||
|
||||
| 步骤 | 代码定位 | 说明 |
|
||||
|---|---|---|
|
||||
| 点击蓝牙音乐 | `MainActivity.onItemClick` | 左侧菜单点击 |
|
||||
| 切换菜单位置 | `setPositionFragment(position = 2)` | `2` 对应蓝牙音乐 |
|
||||
| 更新左侧选中态 | `mMenuAdapter.setSelectedPosition` | 菜单高亮 |
|
||||
| 切到音乐 Fragment | `toFragment(mBtMusicFragment)` | 页面容器替换 |
|
||||
| 显示音乐页面 | `fragment_bt_music.xml` | 展示蓝牙音乐 UI |
|
||||
|
||||
## 7. 页面 UI 状态流程
|
||||
|
||||
蓝牙音乐 UI 由 `isBtConnect` 控制两种状态。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["进入音乐页"]
|
||||
B{"A2DP 已连接"}
|
||||
C["音乐控制面板"]
|
||||
D["未连接提示"]
|
||||
|
||||
A --> B
|
||||
B -->|是| C
|
||||
B -->|否| D
|
||||
```
|
||||
|
||||
UI 文件:
|
||||
|
||||
- `fragment_bt_music.xml`
|
||||
- `activity_bluetooth_music.xml`
|
||||
|
||||
主要控件:
|
||||
|
||||
| 控件 ID | 作用 |
|
||||
|---|---|
|
||||
| `iv_bt_music_album` | 默认蓝牙音乐封面 |
|
||||
| `tv_bt_music_title` | 歌曲名 |
|
||||
| `tv_bt_music_artist` | 歌手 |
|
||||
| `seek_bt_music` | 播放进度展示,不允许拖动 |
|
||||
| `tv_cur_time` | 当前播放时间 |
|
||||
| `tv_song_time` | 总时长 |
|
||||
| `iv_bt_music_pre` | 上一首 |
|
||||
| `iv_bt_music_state` | 播放 / 暂停 |
|
||||
| `iv_bt_music_next` | 下一首 |
|
||||
| `ll_bt_connect_panel` | 未连接提示页 |
|
||||
| `tv_connect_btn` | 连接蓝牙按钮,目前未实现跳转 |
|
||||
|
||||
状态对应关系:
|
||||
|
||||
| 状态 | 显示内容 | 触发条件 |
|
||||
|---|---|---|
|
||||
| 音乐控制面板 | 默认封面、歌名、歌手、进度条、三个控制按钮 | `isA2dpConnected == true` |
|
||||
| 未连接提示 | 连接手机提示、连接蓝牙按钮 | `isA2dpConnected == false` |
|
||||
|
||||
## 8. A2DP 连接流程
|
||||
|
||||
A2DP 是手机音乐音频传到车机的通道。
|
||||
|
||||
进入页面后的检查:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["进入音乐页"]
|
||||
B["检查 A2DP"]
|
||||
C["获取手机设备"]
|
||||
D["发起 A2DP 连接"]
|
||||
E["等待连接结果"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
分支规则:
|
||||
|
||||
| 判断点 | 是 | 否 |
|
||||
|---|---|---|
|
||||
| A2DP 是否已连接 | 显示音乐控制面板 | 继续获取已连接手机设备 |
|
||||
| 已连接设备是否为手机 | 检查 A2DP 是否正在连接 | 保持未连接状态 |
|
||||
| A2DP 是否正在连接 | 等待连接结果 | 调用 `connectA2dp(mac)` |
|
||||
|
||||
调用定位:
|
||||
|
||||
| 步骤 | 代码定位 |
|
||||
|---|---|
|
||||
| 进入音乐页 | `BtMusicFragment.onResume` / `BluetoothMusicActivity.onResume` |
|
||||
| 检查 A2DP | `BtMusicViewModel.isA2dpConnected` |
|
||||
| 获取手机设备 | `BtMusicFragment.btMusicInfo` / `BluetoothMusicActivity.btMusicInfo` |
|
||||
| 发起连接 | `BtMusicViewModel.connectA2dp` |
|
||||
| 底层连接 | `BtSettingControlManager.connectA2dp` / `BluetoothA2dpSinkClient` |
|
||||
|
||||
触发位置:
|
||||
|
||||
- `BtMusicFragment.btMusicInfo`
|
||||
- `BluetoothMusicActivity.btMusicInfo`
|
||||
- `BtMusicViewModel.connectA2dp`
|
||||
- `BtSettingControlManager.connectA2dp`
|
||||
|
||||
连接结果回调:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["A2DP 状态变化"]
|
||||
B["SDK 连接监听"]
|
||||
C["音乐管理器处理"]
|
||||
D["LiveData 通知"]
|
||||
E["UI 切换状态"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
| 步骤 | 代码定位 | 说明 |
|
||||
|---|---|---|
|
||||
| A2DP 状态变化 | 系统蓝牙服务 | A2DP 连接或断开 |
|
||||
| SDK 连接监听 | `BluetoothConnectListenerClient` | 接收系统连接状态 |
|
||||
| 音乐管理器处理 | `BtMusicManager.onA2dpConnect / onA2dpDisconnect` | 清理或恢复蓝牙音乐状态 |
|
||||
| LiveData 通知 | `BtMusicViewModel.mLiveDataA2dpConnectStatus` | 通知 UI |
|
||||
| UI 切换状态 | `BtMusicFragment` / `BluetoothMusicActivity` | 显示音乐页或未连接页 |
|
||||
|
||||
## 9. 播放控制流程
|
||||
|
||||
### 9.1 点击播放 / 暂停
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["点击按钮"]
|
||||
B{"当前在播放"}
|
||||
C["走暂停链路"]
|
||||
D["走播放链路"]
|
||||
E["手机播放器响应"]
|
||||
|
||||
A --> B
|
||||
B -->|是| C
|
||||
B -->|否| D
|
||||
C --> E
|
||||
D --> E
|
||||
```
|
||||
|
||||
| 分支 | 调用顺序 | 说明 |
|
||||
|---|---|---|
|
||||
| 播放 | `onClick -> isMusicPlaying == false -> playBtMusic -> requestFocus -> AVRCP play` | 先拿焦点,再发送播放 |
|
||||
| 暂停 | `onClick -> isMusicPlaying == true -> pauseBtMusic -> AVRCP pause` | 暂停不需要重新申请焦点 |
|
||||
| UI 更新 | `mLiveDataPlayState -> updatePlayState` | 最终以回调状态刷新图标 |
|
||||
|
||||
### 9.2 点击上一首 / 下一首
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["点击切歌"]
|
||||
B["ViewModel 转发"]
|
||||
C["SDK 申请焦点"]
|
||||
D["发送 AVRCP 命令"]
|
||||
E["手机播放器切歌"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
底层实际命令:
|
||||
|
||||
| 操作 | 页面入口 | ViewModel | 最终调用 |
|
||||
|---|---|---|---|
|
||||
| 播放 | `onClick` | `playBtMusic` | `TransportControls.play()` |
|
||||
| 暂停 | `onClick` | `pauseBtMusic` | `TransportControls.pause()` |
|
||||
| 下一首 | `onClick` | `nextBtMusic` | `TransportControls.skipToNext()` |
|
||||
| 上一首 | `onClick` | `preBtMusic` | `TransportControls.skipToPrevious()` |
|
||||
|
||||
## 10. 歌曲信息与播放状态刷新流程
|
||||
|
||||
手机通过系统蓝牙 MediaBrowser 回传媒体状态。
|
||||
|
||||
播放状态回调:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["手机播放状态变化"]
|
||||
B["系统媒体回调"]
|
||||
C["SDK 转换状态"]
|
||||
D["LiveData 通知"]
|
||||
E["刷新按钮和进度"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
歌曲信息回调:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["手机歌曲变化"]
|
||||
B["系统元数据回调"]
|
||||
C["SDK 转换歌曲信息"]
|
||||
D["LiveData 通知"]
|
||||
E["刷新歌名和时长"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
代码定位:
|
||||
|
||||
| 回调类型 | SDK 入口 | ViewModel 入口 | UI 刷新内容 |
|
||||
|---|---|---|---|
|
||||
| 播放状态 | `onPlaybackStateChanged` | `onPlayMusicState / onPlayMusicInfo` | 播放按钮、当前进度、当前时间 |
|
||||
| 歌曲信息 | `onMetadataChanged` | `onPlayMusicInfo` | 歌名、歌手、总时长 |
|
||||
|
||||
字段来源:
|
||||
|
||||
| 字段 | 来源 |
|
||||
|---|---|
|
||||
| 歌名 | `MediaMetadata.METADATA_KEY_TITLE` |
|
||||
| 歌手 | `MediaMetadata.METADATA_KEY_ARTIST` |
|
||||
| 专辑 | `MediaMetadata.METADATA_KEY_ALBUM` |
|
||||
| 总时长 | `MediaMetadata.METADATA_KEY_DURATION` |
|
||||
| 当前进度 | `PlaybackStateCompat.getPosition()` |
|
||||
| 播放状态 | `PlaybackStateCompat.STATE_PLAYING` |
|
||||
|
||||
当前 UI 特殊点:
|
||||
|
||||
- `albumUrl` 虽然会接收,但 UI 不展示手机封面。
|
||||
- 默认封面固定使用 `R.mipmap.icon_bluetooth_music`。
|
||||
|
||||
## 11. 音频焦点流程
|
||||
|
||||
蓝牙音乐播放前需要申请音频焦点,否则可能出现手机在播但车机无声或被其他音源压住。
|
||||
|
||||
申请焦点:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["准备播放"]
|
||||
B["确认 A2DP"]
|
||||
C["请求音频焦点"]
|
||||
D["解除 A2DP 静音"]
|
||||
E["允许播放命令"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
失败规则:
|
||||
|
||||
| 失败点 | 结果 |
|
||||
|---|---|
|
||||
| A2DP 未连接 | `requestFocus` 返回 `false` |
|
||||
| 音频焦点申请失败 | 不应认为蓝牙音乐已经有效播放 |
|
||||
| MediaSession 未就绪 | AVRCP 控制可能无效 |
|
||||
|
||||
焦点变化:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["焦点变化"]
|
||||
B["静音或恢复 A2DP"]
|
||||
C["暂停或恢复播放"]
|
||||
D["刷新音乐状态"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
```
|
||||
|
||||
焦点变化处理:
|
||||
|
||||
| 焦点事件 | 当前处理 |
|
||||
|---|---|
|
||||
| `AUDIOFOCUS_GAIN` | 解除 A2DP 静音;如果丢焦前在播,则恢复播放 |
|
||||
| `AUDIOFOCUS_LOSS` | 释放焦点;暂停蓝牙音乐;清空 Launcher 音乐信息 |
|
||||
| `AUDIOFOCUS_LOSS_TRANSIENT` | A2DP 静音;记录是否正在播放;暂停 |
|
||||
| `AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK` | 降低 A2DP 音量 |
|
||||
|
||||
## 12. 断开与关闭流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["连接断开"]
|
||||
B["SDK 收到回调"]
|
||||
C["清空音乐缓存"]
|
||||
D["通知 UI 未连接"]
|
||||
E["显示未连接面板"]
|
||||
|
||||
A --> B
|
||||
B --> C
|
||||
C --> D
|
||||
D --> E
|
||||
```
|
||||
|
||||
相关回调:
|
||||
|
||||
- `onDisconnect`
|
||||
- `onA2dpDisconnect`
|
||||
- `onClose`
|
||||
- `onBtDisconnect`
|
||||
- `onBtClose`
|
||||
|
||||
清理内容:
|
||||
|
||||
| 清理项 | 目的 |
|
||||
|---|---|
|
||||
| `title / artist / album` | 避免断开后继续显示旧歌曲 |
|
||||
| `progress / duration` | 避免进度条停在旧状态 |
|
||||
| 播放状态 | 播放按钮恢复为播放图标 |
|
||||
| `mLiveDataA2dpConnectStatus = false` | UI 切回未连接面板 |
|
||||
|
||||
## 13. 当前实现中的关键状态
|
||||
|
||||
| 状态 | 位置 | 含义 |
|
||||
|---|---|---|
|
||||
| `mLastPlayState` | `BtMusicFragment` / `BluetoothMusicActivity` | 记录上次播放状态,用于页面恢复时决定是否续播 |
|
||||
| `musicPlaying` | `BtMusicViewModel` | 音频焦点丢失前是否在播放 |
|
||||
| `mMusicInfo` | `BtMusicViewModel` | 最近一次音乐信息缓存 |
|
||||
| `mLiveDataMusicInfo` | `BtMusicViewModel` | 歌曲信息 UI 通知 |
|
||||
| `mLiveDataPlayState` | `BtMusicViewModel` | 播放状态 UI 通知 |
|
||||
| `mLiveDataA2dpConnectStatus` | `BtMusicViewModel` | A2DP 连接状态 UI 通知 |
|
||||
| `isFocus` | `BtMusicAudioManager` | 蓝牙音乐是否持有音频焦点 |
|
||||
| `isPlayingLossFocus` | `BtMusicAudioManager` | 丢失焦点前是否在播放 |
|
||||
|
||||
## 14. 和老项目对齐的现状
|
||||
|
||||
已对齐:
|
||||
|
||||
- 主界面增加“蓝牙音乐”入口。
|
||||
- 蓝牙音乐 UI 使用老项目默认封面和圆形控制按钮。
|
||||
- 进度条资源复用老项目。
|
||||
- 封面固定使用默认蓝牙音乐图,不使用手机专辑图。
|
||||
- 歌名、歌手、进度、时间通过现有 SDK 刷新。
|
||||
- 播放、暂停、上一首、下一首继续走新项目 SDK,不搬旧项目业务代码。
|
||||
|
||||
未完全补齐:
|
||||
|
||||
- 未连接状态下 `tv_connect_btn` 目前没有跳转蓝牙设置页。
|
||||
- 没有“连接中”状态,例如 A2DP 正在连接时的进度提示。
|
||||
- MediaBrowser 未连接时没有明显错误提示。
|
||||
- 播放控制失败没有 UI 反馈。
|
||||
- Fragment 与 Activity 两套蓝牙音乐逻辑重复,后续可抽公共逻辑。
|
||||
|
||||
## 15. 开发切入点
|
||||
|
||||
### 15.1 要改 UI
|
||||
|
||||
优先看:
|
||||
|
||||
- `fragment_bt_music.xml`
|
||||
- `activity_bluetooth_music.xml`
|
||||
- `selector_music_play.xml`
|
||||
- `selector_music_pause.xml`
|
||||
- `selector_music_pre.xml`
|
||||
- `selector_music_next.xml`
|
||||
- `bt_music_progress_layer.xml`
|
||||
|
||||
### 15.2 要改播放控制
|
||||
|
||||
优先看:
|
||||
|
||||
- `BtMusicFragment.onClick`
|
||||
- `BluetoothMusicActivity.onClick`
|
||||
- `BtMusicViewModel.playBtMusic / pauseBtMusic / preBtMusic / nextBtMusic`
|
||||
- `BtMusicControlManager`
|
||||
- `BtMusicManager`
|
||||
- `BluetoothAvrcpClient`
|
||||
|
||||
### 15.3 要改连接逻辑
|
||||
|
||||
优先看:
|
||||
|
||||
- `BtMusicFragment.btMusicInfo`
|
||||
- `BluetoothMusicActivity.btMusicInfo`
|
||||
- `BtMusicViewModel.connectA2dp`
|
||||
- `BtSettingControlManager.connectA2dp`
|
||||
- `BluetoothA2dpSinkClient`
|
||||
|
||||
### 15.4 要改音频焦点
|
||||
|
||||
优先看:
|
||||
|
||||
- `BtMusicAudioManager.requestAudioFocus`
|
||||
- `BtMusicAudioManager.releaseAudioFocus`
|
||||
- `BtMusicAudioManager.mAudioFocusListener`
|
||||
- `BtMusicManager.requestFocus`
|
||||
|
||||
## 16. 建议补齐的功能
|
||||
|
||||
| 优先级 | 功能 | 原因 |
|
||||
|---|---|---|
|
||||
| P0 | 未连接按钮跳转蓝牙设置页 | 用户当前只有蓝牙音乐入口,通过设置页跳转时需要闭环 |
|
||||
| P0 | A2DP 连接中状态 | 避免点击后无反馈 |
|
||||
| P1 | MediaBrowser 未连接提示 | 手机蓝牙媒体服务未就绪时便于定位 |
|
||||
| P1 | 播放控制失败日志/提示 | AVRCP 命令可能因手机端播放器未启动而无效 |
|
||||
| P1 | Fragment 和 Activity 逻辑复用 | 降低后续维护成本 |
|
||||
| P2 | 进度条拖动 seek | 当前只展示,不支持拖动 |
|
||||
| P2 | 专辑图开关 | 如果后续产品要求展示手机专辑图,可恢复 `albumUrl` 加载 |
|
||||
|
||||
## 17. 测试清单
|
||||
|
||||
### 17.1 正常流程
|
||||
|
||||
- 手机已配对并连接 HFP。
|
||||
- 进入蓝牙音乐页。
|
||||
- 自动检查并连接 A2DP。
|
||||
- 手机打开音乐播放器。
|
||||
- 点击播放,车机出声。
|
||||
- 点击暂停,手机播放器暂停。
|
||||
- 点击上一首/下一首,手机播放器切歌。
|
||||
- 歌名、歌手、时间、进度刷新。
|
||||
|
||||
### 17.2 连接异常
|
||||
|
||||
- 未连接手机进入蓝牙音乐页,应显示未连接面板。
|
||||
- 手机只连 HFP 未连 A2DP,进入音乐页应尝试连接 A2DP。
|
||||
- A2DP 连接失败,应能从日志看到 `connectA2dp` 相关信息。
|
||||
- 手机断开蓝牙,UI 应切回未连接状态。
|
||||
|
||||
### 17.3 焦点与打断
|
||||
|
||||
- 蓝牙音乐播放时来电,音乐应暂停或静音。
|
||||
- 通话结束后,如果丢焦前在播,应尝试恢复。
|
||||
- 切到收音机/USB 音乐,蓝牙音乐应释放或失去焦点。
|
||||
- 重新切回蓝牙音乐,按状态恢复播放。
|
||||
|
||||
### 17.4 兼容性
|
||||
|
||||
- 不同手机品牌测试 AVRCP 元数据。
|
||||
- 手机音乐 App 未打开时点击播放。
|
||||
- 手机音乐 App 开着但无播放队列。
|
||||
- 蓝牙断开后快速重连。
|
||||
- 系统重启后首次进入蓝牙音乐。
|
||||
|
||||
## 18. 日志关键字
|
||||
|
||||
调试时可优先过滤这些关键字:
|
||||
|
||||
```text
|
||||
BtMusicFragment
|
||||
BluetoothMusicActivity
|
||||
BtMusicViewModel
|
||||
BtMusicControlManager
|
||||
BtMusicManager
|
||||
BtMusicAudioManager
|
||||
BluetoothAvrcpClient
|
||||
MediaBrowserHelper
|
||||
connectA2dp
|
||||
onA2dpConnect
|
||||
onA2dpDisconnect
|
||||
onPlaybackStateChanged
|
||||
onMetadataChanged
|
||||
requestAudioFocus
|
||||
AUDIOFOCUS_GAIN
|
||||
AUDIOFOCUS_LOSS
|
||||
```
|
||||
|
||||
## 19. 总结
|
||||
|
||||
蓝牙音乐模块可以按四条主线理解:
|
||||
|
||||
1. **入口线**:`MainActivity -> BtMusicFragment`。
|
||||
2. **连接线**:`BtMusicFragment -> BtMusicViewModel -> BtSettingControlManager -> A2DP`。
|
||||
3. **控制线**:`按钮点击 -> BtMusicViewModel -> BtMusicControlManager -> BtMusicManager -> BluetoothAvrcpClient -> 手机播放器`。
|
||||
4. **回调线**:`手机媒体状态 -> MediaBrowserClient -> BluetoothAvrcpClient -> BtMusicManager -> BtMusicViewModel LiveData -> UI`。
|
||||
|
||||
开发这个模块时,优先保证 A2DP 连接、AVRCP 控制、音频焦点三条链路可验证,再做 UI 微调和异常状态补齐。
|
||||
@@ -0,0 +1,84 @@
|
||||
# 蓝牙音乐模块流程与开发指南
|
||||
|
||||
> 这是入口文档。
|
||||
> 本次排版已去掉 Mermaid 图,改为“纯文本流程块 + 编号步骤表 + 拆分文档”,适合 Obsidian 直接阅读。
|
||||
|
||||
## 推荐阅读顺序
|
||||
|
||||
| 顺序 | 文档 | 先看什么 |
|
||||
|---|---|---|
|
||||
| 1 | [[00_总览_一页看懂]] | 先建立模块整体印象。 |
|
||||
| 2 | [[01_启动初始化流程]] | 看蓝牙音乐 SDK 是怎么启动的。 |
|
||||
| 3 | [[02_A2DP连接与断开流程]] | 看“有没有声音通道”怎么判断。 |
|
||||
| 4 | [[03_AVRCP歌曲信息与播放状态流程]] | 看歌名、歌手、播放状态从哪里来。 |
|
||||
| 5 | [[04_播放控制与音频焦点流程]] | 看播放、暂停、切歌、焦点抢占。 |
|
||||
| 6 | [[05_Launcher卡片流程]] | 看桌面卡片怎么显示和控制蓝牙音乐。 |
|
||||
| 7 | [[06_开发入口_风险点_测试清单]] | 开发前定位文件、风险点和测试项。 |
|
||||
|
||||
## 最短主链路
|
||||
|
||||
### 用户点击播放
|
||||
|
||||
```text
|
||||
用户
|
||||
↓
|
||||
BluetoothMusicActivity.onClick()
|
||||
↓
|
||||
BtMusicViewModel.playBtMusic()
|
||||
↓
|
||||
BtMusicControlManager.playBtMusic()
|
||||
↓
|
||||
BtMusicManager.requestFocus()
|
||||
↓
|
||||
BtMusicAudioManager.requestAudioFocus()
|
||||
↓
|
||||
BtMusicManager.playBtMusic()
|
||||
↓
|
||||
BluetoothAvrcpClient.play()
|
||||
↓
|
||||
MediaControllerCompat.TransportControls.play()
|
||||
↓
|
||||
手机音乐 App 开始播放
|
||||
```
|
||||
|
||||
### 手机切歌后刷新页面
|
||||
|
||||
```text
|
||||
手机音乐 App 切歌
|
||||
↓
|
||||
Android 蓝牙栈 AVRCP 元数据变化
|
||||
↓
|
||||
MediaBrowserClient.onMetadataChanged()
|
||||
↓
|
||||
BluetoothAvrcpClient.onMetadataChanged()
|
||||
↓
|
||||
BtMusicManager 解析歌曲信息
|
||||
↓
|
||||
BtMusicViewModel.mLiveDataMusicInfo
|
||||
↓
|
||||
BluetoothMusicActivity 更新歌名/歌手/封面/时长
|
||||
↓
|
||||
BtInfoPushManager.sendMusicInfoToLauncher()
|
||||
↓
|
||||
Launcher 蓝牙音乐卡片更新
|
||||
```
|
||||
|
||||
## 当前代码根路径
|
||||
|
||||
```text
|
||||
F:\zdCode\tongli
|
||||
```
|
||||
|
||||
## 蓝牙音乐最核心文件
|
||||
|
||||
| 文件 | 作用 |
|
||||
|---|---|
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothMusicActivity.java` | 蓝牙音乐页面。 |
|
||||
| `YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\BtMusicViewModel.java` | 页面状态和控制转发。 |
|
||||
| `YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\manager\BtMusicControlManager.java` | 对外音乐控制入口。 |
|
||||
| `YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtMusicManager.java` | 蓝牙音乐核心业务。 |
|
||||
| `YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothA2dpSinkClient.java` | A2DP 音频连接。 |
|
||||
| `YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothAvrcpClient.java` | AVRCP 控制和歌曲信息。 |
|
||||
| `YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\audio\BtMusicAudioManager.java` | 音频焦点和媒体按键。 |
|
||||
| `YX_Launcher\src\main\java\com\yaxon\launcher\card\BluetoothMusicCardView.java` | Launcher 蓝牙音乐卡片。 |
|
||||
|
||||