Files

825 lines
90 KiB
Markdown
Raw Permalink Normal View History

2026-11-05 14:35:04 +08:00
# agent.md
## 当前阶段
- 2026-06-05:排查并优化蓝牙通讯录/通话记录权限与同步 UI 状态。
## 已验证结论
- 清空蓝牙本地 SQLite 表不是通讯录同步失败的直接原因;正常清空后 PBAP 同步会重新写入。
- 日志中偶现的 `ConcurrentModificationException` 指向联系人保存阶段共享列表被并发访问,属于同步完成后保存/回调链路稳定性问题。
- 2026-06-06 新复测日志 `2026_0606_1105_50_063.log` 显示 UI 修复生效:
- 未出现 `Dex checksum does not match`
- 未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`
- 同步中 `ContactsFragment` 保持 `isPhoneBookSynchronized=false isPhoneBookPulling=true`
- 最终 `11:08:28.044` 收到 `ContactListViewModel: onContactsPhoneBook = 1973`
- `2026_0606_1105_50_063.log` 同时确认双进程重复同步:
- `11:06:32.967` `com.yaxon.carcontrol` PID 3859 触发 `phoneBookPullRequest`
- `11:06:32.977` `com.yaxon.bluetooth` PID 4968 触发 `phoneBookPullRequest`
- 两个进程都收到 `onContactsPullCompleted`、读取 `1973` 条、执行 `deleteContacts`
- 2026-06-06 复测日志 `2026_0606_1136_51_087.log` 确认移除 `YX_CarControl` 联系人/PBAP 初始化后双进程重复同步已消失:
- PID 3793 为 `com.yaxon.carcontrol`,只出现配对弹窗和自动同步开关日志,未出现 `BtContactsControlManager`
- PID 5101 为 `com.yaxon.bluetooth`,唯一执行 `BluetoothPbapClient: phoneBookPullRequest`
- 联系人同步成功:`11:38:25.186 contacts read completed size = 1973`,`11:38:41.378 onContactsPhoneBook = 1973`
- 通话记录同步成功:`11:38:02.223 mCallRecordBeanList size = 51`,`11:38:02.953 notifyPhoneRecordList list == 51`
- 未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`、`Dex checksum does not match`
- 2026-06-06 用户现场验证 `2026_0606_1136_51_087.log` 对应 UI 表现符合预期:
- 点击下载通讯录后联系人页显示“正在加载通讯录”
- 切到通话页面时通话列表可正常显示
- 再切回联系人页面后通讯录正常显示
- 说明联系人同步中的空列表中间态已被 loading 承接,未再误显示 0 个联系人
- 2026-06-06 现场重新清理 logcat 后复测,导出日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\verify_2026_0606_1348_32_838.log`:
- `com.yaxon.carcontrol` PID 3957 只出现配对弹窗/自动同步开关日志,未出现 `BtContactsControlManager`
- `com.yaxon.bluetooth` PID 5030 唯一执行 `BluetoothPbapClient: phoneBookPullRequest`
- 通讯录流程成功:`12:40:35.640 phoneBookPullRequest`,`12:41:22.565 onContactsPullCompleted`,`12:41:51.884 contacts read completed size = 1973`
- 通话记录流程成功:`12:41:27.963 mCallRecordBeanList size = 51`,`12:41:28.665 notifyPhoneRecordList list == 51`
- 用户切回联系人页后读取并显示成功:`12:44:48.892 onContactsPhoneBook = 1973`,`isPhoneBookSynchronized = true isPhoneBookPulling=false`
- 未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`、`Dex checksum does not match`
- 2026-06-06 用户再次点击“同步通讯录”后复测,导出日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\verify_resync_2026_0606_1352_02_395.log`:
- 再次点击会重新触发一次 PBAP 通讯录同步:`12:47:06.124 BluetoothPbapClient: phoneBookPullRequest`
- 仍然只有 `com.yaxon.bluetooth` PID 5030 执行同步,`BtContactsControlManager = 0`
- 第二次同步成功:`12:47:50.259 onContactsPullCompleted`,`13:51:27.509 contacts read completed size = 1973`,`13:51:45.832 onContactsPhoneBook = 1973`
- 第二次通话记录也成功刷新:`12:47:55.391 mCallRecordBeanList size = 51`,`12:47:55.982 notifyPhoneRecordList list == 51`
- 日志中出现设备时间从 `06-05 12:47` 跳到 `06-06 13:51` 的校时现象,因此第二次同步耗时不能直接按日志墙钟计算
- 未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`、`Dex checksum does not match`
- 2026-06-06 清空 logcat 后再次点击“同步通讯录”复测,导出日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\verify_resync_clean_2026_0606_1355_54_369.log`,补抓尾部 `verify_resync_clean_tail_2026_0606_1356_44_383.log`:
- 本轮无时间跳变,可计算耗时:`13:54:40.708 phoneBookPullRequest` 到 `13:56:06.301 onContactsPhoneBook = 1973`,约 85.6 秒
- PBAP 拉取阶段:`13:54:40.708` 到 `13:55:24.857 onContactsPullCompleted`,约 44.1 秒
- 联系人读取阶段:`13:55:24.926 cursor.getCount() = 1973` 到 `13:55:51.351 contacts read completed size = 1973`,约 26.4 秒
- 写库/页面回调阶段:`13:55:51.352 deleteContacts` 到 `13:56:06.301 onContactsPhoneBook = 1973`,约 14.9 秒
- `BtContactsControlManager = 0`,`BluetoothPairedRequestView = 0`,说明手动同步未经过 `YX_CarControl` 配对弹窗
- 未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`、`Dex checksum does not match`
- 2026-06-06 通话页 loading UI 修复后复测日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0606_1405_50_910.log`:
- 新逻辑已生效:切回通话页时出现 `CallLogsFragment: onHiddenChanged hidden=false` 后紧跟 `setIsBtConnect=true`
- 通话记录同步完成:`14:07:31.863 mCallRecordBeanList size = 51`,`14:07:32.670 notifyPhoneRecordList list == 51`
- 通讯录同步完成:`14:07:56.555 contacts read completed size = 1973`,`14:08:14.822 onContactsPhoneBook = 1973`
- 切回通话页时即使 `isPhoneBookPulling=true`,日志仍显示 `CallRecordUpdated = true` 并刷新 `notifyPhoneRecordList list == 51`
- 本轮未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`
- 本轮出现 `Dex checksum does not match`,推测为系统 APK 覆盖推送后旧 dex/oat 缓存未刷新,需重启设备或清理对应编译缓存后再做最终确认
- 2026-06-06 重新加载/重启后复测日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0606_1413_21_443.log`,补抓尾部 `tail_after_2026_0606_1413_21_443_2026_0606_1416_43_579.log`:
- 未再出现 `Dex checksum does not match`
- 通话页切回刷新逻辑生效:`CallLogsFragment: onHiddenChanged hidden=false` 后出现 `setIsBtConnect=true`
- 通话记录同步完成:`14:14:39.747 mCallRecordBeanList size = 51`,`14:14:40.416 notifyPhoneRecordList list == 51`
- 通讯录同步完成:`14:15:06.632 contacts read completed size = 1973`,补抓尾部 `14:15:22.731 onContactsPhoneBook = 1973`
- PBAP 断开完成:`14:15:22.821 prevState = 2 state = 3`,`14:15:22.886 prevState = 3 state = 0`
- 本轮未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`
- `2026_0605_1739_21_177.log` 中“先显示 0 个联系人,随后显示联系人列表”的原因不是同步失败,而是 UI 状态提前:
- `17:40:48.576` 收到 `BtContactsManager: onContactsPullCompleted`
- `17:40:48.623` 系统联系人 Provider 查到 `1973` 条
- `17:41:19.867` SDK 读联系人完成:`contacts read completed size = 1973`
- `17:41:30.114` 页面判断 `isPhoneBookSynchronized = true isPhoneBookPulling=true`
- `17:41:36.979` ViewModel 才收到 `onContactsPhoneBook = 1973`
- `isPhoneBookSynchronized()` 只查 DB 是否存在 1 条联系人;保存大量联系人时第一条写入后就可能返回 true,导致联系人页提前显示空列表区域。
## Bug 与修复记录
- 2026-06-09 蓝牙音乐封面继续扩展到 `YX_SystemUI` / `YX_Launcher`:
- 用户确认先备份,已备份 `DisplayShareViewModel.java`、`MediaControlView.java`、`WidgetsMedia.java` 到 `.codex_backup\music_cover_systemui_launcher_20260609_194529`
- 最新日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0609_1939_16_256.log` 显示 `YX_Music` 已能在封面文件稳定后加载成功,剩余问题集中在 SystemUI/Launcher 空 `albumUrl` 或无效文件覆盖已有封面
- `YX_SystemUI` 的 `MediaControlView` 增加蓝牙曲目 key 与封面显示状态:同一首歌收到空 `albumUrl` 或无效文件时保留当前封面;新歌收到空/无效封面时显示默认图,避免继续显示上一首封面
- `YX_Launcher` 的 `WidgetsMedia` 按相同规则处理手写 widget 封面逻辑,并避免 `BitmapFactory.decodeFile()` 直接解码空路径
- `YX_Launcher` 的 `DisplayShareViewModel.btMusicAlbum` 数据绑定入口增加 lastAlbum 缓存:同一首歌后续空路径/0 字节/解码失败时返回当前封面,新歌无有效封面时返回默认图
- 已执行 `.\gradlew :YX_SystemUI:assembleDebug` 和 `.\gradlew :YX_Launcher:assembleDebug`,均编译通过
- 已推送 `YX_SystemUI.apk` 到 `/system/app/YX_SystemUI/YX_SystemUI.apk`,推送 `YX_Launcher.apk` 到 `/system/app/YX_Launcher/YX_Launcher.apk`,并执行设备重启;重启后 `sys.boot_completed=1`,两个包路径正常
- 2026-06-08 蓝牙音乐封面短暂显示后消失问题:
- 最新日志显示 SDK 可收到 `albumUrl = /sdcard/ID3/id3cover.JPEG`,但 `YX_Music` 侧多次出现 `Failed to create image decoder` 和 `BluetoothMusicViewModel: 获取bitmap为null`
- `YX_BluetoothSDK` 只转发系统蓝牙 `MediaMetadata.METADATA_KEY_ALBUM_ART_URI`,未发现生成 `/sdcard/ID3/id3cover.JPEG` 的逻辑
- `YX_Music` 已调整封面加载兜底:先解码新图片,成功后再替换并回收旧图;解码失败时如果当前已有文件封面则保留当前封面
- `BluetoothMusicFragment` 已调整空 `albumUrl` 处理:当前没有文件封面时才显示默认图,避免元数据短暂空值覆盖已有封面
- 2026-06-08 最新日志 `2026_0608_1939_20_273.log` 确认 `/sdcard/ID3/id3cover.JPEG` 文件存在且可读,但长度始终为 0:
- `BluetoothMusicViewModel: 获取bitmap为null, filePath = /sdcard/ID3/id3cover.JPEG, exists = true, length = 0, canRead = true`
- 本轮日志中 `length 0 = 53`,未发现非 0 字节封面文件
- `YX_BluetoothSDK` 已做最小颗粒过滤:`BtMusicManager` 和 `BtInfoPushManager` 下发封面前校验本地文件路径,0 字节/不可读/不存在的封面路径不再下发
- 同时增加 `ALBUM_ART_URI`、`ART_URI`、`DISPLAY_ICON_URI`、`ALBUM_ART`、`ART` 字段诊断日志,暂不做 bitmap 落盘,避免扩大改动
- 2026-06-08 最新日志 `2026_0608_1958_40_593.log` 确认切歌后封面路径晚于歌曲信息到达:
- 点击下一首后,歌名通常先到,`albumUrl` 为空;约 2 到 4 秒后才出现固定路径 `/sdcard/ID3/id3cover.JPEG`
- 由于系统复用同一个固定封面文件,过早解码可能读到上一首封面内容,导致“先显示上一首,过会儿显示正确”
- `BluetoothMusicFragment` 已增加切歌封面调度:切歌或曲目 key 变化时先显示默认封面;固定 ID3 封面路径延迟 1 秒解码;使用版本号取消旧封面延迟任务
- 2026-06-09 继续分析日志 `2026_0608_2015_49_223.log`,确认 1 秒延迟仍不足以规避固定封面文件重写竞态:
- `skip invalid album file = 111`,说明 `YX_BluetoothSDK` 的 0 字节过滤已生效
- `BluetoothMusicViewModel: 获取bitmap为null = 8`,说明 `YX_Music` 延迟解码时仍可能撞上 `/sdcard/ID3/id3cover.JPEG` 被底层清零或半写入
- 典型时序:`20:18:11.761` 底层开始 `get image`,`20:18:11.803` SDK 检测 `length = 0` 并过滤,`20:18:11.808` `YX_Music` 解码同一路径失败,`20:18:12.294` 底层才提示 `avrcp image download succeed`
- 根因是 `/sdcard/ID3/id3cover.JPEG` 为底层复用固定路径,单次下发时校验和页面实际解码之间仍存在文件重写竞态
- 本轮按用户要求先只修改 `YX_Music`:`BluetoothMusicFragment` 对固定 ID3 封面路径增加文件刷新检测、连续两次长度/修改时间稳定后再解码、最多 20 次 300ms 重试;切歌/上一首/下一首时重置检测状态并先显示默认封面
- `BluetoothMusicViewModel.loadFileImageToView()` 改为返回解码是否成功,解码失败时保留已有有效文件封面;无有效文件封面时才回退默认图
- `BtContactsManager` 已增加联系人列表快照防御:
- PBAP 联系人读取完成后使用 `contactsSnapshot`
- 保存 DB 和回调 UI 不再直接传共享的 `mContactsList`
- `saveContactsList()` 增加 `null` 判断和防御拷贝
- `ContactsFragment` 已调整联系人页显示时序:
- LiveData 收到非空联系人时,先填充 `mPhoneBookList`,再显示联系人列表
- 已同步但本地联系人列表为空时,先显示 loading,不提前显示空列表
- 点击一键同步且 PBAP 正在同步时,联系人页显示 loading,避免重复同步 Toast
- 重新同步期间如果读取到旧库联系人,但本轮 PBAP 未完成(`isPhoneBookPulling=true && getContactLoadWonType=false`),忽略这批旧数据并保持 loading,避免提前显示旧通讯录列表
- `YX_CarControl` 已移除配对弹窗中对 `BtContactsControlManager.getInstance().init(context)` 的调用:
- `YX_CarControl` 只保留自动同步开关设置
- 联系人/PBAP 同步统一由 `YX_BlueTooth` 进程负责
- 避免 `YX_CarControl` 和 `YX_BlueTooth` 两个进程同时注册 PBAP 完成广播并重复删写联系人表
- `CallLogsFragment` 已修复通话页切回后仍显示 loading 的 UI 问题:
- `onHiddenChanged(false)` 时重新执行 `setIsBtConnect(...)`,确保切回通话页会按最新通话记录同步状态刷新 UI
- `readCallRecordUpdated()` 中已有通话记录列表时优先显示列表,不再被 `isPhoneBookPulling=true` 覆盖成 loading
- 补充兜底:`notifyPhoneRecordList()` 收到非空通话记录时直接关闭 loading、显示通话列表,避免通话记录已到但外层状态未及时收敛导致页面仍转圈
- 2026-06-06 复测日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0606_1528_11_585.log` 确认联系人页重新同步期间仍会显示旧列表:
- `15:31:00.768` 第二次 `phoneBookPullRequest`
- `15:31:04.208` 到 `15:31:20.734` 多次切回联系人页,均为 `isPhoneBookSynchronized = true isPhoneBookPulling=true`
- 本轮联系人实际到达为 `15:32:25.450 onContactsPhoneBook = 1973`
- 根因是切回联系人页时 `syncPhoneBookSynchronized(true)` 只看本地库已有联系人,未优先判断“本轮同步尚未完成”,导致旧内存列表重新显示
- 已追加联系人同步中隐藏旧列表修复:
- `BtContactsManager.phoneBookPullRequest()` 开始新一轮同步时重置 `isContactsPullCompeted=false`
- `ContactsFragment.syncPhoneBookSynchronized()` 优先判断 `isPhoneBookPulling && !getContactLoadWonType()`,该状态强制显示 loading 并隐藏联系人列表
- `showContactsLoadingPanel()` 统一隐藏蓝牙连接面板并调用 `updateLoadPage(true)`
- 2026-06-06 复测日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0606_1556_40_545.log` 确认通话记录“显示空”不是同步失败:
- 无 `Dex checksum does not match`
- `15:57:41.356` 通话记录同步到 `mCallRecordBeanList size = 51`
- `15:57:41.377` 保存通话记录 `saveCallRecordList size = 51`
- `15:58:24.959` 切回通话页后 `notifyPhoneRecordList list == 51`
- 根因是 `CallLogsFragment.notifyPhoneRecordList()` 先填充字段 `mCallRecordList`,再把同一个 list 传给 `CallRecordAdapter.setData()`;Adapter 内部先 `clear()` 再 `addAll()`,导致自清空,日志显示 51 但 Adapter 实际为空
- 已修复通话记录 Adapter 自清空问题:
- `CallLogsFragment.notifyPhoneRecordList()` 改为传入 `new ArrayList<>(mCallRecordList)`,避免 Adapter 清空同一个数据源引用
## 验证记录
- 2026-06-08:蓝牙音乐封面兜底修复后,`.\gradlew :YX_Music:assembleDebug` 编译通过。第一次沙箱内编译因读取本机 Android SDK `android.jar` 权限失败,使用非沙箱权限重跑通过。
- 2026-06-08:`YX_BluetoothSDK` 封面路径有效性过滤后,`.\gradlew :YX_BluetoothSDK:assembleDebug` 编译通过;随后 `.\gradlew :YX_Music:assembleDebug`、`.\gradlew :YX_BlueTooth:assembleDebug` 集成编译通过。
- 2026-06-08:`BluetoothMusicFragment` 切歌封面延迟加载修复后,`.\gradlew :YX_Music:assembleDebug` 编译通过。
- 2026-06-09:`YX_Music` 固定 ID3 封面文件稳定性检测和重试修复后,`.\gradlew :YX_Music:assembleDebug` 编译通过;尚待推送设备后复测切歌封面是否仍出现默认图/上一首封面/解码失败。
- 2026-06-09:`YX_Music.apk` 已推送到设备 `/system/app/YX_Music/YX_Music.apk`,设备旧包已备份到 `/sdcard/YX_Music.apk.bak_20260609_112853`;尚未重启设备,需重启后复测以避免旧 dex/oat 缓存影响。
- 2026-06-09:重新执行 `.\gradlew :YX_Music:assembleDebug` 编译通过,并再次推送 `/system/app/YX_Music/YX_Music.apk`;推送前设备当前包备份到 `/sdcard/YX_Music.apk.bak_20260609_113508`,随后已重启设备。
- 2026-06-09:重启后启动验证日志已保存到 `C:\Users\Administrator\Desktop\徐工日志\yx_log\verify_music_yxmusic_only_20260609_1142.log`:
- `sys.boot_completed = 1`
- `pm path com.yaxon.music` 指向 `/system/app/YX_Music/YX_Music.apk`
- 通过 `am start -n com.yaxon.music/com.yaxon.musicplay.view.UsbMainActivity` 可启动应用
- 启动阶段未发现 `Dex checksum`、`FATAL EXCEPTION`、`获取bitmap为null`、`Failed to create image decoder`
- 当前日志显示 `isMusicPlaying false`,尚未覆盖蓝牙播放中切歌封面场景;需现场播放蓝牙音乐并切歌后再抓日志确认
- 2026-06-09:分析 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0609_1409_00_592.log`,最后一段同专辑 `与我无关` 复用固定封面路径;`14:18:50-14:18:51` 底层写 `/sdcard/ID3/id3cover.JPEG` 时出现 `length=0 -> 16146 -> 24220 -> 43843`,`YX_Music` 已等待文件稳定,`SystemUI/Launcher` 仍会直接读固定路径。为复现确认,`YX_Music` 仅追加诊断日志:
- `BluetoothMusicFragment` 记录 ID3 文件稳定确认、准备加载、加载失败重试、重置检查状态
- `BluetoothMusicViewModel` 记录默认封面设置、文件封面解码成功的文件长度/修改时间/bitmap 宽高/byteCount
- 2026-06-09:`YX_Music` 蓝牙封面诊断日志追加后,`.\gradlew :YX_Music:assembleDebug` 编译通过;已备份设备旧包到 `/sdcard/YX_Music.apk.bak_20260609_143548`,新包已推送到 `/system/app/YX_Music/YX_Music.apk` 并重启设备;重启后确认 `sys.boot_completed=1`,`pm path com.yaxon.music` 指向系统路径。
- 2026-06-09:分析 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0609_1451_00_890.log`,确认 `YX_Music` 未解码失败,但固定 ID3 封面稳定判断过早:
- `loadFileImageToView success = 1125`,`获取bitmap为null = 0`,`loadFileImageToView e = 0`
- 同一 `lastModified` 下出现多个成功加载长度,例如 `1780988430000: 8072 -> 16146 -> 27885`
- 典型时序:`15:00:30.103` 先成功加载 `length=8072`,`15:00:30.293` 底层才 `avrcp image download succeed`,`15:00:30.403` 再成功加载 `length=27885`
- 根因是元数据回调密集,连续两次长度/修改时间相同可在数毫秒内发生,当前稳定判断会把半写入文件误判为稳定
- 2026-06-09:继续仅修改 `YX_Music`,延后 `SystemUI/Launcher`:
- `BluetoothMusicFragment` 增加 `ID3_ALBUM_STABLE_DURATION_MS=500`,固定 ID3 封面必须长度/修改时间连续稳定满 500ms 才允许解码
- 合并重复的 ID3 延迟加载请求,避免密集元数据回调堆叠多个相同 runnable
- 已显示同一长度/修改时间的 ID3 文件时跳过重复解码,减少反复 setImageBitmap
- `.\gradlew :YX_Music:assembleDebug` 编译通过;设备旧包备份到 `/sdcard/YX_Music.apk.bak_20260609_160339`,新包已推送到 `/system/app/YX_Music/YX_Music.apk` 并重启;重启后确认 `sys.boot_completed=1`,`pm path com.yaxon.music` 正常
- `.\gradlew :YX_BlueTooth:assembleDebug` 已编译通过。
- 最新 `ContactsFragment` UI 时序修改已编译通过,但在用户中断前尚未执行 `adb push` 到设备验证。
- 2026-06-06:`YX_BlueTooth` APK 已重新 clean 编译并推送到 `/system/app/YX_BlueTooth`。
- 2026-06-06:`CallLogsFragment` loading UI 修复后,`.\gradlew :YX_BlueTooth:assembleDebug` 编译通过,并已推送到 `/system/app/YX_BlueTooth/YX_BlueTooth.apk`,设备端大小 `21232834`。
- 2026-06-06:`CallLogsFragment.notifyPhoneRecordList()` 非空列表强制关闭 loading 兜底后,`.\gradlew :YX_BlueTooth:assembleDebug` 编译通过,并已推送到 `/system/app/YX_BlueTooth/YX_BlueTooth.apk`,设备端大小 `21232896`。
- 2026-06-06:`ContactsFragment` 重新同步期间过滤旧库联系人兜底后,`.\gradlew :YX_BlueTooth:assembleDebug` 编译通过,并已推送到 `/system/app/YX_BlueTooth/YX_BlueTooth.apk`,设备端大小 `21233011`。
- 2026-06-06:联系人页切回时隐藏旧列表修复后,`.\gradlew :YX_BlueTooth:assembleDebug` 编译通过,并已推送到 `/system/app/YX_BlueTooth/YX_BlueTooth.apk`,设备端大小 `21233092`。
- 2026-06-06:通话记录列表 Adapter 自清空修复后,`.\gradlew :YX_BlueTooth:assembleDebug` 编译通过,并已推送到 `/system/app/YX_BlueTooth/YX_BlueTooth.apk`,设备端大小 `21233566`。
- 2026-06-06 复测日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0606_1605_11_651.log`:
- 用户确认通话列表已有显示
- 通话记录同步成功:`16:05:58.835 mCallRecordBeanList size = 51`,`16:05:58.843 saveCallRecordList size = 51`,`16:05:59.636 notifyPhoneRecordList list == 51`
- 通讯录同步成功:`16:06:24.788 contacts read completed size = 1973`,`16:06:40.194 onContactsPhoneBook = 1973`
- `BtContactsControlManager = 0`,未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`
- 仍出现 `Dex checksum does not match`,最终验收建议重启设备后再抓一轮无 dex 缓存异常日志
- 2026-06-06 最新复测日志 `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0606_1612_17_622.log` 作为当前最终有效验证:
- `Dex checksum does not match = 0`
- `com.yaxon.carcontrol` 只出现 `BluetoothPairedRequestView` 配对弹窗/自动同步开关日志,`BtContactsControlManager = 0`
- `com.yaxon.bluetooth` 唯一触发 `16:13:20.668 phoneBookPullRequest`
- 通话记录同步和显示成功:`16:14:11.907 mCallRecordBeanList size = 51`,`16:14:11.921 saveCallRecordList size = 51`,`16:14:12.673 notifyPhoneRecordList list == 51`;同步完成后切回通话页 `16:15:36.739 notifyPhoneRecordList list == 51`
- 通讯录同步成功:`16:14:35.249 contacts read completed size = 1973`,`16:14:51.422 onContactsPhoneBook = 1973`
- 未出现 `ConcurrentModificationException`、`onContactsPullFailed`、`通讯录同步失败`
## 待办事项
- 清空蓝牙联系人/通话记录表后重新验证:
- 联系人权限允许后点击一键同步
- 同步过程中切换联系人页/通话记录页
- 确认不再短暂显示 0 个联系人列表
- 确认同步完成后显示实际联系人数量
- 确认无 `ConcurrentModificationException` 和“通讯录同步失败” Toast
- 编译并推送 `YX_CarControl.apk` 后重启设备,重新确认只剩 `com.yaxon.bluetooth` 一个进程执行 `BtContactsManager` 联系人同步。
## 更新记录
- 2026-06-23:调整语音 `playDislike`(不喜欢这首歌)处理逻辑:
- 用户确认本次不做文件备份。
- `YX_Voice/src/main/java/com/yaxon/voice/protocol/VoiceProtocol.java` 中 `CMD_PLAY_DISLIKE` 从原先复用下一首逻辑改为音频源取消收藏逻辑。
- 视频源仍保持下一个视频:`sendVideoController(CMD_VIDEO_NEXT)`。
- 收音机源改为复用 `sendMusicUnFavorites(false)`,走现有收音机取消收藏按键链路。
- USB/蓝牙音乐源改为复用 `sendMusicUnFavorites(true)`,向音乐模块发送 `CMD_PLAY_UNFAVORITES`。
- 验证:`.\gradlew :YX_Voice:assembleDebug` 编译通过。
- 2026-06-22:按 TXZ 反馈调整“可见即可说”验证方向:
- TXZ 确认 `shadow.register` 属于网页、小程序、自绘等非常规页面的主动插词注册方式;普通原生页面不需要主动同步给 TXZ,语音侧会通过无障碍扫描 `id/text/contentDescription/clickable/scrollable`。
- 本次选 `YX_CarControl` 的语音设置页 `fragment_voice.xml` 做普通页面扫描验证:为语音唤醒、免唤醒、免唤醒时长、系统语音包、唤醒词入口补充 `contentDescription`、`clickable/focusable` 和滚动容器 id。
- `CustomTriStateSwitch` 按文档要求在 `onInitializeAccessibilityNodeInfo()` 中暴露 `checkable/checked/clickable/enabled`,便于 TXZ 判断自定义开关状态。
- `VoiceFragment` 根据开关当前状态动态更新 `contentDescription`:关闭态暴露“打开/开启”,开启态暴露“关闭/关掉”,避免状态型控件同时暴露正反插词导致动作反向。
- 暂停 `YX_CarControl` 启动后自动注册测试插词,不再自动发送 `shadow.register`,但保留 `registerVisibleSpeakKeywords(...)` / `unregisterVisibleSpeakKeywords(...)` 预留接口供后续非常规页面使用。
- 验证:`.\gradlew :YX_CarControl:assembleDebug` 编译通过。
- 2026-06-22 更新:语音设置页可能与既有语义命令重叠,不适合作为无障碍扫描效果验证页,已新增维保页独立测试点:
- `fragment_maintenance.xml` 为保养里程选项和保养重置按钮补充独特测试插词:`可见测试关闭保养里程`、`可见测试五千公里`、`可见测试一万公里`、`可见测试三万公里`、`可见测试保养重置`。
- `MaintenanceFragment` 在对应点击入口增加 `visible speak test click: ...` 日志,便于判断语音命中后是否真实触发控件点击。
- 验证:`.\gradlew :YX_CarControl:assembleDebug` 编译通过;新包已推送到 `/system/app/YX_CarControl/YX_CarControl.apk` 并重启设备,`sys.boot_completed=1`,`pm path com.yaxon.carcontrol` 指向系统路径。
- 2026-06-22 更新:维保页 RadioButton/contentDescription 测试现场未响应,进一步简化为系统页可见标准按钮测试:
- 在 `fragment_system.xml` 顶部新增标准 `Button`,可见文本为 `可见测试按钮`,依赖 TXZ 扫描 `android:text`,避免 RadioButton、隐藏标签、既有语义干扰。
- `SystemFragment` 绑定按钮点击后打印 `visible speak simple test button clicked` 并 Toast `可见测试按钮已点击`。
- 验证:`.\gradlew :YX_CarControl:assembleDebug` 编译通过;新包已推送到 `/system/app/YX_CarControl/YX_CarControl.apk` 并重启设备,`sys.boot_completed=1`,`pm path com.yaxon.carcontrol` 指向系统路径。
- 2026-06-13:抽取“可见即可说”业务侧公共工具:
- 用户确认本次不做文件备份。
- 新增 `YX_Lib_CommonBase/src/main/java/com/yaxon/common/base/voice/VisibleSpeakHelper.java`,统一封装业务模块向 `YX_Voice` 发送 `cmd.visibleSpeakRegister` / `cmd.visibleSpeakUnregister` 的 Bundle 组装和 IPC 发送逻辑。
- `VisibleSpeakHelper` 同时提供 `parseExecute(Bundle)`,业务模块收到 `cmd.visibleSpeakExecute` 后可直接解析出 `packageName`、`className`、`pageName`、`keyword`。
- `YX_Lib_CommonBase/build.gradle` 增加 `implementation project(path: ':YX_IPC_SDK')`,用于公共工具内部调用 `YxIPC`。
- `YX_CarControl` 的 `VoiceSettingManager` 保留测试插词注册时机和命中日志,但注册/注销发送改为复用 `VisibleSpeakHelper`,避免后续模块重复手写 Bundle。
- 验证:`.\gradlew :YX_CarControl:assembleDebug` 编译通过;`.\gradlew :YX_Voice:assembleDebug` 编译通过。
- 2026-06-13 更新:为避免 `YX_Lib_CommonBase` 扩大 IPC 依赖面,已移除 `YX_Lib_CommonBase/build.gradle` 中的 `implementation project(path: ':YX_IPC_SDK')`:
- `VisibleSpeakHelper` 改为只负责 `buildRegisterBundle(...)`、`buildUnregisterBundle(...)`、`parseExecute(...)` 等参数构造/解析,不再直接 import 或调用 `YxIPC`。
- 业务模块仍由自身已有的 `YX_IPC_SDK` 依赖执行 `YxIPC.getInstance().sendRemoteNoCall(...)`;当前 `YX_CarControl` 已按此方式调整。
- 验证:`.\gradlew :YX_CarControl:assembleDebug`、`.\gradlew :YX_Voice:assembleDebug` 编译通过;单独 `.\gradlew :YX_Lib_CommonBase:assembleDebug` 在沙箱内因本机 Android SDK `android.jar` 读取权限失败,非沙箱授权请求被审批服务异常拒绝,但 `YX_CarControl`/`YX_Voice` 编译已覆盖该公共库改动。
- 2026-06-13:预留“可见即可说”自动注册框架接口:
- 用户确认本次不做文件备份。
- 阅读《同行者可见即可说自动注册框架对接方案.docx》后,按文档预留 `shadow.register`、`shadow.unregister`、`shadow.execute` 三段链路。
- `YX_Lib_CommonBase` 的 `VoiceNewConstant` 增加可见即可说 IPC 命令和参数 key:包名、类名、页面名、插词列表。
- `YX_Voice` 的 `VoiceNewProtocolConst` 增加 TXZ 侧 method/字段常量:`key_type`、`action`、`keywordList`、`cmd` 等。
- `YX_Voice` 的 `BaseProtocol` 增加可见即可说注册/注销/执行分发;`VoiceEventHelper` 负责把应用注册转成 TXZ 请求,并把命中的插词回传给目标应用;`VoiceProtocol` 接收 TXZ `shadow.execute` 后回传应用。
- `YX_CarControl` 的 `VoiceSettingManager` 增加 `registerVisibleSpeakKeywords(...)` / `unregisterVisibleSpeakKeywords(...)` 预留方法,并在语音 IPC 和 TXZ bind 都就绪后注册一组测试插词 `可见即可说测试#打开可见即可说测试`,命中后当前只记录 `handleVisibleSpeakExecute` 日志,不执行真实业务动作。
- 验证:`.\gradlew :YX_Voice:assembleDebug` 编译通过;`.\gradlew :YX_CarControl:assembleDebug` 编译通过。
- 待现场验证:推送 `YX_Voice.apk` / `YX_CarControl.apk` 后观察日志中是否出现 `registerVisibleSpeakKeywords`、`sendCommandTxz → method= shadow.register`;说出测试插词后确认是否出现 `shadow.execute` 和 `handleVisibleSpeakExecute`。
- 已知风险:TXZ 侧 `shadow.register` 参数结构目前按文档文字和流程图推断为 `keywordList` 使用 `#` 分隔字符串;若同行者实际要求 array/list 或字段名不同,需要按现场日志调整。
- 2026-06-12:修复语音前台 TTS 播报转发命令:
- 用户确认本次不做文件备份。
- 核对链路:`YX_CarControl` 的 `VoiceSettingManager.sendForegroundTtsPlay(...)` 发送 `cmd.syncForegroundTtsText` 到 `YX_Voice`。
- `YX_Voice` 的 `BaseProtocol` 已能将 `cmd.syncForegroundTtsText` 分发到 `VoiceEventHelper.playTtsOnUi(...)`。
- 修复点:`VoiceEventHelper.playTtsOnUi(...)` 转发 TXZ 时由 `syncBackgroundTtsText` 改为 `syncForegroundTtsText`,后台 `playTtsInBackground(...)` 保持 `syncBackgroundTtsText` 不变。
- 验证:`.\gradlew :YX_Voice:assembleDebug` 编译通过。
- 待现场验证:触发仪表脱手报警“请手握方向盘”或其它前台 TTS 场景,确认日志出现 `sendForegroundTtsPlay`、`playTtsOnUi` 且 TXZ 前台播报生效。
- 2026-06-09:新增语音前台 TTS 文本同步接口 `syncForegroundTtsText`:
- `VoiceNewConstant` 增加 IPC 入站命令 `CMD_SYNC_FOREGROUND_TTS_TEXT = "cmd.syncForegroundTtsText"`
- `VoiceNewProtocolConst` 增加 TXZ 协议命令 `CMD_SYNC_FOREGROUND_TTS_TEXT = "syncForegroundTtsText"`
- `BaseProtocol` 新增前台 TTS 分支,收到 `cmd.syncForegroundTtsText` 后调用 `VoiceEventHelper.playTtsOnUi(...)`
- `VoiceEventHelper.playTtsOnUi(...)` 改为向 TXZ 发送 `syncForegroundTtsText`,后台 `syncBackgroundTtsText` 保持不变
- `VoiceSettingManager` 新增 `sendForegroundTtsPlay(String tts)`,供 `YX_CarControl` 侧发送前台 TTS
- `.\gradlew :YX_Voice:assembleDebug` 编译通过
- `.\gradlew :YX_CarControl:assembleDebug` 编译通过
- 2026-06-09:继续仅修改 `YX_Music` 蓝牙音乐封面加载策略:
- 切歌后 `albumUrl` 为空时,不再立刻判定无封面;进入固定 ID3 封面 `/sdcard/ID3/id3cover.JPEG` 等待链路。
- 同一首歌已经显示文件封面后,后续空 `albumUrl` 不清空当前封面。
- ID3 封面重试从原约 6 秒扩展为前 20 次 300ms、后续 1s、最多 50 次,总窗口约 36 秒,用于覆盖封面二十几秒后才下发的场景。
- `.\gradlew :YX_Music:assembleDebug` 编译通过;`YX_Music.apk` 已推送到 `/system/app/YX_Music/YX_Music.apk` 并重启设备,`sys.boot_completed=1`,`pm path com.yaxon.music` 指向系统路径。
- 2026-06-16:修复蓝牙联系人页权限恢复后“正在同步通讯录”卡住:
- 用户确认本次不做文件备份。
- 复现日志 `2026_0616_1437_51_345.log` 显示本轮 `phoneBookPullRequest = 0`、`onContactsPullCompleted = 0`,不是 PBAP 真同步;权限置 0 后 `ContactListViewModel` 仍回调 `onContactsPhoneBook = 257`,但 `ContactsFragment` 会先清空 `mPhoneBookList` 并因无权限返回。
- 权限重新确认后,页面状态为 `isPhoneBookSynchronized = true isPhoneBookPulling=false`,但内存列表为空,旧逻辑直接显示 `llLoading`,导致“正在同步通讯录”没有后续回调关闭。
- `ContactsFragment` 增加本地通讯录重读收敛:已同步且列表为空、未处于 PBAP 拉取时,只触发一次 `readPhoneBook()` 从本地库回填;若回填为空则关闭 loading 并显示下载入口,避免无限 loading。
- 验证:`.\gradlew :YX_BlueTooth:assembleDebug` 编译通过。
- 2026-06-16:更新版本后复核日志 `2026_0616_1437_51_345.log`:
- 日志更新时间为 `2026/6/16 15:14:16`,大小 `16500679`。
- `15:09:33.236` 与 `15:13:01.840` 均触发 `BluetoothPbapClient: phoneBookPullRequest`,随后分别在 `15:09:40.295`、`15:13:09.663` 出现 `onContactsPullCompleted`。
- 两轮 PBAP 均读到联系人 `257` 和通话记录 `51`,最终状态均回到 `isPhoneBookSynchronized=true isPhoneBookPulling=false`,说明真实同步链路已完成。
- 本次日志未出现新修复分支日志 `phonebook synchronized but local list is empty, reload local phonebook`,暂不能证明本地空列表兜底分支已被执行。
- 日志仍出现 `/system/app/YX_BlueTooth/YX_BlueTooth.apk!classes2.dex` 的 `Dex checksum does not match`,建议推送系统 APK 后重启设备或清理旧 oat/dex 缓存再复测,避免运行旧代码或混合 dex。
- 2026-06-16:复测日志 `2026_0616_1533_52_917.log` 验证蓝牙联系人权限恢复兜底:
- 日志更新时间为 `2026/6/16 15:37:06`,大小 `11242513`,本轮未检出 `Dex checksum does not match`。
- `15:35:03`、`15:35:27`、`15:36:06`、`15:36:38` 多次出现通讯录权限置 0;权限关闭期间本地库仍能回调 `onContactsPhoneBook = 257`,但 UI 按权限拒绝分支处理。
- `15:35:16.420` 和 `15:36:50.420` 出现 `phonebook synchronized but local list is empty, reload local phonebook`,证明新增兜底分支已执行。
- 兜底触发后立即执行 `BtContactsManager: readPhoneBook`,随后回调 `ContactListViewModel: onContactsPhoneBook = 257`,页面可从本地通讯录恢复,不再停留在无限 loading。
- 用户手动/后续触发真实 PBAP 同步时,`15:35:34.363`、`15:36:15.769`、`15:36:52.932` 出现 `phoneBookPullRequest`,并分别完成 `onContactsPullCompleted`、联系人 `257`、通话记录 `51`、`getStartPullLivedata = false`。
- 当前结论:修复有效;后续可考虑减少权限恢复时的重复 `readPhoneBook()` 触发,但不影响主问题闭环。
- 2026-06-23:按 TXZ 无障碍服务 `process is bad`/扫描不起来问题调整 `YX_Voice` 恢复策略:
- 用户前置确认本次不做文件备份,直接修改。
- `VoiceService` 不在 `onCreate()` 常态执行无障碍绑定恢复,改为 TXZ `onConnected()` 后延迟 2 秒执行一次。
- 新增 `mTxzConnected` 与 `mAccessibilityServiceRefreshDone` 状态保护:TXZ 断开后取消待执行任务并允许下次连接重新恢复;已成功恢复后不重复重绑。
- 恢复逻辑先读取当前 `Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES`,保留已有服务,只针对 `com.txzing.voiceassistant/com.txznet.shadow.words.ShadowAccessibilityService` 做移除再加回,触发系统重新绑定 TXZ 无障碍服务。
- 不主动追加 `YX_Factory` 无障碍服务,避免扩大影响面;如果 Factory 已在列表中会被原样保留。
- 补充空列表保护,避免 `ENABLED_ACCESSIBILITY_SERVICES` 为 `null` 或空字符串时 `containsAccessibilityService()` 空指针。
- 验证:`.\gradlew :YX_Voice:assembleDebug` 编译通过。后续推送设备后重点看 `refreshAccessibilityServiceBinding rebind txz`、`ensureAccessibilityServiceEnabled`、`ShadowService: onServiceConnected`、`VisualCommandModule: tryUseAccessibilityService: result = 1`、`registerKeywords`。
- 2026-06-23:修复语音 `playDislike`/`playUnfavorites` 把未收藏歌曲反向收藏的问题:
- 用户确认本次不做文件备份。
- 根因:`YX_MusicSDK` 的 `MusicService` 将 `CMD_PLAY_FAVORITES` 和 `CMD_PLAY_UNFAVORITES` 合并到 `onCollectCurrentMusic()`;下游 `SongProvider.collectPlayRecord()` 是 toggle 逻辑,当前歌曲 `isCollect=0` 时执行“取消收藏”会变成收藏。
- `MusicService` 将收藏和取消收藏分支拆开;`CMD_PLAY_UNFAVORITES` 改调 `VoiceControlManager.onUnCollectCurrentMusic()`。
- `VoiceControlManager.onUnCollectCurrentMusic()` 先读取当前播放歌曲,只有已收藏时才复用原 toggle 取消收藏;未收藏时跳过,避免反向收藏。
- 收音机链路同步修正:`VoiceEventHelper.sendMusicUnFavorites(false)` 不再发送 `KEYCODE_MEDIA_RECORD` toggle 按键,改为向 `YX_Radio` 发送 `CMD_PLAY_UNFAVORITES`;`VoiceRadioManager` 收到后仅在当前电台已收藏时调用 `cancelCollection()`。
- 验证:`.\gradlew :YX_Music:assembleDebug :YX_Voice:assembleDebug :YX_Radio:assembleDebug` 编译通过。
## 2026-06-24 playDislike atomic handling
- Continue voice `playDislike` fix without backup.
- Further narrowed `playDislike` to one IPC command from `YX_Voice` to the target media module instead of sending cancel-favorite and next as two independent actions.
- `VoiceEventHelper.sendMusicDislike(...)` now sends `cmd.playDislike` and carries current media type with `typeKey`.
- `VoiceProtocol` keeps video behavior as next video; radio and music now dispatch only `sendMusicDislike(...)`.
- `YX_MusicSDK` handles `cmd.playDislike` in `MusicService`: USB music performs safe un-favorite only when collected, then next track; Bluetooth music does not touch USB favorite state and only sends Bluetooth next when it has focus.
- `YX_Radio` handles `cmd.playDislike` in `VoiceRadioManager`: cancel current station only when already collected, then call `nextAutoSearch()`.
- Verification: `.\gradlew :YX_Music:assembleDebug :YX_Voice:assembleDebug :YX_Radio:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl visible-speak cleanup
- User confirmed no backup before editing.
- Reverted temporary visible-speak validation code from `YX_CarControl`: removed the system page `可见测试按钮`, its click log/toast, maintenance page `可见测试...` content descriptions, and maintenance test click logs.
- Kept formal accessibility/visible-speak enhancements for the voice settings page: dynamic switch `contentDescription`, voice settings control descriptions, and `CustomTriStateSwitch` accessibility node state.
- Verified no remaining `可见测试`, `visible speak test`, `visible speak simple`, `btn_visible_speak_simple_test`, or `maintenance_scroll_view` test markers under `YX_CarControl/src/main`.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Follow-up: removed the shared `CustomTriStateSwitch` accessibility-node change to keep this submission scoped to the voice settings page only; remaining `YX_CarControl` diff is limited to `VoiceFragment.java`, `fragment_voice.xml`, and `strings.xml`.
- Verification after removal: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Follow-up: kept multi-keyword visible-speak text with `#`, but changed all added `fragment_voice.xml` `contentDescription` values from hard-coded text to `@string/...` references for maintainability.
- Verification after XML string-reference change: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Follow-up: added matching `values-en/strings.xml` translations for all new `voice_speak_*` resources while preserving `#` multi-keyword format.
- Verification after English resource completion: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl EnergyFragment visible-speak
- User confirmed no backup before this round of edits.
- Added native visible-speak accessibility text for the Energy page without using `shadow.register`, following TXZ native page scanning via `contentDescription`.
- Scope:
- `EnergyFragment.java`: dynamically updates charge/discharge switch `contentDescription` according to current checked state, so closed state exposes open keywords and opened state exposes close keywords.
- `fragment_energy.xml`: added `contentDescription` for charge allow, discharge allow, reset trip after last charge, more trips, and reset statistics controls.
- `dialog_trip_record.xml`: added `contentDescription` for subtotal trip A/B reset buttons.
- `values/strings.xml` and `values-en/strings.xml`: added matching `energy_speak_*` multi-keyword strings using `#`.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Follow-up: moved `energy_speak_*` resources into the voice settings resource section and labeled them with `可见即可说 - 能源页面`; also labeled existing visible-speak groups for System and Voice pages in both Chinese and English string files.
- Verification after resource regrouping: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl SystemFragment visible-speak
- Followed reference commit `ef20ec3ca823aece8d1beb8e45442cdab7ba420f` and reused the native page scanning approach.
- Added `contentDescription` visible-speak keywords to System page clickable containers: factory reset, USB upgrade, system update, time setting, language setting, unit setting, disclaimer, privacy policy, and permission authorization.
- Added matching `system_speak_*` string resources in `values/strings.xml` and `values-en/strings.xml`, with multiple keywords separated by `#`.
- No Java behavior change was needed in `SystemFragment.java`; existing click listeners already dispatch the actions after TXZ triggers the scanned views.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Follow-up: moved the new `system_speak_*` resources into the voice settings resource section in both Chinese and English string files, because these entries are visible-speak keywords rather than ordinary System page labels.
- Verification after resource regrouping: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_Voice background TTS toast
- User confirmed no backup before editing.
- Requirement: both `playTtsOnUi()` and `playTtsInBackground()` are in use, but foreground TTS remains only a reserved API; actual playback must continue through `syncBackgroundTtsText`.
- Added `VoiceToastHelper` in `YX_Voice` using system `Toast` with custom view, based on the existing `YX_CarControl` designed Toast style instead of using a Dialog-style helper.
- Added independent `YX_Voice` Toast resources: `voice_toast_view.xml`, `voice_toast_bg.xml`, day/night colors, and Toast dimensions.
- Refactored `VoiceEventHelper` so `playTtsOnUi()` and `playTtsInBackground()` both call `sendBackgroundTtsWithToast(...)`; this shows the designed Toast and sends TXZ `syncBackgroundTtsText`.
- Kept foreground TTS command disabled; `playTtsOnUi()` comment now explicitly states foreground TTS is not supported and the reserved entry still uses background TTS.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl SoundFragment visible-speak
- User requested native visible-speak support for the Sound page and previously confirmed no backup before editing.
- Added Sound page keywords through native TXZ accessibility scanning via `contentDescription`; this page does not use `shadow.register`.
- Scope:
- `fragment_sound.xml`: added visible-speak keywords for sound field options, media/phone/navigation/voice volume bars, mute switch, and key tone switch.
- `layout_eq.xml`: added visible-speak keywords for default, classical, pop, jazz, electronic, rock, and custom EQ options.
- `SoundFragment.java`: dynamically refreshes mute and key tone switch `contentDescription` by current checked state, so disabled state exposes open keywords and enabled state exposes close keywords.
- `values/strings.xml` and `values-en/strings.xml`: added `sound_speak_*` resources under `可见即可说 - 声音页面`.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Known validation boundary: the volume bars are custom `ItemCustomSeekBarView` controls; keywords are exposed for scanning, but exact voice-side adjustment behavior depends on TXZ accessibility handling of custom drag/click views.
- Follow-up: per user decision, removed visible-speak keywords from the four custom volume bars for now; Sound page visible-speak scope remains sound field, EQ options, mute switch, and key tone switch.
## 2026-06-24 YX_CarControl ADAS hands-off TTS debounce
- User confirmed no backup before editing.
- Symptom from `2026_0624_1540_03_018.log`: `请手握方向盘` was heard as repeated leading fragments like `请 请 请 请 请 手握方向盘`.
- Root cause: `sendAdasLdsHandsOffWarnStChange(2)` was triggered repeatedly while the warning stayed active, so `sendForegroundTtsPlay("请手握方向盘")` and the downstream background TTS command were sent repeatedly; each new request interrupted the previous playback.
- Fix scope: `VoiceSettingManager.sendAdasLdsHandsOffWarnStChange(int sts)` now only sends TTS when the ADAS hands-off warning enters `0x02` from a non-warning state, with a 5-second debounce against rapid signal chatter.
- Expected validation: repeated `sendAdasLdsHandsOffWarnStChange sts = 2` logs may still appear, but repeated TTS requests should be replaced by `sendAdasLdsHandsOffWarnStChange skip duplicate tts`; `sendForegroundTtsPlay tts = 请手握方向盘`, `playTtsOnUi`, and TXZ `syncBackgroundTtsText` should no longer repeat at millisecond level.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Follow-up after reboot log `2026_0624_1612_11_547.log`: ADAS warning status still bounced between `1` and `2`; the previous 5-second debounce allowed another TTS after the time window expired, producing 15 sends and 15 duplicate skips.
- Updated strategy: replace fixed 5-second replay debounce with a warning-cycle state gate. The first `sts=2` still plays immediately, but the warning is not considered cleared until `sts != 2` remains stable for 2 seconds; only after that can a later `sts=2` play again.
- Verification after stable-clear update: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl ShowFragment visible-speak
- User requested visible-speak support for the Display page and previously confirmed no backup before editing.
- Added native TXZ accessibility scanning keywords via `contentDescription`; this page does not use `shadow.register`.
- Scope:
- `fragment_show.xml`: added visible-speak keywords for screen off, theme mode, hidden display mode controls, wallpaper options, projection options, and video safety mode.
- `ShowFragment.java`: dynamically refreshes Bluetooth phone projection, media audio projection, and video safety mode `contentDescription` according to current selected/checked state, so disabled state exposes open keywords and enabled state exposes close keywords.
- `values/strings.xml` and `values-en/strings.xml`: added `show_speak_*` resources under `可见即可说 - 显示页面`.
- Brightness bars are custom `ItemCustomSeekBarView` controls and were intentionally not added per the latest user decision to skip sliders for now.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-07-01 YX_CarControl SoundFragment visible-speak short keywords
- Log `C:\Users\Administrator\Desktop\徐工日志\2026_07_01_20_22_01\yx_log\2026_0701_2010_07_922.log` showed TXZ accessibility was connected and Sound page keywords were scanned into `VisualCommandModule offline`, but short utterances such as `古典`, `爵士`, and `主驾` returned `TxzVisualCommand matchedEntry = null`.
- Root cause: Sound page `contentDescription` entries only exposed longer phrases such as `古典音效` and `主驾音场`; TXZ visible-command matching did not treat short utterances as substring matches.
- Fix: prepend standalone short keywords to Sound page visible-speak string resources: `全车`, `主驾`, `副驾`, `默认`, `古典`, `流行`, `爵士`, `打击`, and `摇滚`; mirrored the same style in `values-en`.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Follow-up from log `C:\Users\Administrator\Desktop\徐工日志\9_45_17\yx_log\2026_0622_1451_07_056.log`: utterance `自定义` still returned `TxzVisualCommand matchedEntry = null` because the scanned Sound page list only contained `自定义音效#...` and `自定义音场#...`.
- Final decision: do not add a bare `自定义` keyword because the Sound page has two visible `自定义` controls. Keep explicit `自定义音效` for EQ custom and `自定义音场` for sound-field custom to avoid duplicate keyword ambiguity.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl ConnectFragment visible-speak
- User requested visible-speak support for the Connect page and previously confirmed no backup before editing.
- Added native TXZ accessibility scanning keywords via `contentDescription`; this page does not use `shadow.register`.
- Scope:
- `fragment_connect.xml`: added visible-speak keywords for Bluetooth settings entry, WLAN settings entry, Bluetooth switch, and WLAN switch.
- `ConnectFragment.java`: dynamically refreshes Bluetooth and WLAN switch `contentDescription` according to current checked state, so disabled state exposes open keywords and enabled state exposes close keywords.
- `values/strings.xml` and `values-en/strings.xml`: added `connect_speak_*` resources under `可见即可说 - 连接页面`.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl SmartDriveFragment visible-speak
- User requested visible-speak support for the Smart Drive page and previously confirmed no backup before editing.
- Added native TXZ accessibility scanning keywords via `contentDescription`; this page does not use `shadow.register`.
- Scope:
- `fragment_smart_drive.xml`: added visible-speak keywords for ACC time gap, LDW switch/sensitivity, AEBS switch, FCW sensitivity, blind spot monitor, driver monitor, and traffic sign recognition.
- `SmartDriveFragment.java`: dynamically refreshes LDW, AEBS, blind spot monitor, driver monitor, and traffic sign recognition switch `contentDescription` according to current checked state, so disabled state exposes open keywords and enabled state exposes close keywords.
- `values/strings.xml` and `values-en/strings.xml`: added `smart_speak_*` resources under visible-speak resource groups.
- Hidden BSIS/MOIS switches were intentionally not added because this fragment has no matching business listener for them in the current implementation.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl LightControlFragment visible-speak
- User requested visible-speak support for the Light page and previously confirmed no backup before editing.
- Added native TXZ accessibility scanning keywords via `contentDescription`; this page does not use `shadow.register`.
- Scope:
- `fragment_light_control.xml`: added visible-speak keywords for ambient light, breathing mode, auto dome light, safety light radio options, smart welcome light, smart high/low beam, and logo light.
- `LightControlFragment.java`: dynamically refreshes ambient light, breathing mode, logo light, auto dome light, smart welcome light, and smart high/low beam switch `contentDescription` according to current checked state, so disabled state exposes open keywords and enabled state exposes close keywords.
- `values/strings.xml` and `values-en/strings.xml`: added `light_speak_*` resources under visible-speak resource groups.
- Custom color picker, ambient light brightness, and key backlight selector were intentionally not added because they are touch-coordinate based custom selector/slider controls.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl CarControlFragment visible-speak
- User requested visible-speak support for the Car Control page and previously confirmed no backup before editing.
- Added native TXZ accessibility scanning keywords via `contentDescription`; this page does not use `shadow.register`.
- Scope:
- `fragment_car_control.xml`: added visible-speak keywords for adaptive suspension modes, ESC OFF, climb mode, hill start assist, downhill assist, brake energy recovery, smart door lock, smart roof, rain sensor sensitivity, and wireless charging.
- `CarControlFragment.java`: dynamically refreshes switch `contentDescription` according to current checked state, so disabled state exposes open keywords and enabled state exposes close keywords.
- `values/strings.xml` and `values-en/strings.xml`: added `car_speak_*` resources under visible-speak resource groups.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl MaintenanceFragment visible-speak
- User requested visible-speak support for the Maintenance page and previously confirmed no backup before editing.
- Added native TXZ accessibility scanning keywords via `contentDescription`; this page does not use `shadow.register`.
- Scope:
- `fragment_maintenance.xml`: added visible-speak keywords for maintenance mileage off, 5000 km, 10000 km, 30000 km, and maintenance reset.
- `values/strings.xml` and `values-en/strings.xml`: added `maintenance_speak_*` resources under visible-speak resource groups.
- No Java behavior change was needed; existing click listeners still handle mileage selection and reset confirmation.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-24 YX_CarControl PermissionAuthorizationFragment visible-speak
- User requested visible-speak support for the Permission Authorization secondary page and previously confirmed no backup before editing.
- Added native TXZ accessibility scanning keywords via `contentDescription`; this page does not use `shadow.register`.
- Scope:
- `fragment_permission_authorization.xml`: added visible-speak keywords for back, authorization duration off/180 days/360 days, and all visible permission switches.
- `PermissionAuthorizationFragment.java`: dynamically refreshes permission switch `contentDescription` according to current checked state, so disabled/off state exposes open keywords and enabled/on state exposes close keywords.
- `dialog_permission_confirm.xml`: added visible-speak keywords for confirm close and cancel, covering the permission close confirmation flow.
- `values/strings.xml` and `values-en/strings.xml`: added `permission_speak_*` resources under visible-speak resource groups.
- Existing authorization duration, Settings.System persistence, permission confirmation, and switch enable/disable business logic were kept unchanged.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
## 2026-06-26 YX_Voice drowsy TTS reminder dialog
- User confirmed no backup for this TTS reminder work.
- Requirement update: fatigue reminder content must be read directly from `VoiceNewConstant.DROWSY_DRIVING`.
- Kept the previous design decision that foreground TTS is only a reserved API; both `playTtsOnUi()` and `playTtsInBackground()` still send real audio through TXZ `syncBackgroundTtsText`.
- Added `VoiceDrowsyReminderHelper` in `YX_Voice`:
- Only triggers for `VoiceNewConstant.DROWSY_DRIVING`, with punctuation/blank normalization so half-width `!` input can still match the full-width constant.
- Shows a service-safe overlay reminder dialog after the existing toast path.
- Dialog title: `消息提醒`; content: `VoiceNewConstant.DROWSY_DRIVING`; buttons: `稍后提醒` and `不再提醒`.
- `稍后提醒` only dismisses the current dialog; `不再提醒` persists a local SharedPreferences flag and suppresses future fatigue dialogs without suppressing TTS audio or toast.
- Added `SYSTEM_ALERT_WINDOW` permission and released the overlay from `VoiceService.onDestroy()` to avoid stale windows during service restart.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-06-26 YX_Voice remindLaterOrNever command
- User requested voice command `remindLaterOrNever` for the fatigue reminder dialog buttons `稍后提醒` and `不再提醒`.
- Added TXZ protocol constant `CMD_REMIND_LATER_OR_NEVER = "remindLaterOrNever"` in `YX_Voice`.
- `VoiceProtocol` now consumes `remindLaterOrNever` locally and calls `VoiceDrowsyReminderHelper` directly, because the reminder overlay belongs to the voice process and does not need cross-process dispatch.
- Parameter parsing is tolerant across `type`, `value`, `action`, `cmd`, `text`, `word`, `keywordList`, `name`, and raw JSON fallback:
- Contains `不再` / `never` / `noMore` / `not_remind`: persist `不再提醒` and dismiss.
- Contains `稍后` / `later` / `snooze` / `delay`: dismiss only.
- Touch buttons and voice command now share the same internal action handler.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-06-26 YX_Voice drowsy TTS test broadcast
- Added handling for the already registered `com.voice.test.tts.yb` test broadcast in `YX_Voice` `TestReceiver`.
- If the broadcast has no `tts` extra, it defaults to `VoiceNewConstant.DROWSY_DRIVING`, so a single adb command can trigger the fatigue TTS toast/dialog chain.
- If `--es tts ...` is supplied, that custom text is sent through the same `playTtsOnUi()` path.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-06-26 YX_Voice drowsy dialog day mode
- Continued without backup per user preference.
- Matched the drowsy reminder dialog day/night colors to the `YX_CarControl` system version dialog style: light day background/text/buttons, dark night background/text/buttons, blue pressed state.
- Added blank-area dismiss for the overlay root while the dialog container consumes inner clicks, matching `setCanceledOnTouchOutside(true)` behavior from the reference dialog.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
- Follow-up: moved the drowsy dialog title to `strings.xml`, and moved fixed `remindLaterOrNever` protocol attr values `稍后提醒` / `不再提醒` to `VoiceNewProtocolConst`.
- Verification after constant/resource cleanup: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-06-26 YX_Voice dialog/toast day-night compatibility
- Continued without backup per user preference.
- Requirement: the voice reminder dialog and TTS toast must follow the current day/night theme, referencing the vehicle settings theme behavior.
- Added `VoiceThemeHelper` in `YX_Voice`:
- Reads the actual theme first from `Settings.Global.REQUEST_THEME_MODE`, which is written by the car-control theme manager.
- Falls back to `Settings.System[DataUtils.THEME_MODE_CHANGED]`.
- In auto mode, falls back to `Settings.Global.POST_LIGHT_STATUS`.
- Finally falls back to the current resource `Configuration.UI_MODE_NIGHT_MASK`.
- Updated `VoiceDialogHelper` and `VoiceToastHelper` to inflate their custom views with the themed context, so `values/colors.xml` and `values-night/colors.xml` are selected correctly.
- Existing day/night color resources are kept in `YX_Voice`: light dialog/toast colors for day mode, dark dialog/toast colors for night mode.
## 2026-06-26 YX_Voice dialog theme refresh while showing
- User reported the dialog did not change after switching day/night mode; log `2026_0626_1644_36_540.log` was analyzed.
- Key timeline from the log:
- `16:45:28.982` voice received the smoking reckless driving TTS and scheduled the safety dialog.
- `16:45:30.044` `VoiceDialogHelper: showInternal: drowsy reminder shown`, so the overlay view was already added to `WindowManager`.
- `16:45:34.464` `VoiceEventHelper: themeMode 1` and `syncScreenMode status = darkMode`, meaning the theme switch happened several seconds after the overlay was created.
- Root cause: a custom `WindowManager` overlay does not automatically reinflate or re-resolve `values-night` resources when the system theme changes after it is already visible.
- Fix:
- Added `VoiceDialogHelper.onThemeChanged()` to refresh an already-visible dialog by preserving the current title/content, removing the old overlay view, and adding a new themed view.
- `SettingCommonObserver` now calls the refresh hook on `THEME_MODE_CHANGED` and also listens to `Settings.Global.REQUEST_THEME_MODE`.
- `VoiceThemeHelper` now prefers the current `Settings.System[THEME_MODE_CHANGED]` value for manual light/dark mode; auto mode still uses `REQUEST_THEME_MODE` / `POST_LIGHT_STATUS` fallback.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-06-26 YX_Voice toast theme refresh while showing
- User reported the TTS toast did not change after switching day/night mode; log `2026_0626_1705_01_941.log` was analyzed.
- Key timeline:
- `17:06:15.791` / `17:06:17.489` `VoiceEventHelper: playTtsOnUi` received HVAC CO2 TTS text and showed toast.
- `17:06:18.900` user clicked dark mode switch, then `17:06:18.917` voice synced `lightMode`.
- The screenshot still showed black toast because the custom toast view was created while the theme was still dark, before the theme switch.
- Root cause: custom Toast views also do not re-resolve resources after being shown; cancelling/recreating is required if theme changes during the toast display window.
- Fix:
- `VoiceToastHelper` now keeps the last visible toast text and visibility window.
- On `THEME_MODE_CHANGED` / `REQUEST_THEME_MODE` observer callback, it delays briefly, cancels the old toast, and re-shows the same text with the current themed context.
- Added `VoiceToastHelper: refreshForThemeChangedInternal` log for validation.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-06-27 YX_CarControl fatigue reminder interval UI
- User confirmed no backup and required only adding the new fatigue reminder feature, without changing historical feature logic.
- Added the new Smart Drive "疲劳监测提醒" UI under existing "驾驶人员监测": switch + reminder interval control.
- Interval requirement: 0~60 minutes, default 20 minutes, step 10 minutes; progress area is 702dp wide and 100dp high, so each 10-minute step maps to 117dp.
- Existing progress controls were checked:
- `ItemCustomSeekBarView` is shared by Sound/Light/Display pages and normally displays an icon.
- `CustomLightSelector` is a 1~5 backlight selector with icon and dividers.
- EQ/native SeekBars are not suitable for this horizontal reminder UI.
- To avoid impacting shared historical controls, the temporary shared-control icon-removal approach was dropped; a dedicated no-icon `FatigueReminderIntervalSeekBarView` is used only by the new Smart Drive fatigue reminder interval layout.
- Verification: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Follow-up: the new fatigue reminder switch/interval still uses local keys in `SmartDriveFragment`, but reads/writes through `DataManager` + `DataUtils.STATE_URI` and now registers `ContentObserver` callbacks, matching the existing data-center sync style. `YX_CarControl` compiles against `sdklibs/yx_support_classes.jar`, so adding constants only in `YX_Support` source does not affect this module build unless the support jar is regenerated or the dependency is changed.
- Verification after the data-center observer adjustment: `.\gradlew :YX_CarControl:assembleDebug` completed successfully.
- Update: per user request, moved the fatigue reminder keys to `YX_Support` `DataUtils`: `FATIGUE_REMINDER_SWITCH` and `FATIGUE_REMINDER_INTERVAL_MINUTE`. The interval value is stored as an integer minute value, default 20.
- Data center parity with `FATIGUE_MONITOR_SWITCH`: added `YX_DataCenterService` initialization in `YXContentProvider.mHasState`, update dispatch in `YXContentProvider.update()`, new `MessageEvent` IDs 2097/2098, and `ProviderData` SharedPreferences `putInt/getInt` persistence for the reminder switch and integer interval.
- Build note: both `YX_CarControl` and `YX_DataCenterService` compile against `sdklibs/yx_support_classes.jar`, so `YX_Support:makeJar` or the existing support-jar replacement flow is needed before compiling these consumers with the new `DataUtils` constants.
## 2026-06-28 YX_Voice fatigue reminder interval
- User confirmed `YX_Voice` must associate the SmartDrive fatigue reminder settings:
- `DataUtils.FATIGUE_REMINDER_SWITCH`
- `DataUtils.FATIGUE_REMINDER_INTERVAL_MINUTE`
- `DROWSY_DRIVING`, `DISTRACTED_DRIVING`, `SMOKING_RECKLESS_DRIVING`, and `CALLING_RECKLESS_DRIVING` reminder dialogs are now gated by the reminder switch and throttled by the interval minute value.
- `VoiceDialogHelper` reads the DataCenter switch before showing; when enabled, it stores the last shown time and skips repeated dialogs until the configured interval expires. Interval `0` means no throttling.
- Update: user clarified `不再提醒` and `稍后提醒` must not persist locally and must recover after reboot. `VoiceDialogHelper` now keeps both the last shown time and the current-process "never remind" flag only in static memory; it does not write SharedPreferences or change `FATIGUE_REMINDER_SWITCH` for these two dialog actions.
- Update: user clarified the four dialog reminders are four independent functions. The current-process "never remind" flag and the last shown time are now tracked separately for `DROWSY_DRIVING`, `DISTRACTED_DRIVING`, `SMOKING_RECKLESS_DRIVING`, and `CALLING_RECKLESS_DRIVING`; one type's button action or interval throttle does not suppress the other types.
- `SettingCommonObserver` and `BaseVoiceService` now observe the two fatigue reminder URIs; turning the switch off dismisses a pending/showing reminder overlay.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-06-29 YX_Voice radio syncMediaFocus
- User confirmed no backup before editing.
- Log `2026_0629_1152_07_466.log` showed Radio playback was active and TXZ received `syncFocusPlayStatus=true` plus `syncRadioPlayType=fm`, but no `syncMediaFocus: radio`.
- Root cause: `MediaObserver` only sent media focus on `SOURCE_ID` changes; Radio startup in the log updated `RADIO_INFO` playback state without a reliable `SOURCE_ID` change notification.
- Fix: when `RADIO_INFO` reports `mState == 2` and current `SOURCE_ID` is `YXUtils.SOURCE_RADIO`, `MediaObserver` now sends `notifySyncMediaFocus(VoiceNewProtocolConst.RADIO)` before syncing play status.
- Verification: `.\gradlew :YX_Voice:assembleDebug` completed successfully.
## 2026-07-01 YX_BlueTooth ACC state cleanup
- User confirmed no backup before editing.
- Requirement: check whether Bluetooth still depends on legacy `ACC_STATE`; Xugong currently follows power mode rather than old ACC state.
- Finding: `YX_BlueTooth` `BluetoothService` still registered `ACC_STATE`, but the old disconnect/reconnect logic in that branch had already been commented out and only logged `ACC state`.
- Fix: removed the unused `ACC_STATE_URI` constant, removed the `ACC_STATE` observer registration, and deleted the dead `case ACC_STATE_URI` branch.
- Kept the active `LOW_VOLTAGE_POWER_STATE` logic unchanged because it still performs Bluetooth phone/headset disconnect and phone reconnect behavior; only changed misleading logs/comments from `CAN ACC OFF/ON` to `LOW_VOLTAGE_POWER_STATE OFF/ON`.
- Verification: `.\gradlew :YX_BlueTooth:assembleDebug --console plain` completed successfully.
## 2026-07-02 YX_CarControl sound page visible-speak coverage check
- Rechecked Sound page main visible controls:
- Sound field: all-cabin, driver, passenger, custom sound field.
- Sound effect: default, classical, pop, jazz, electronic, rock, custom sound effect.
- Switches: mute and key tone.
- Sliders are intentionally not added to visible-speak yet, matching the previous decision.
- Fixes after the coverage check:
- The clickable custom-effect triangle in `layout_eq.xml` now uses `sound_speak_eq_user` instead of the literal `Triangle Button`.
- `sound_speak_close_mute` now also includes bare `jingyin` / `Mute`, so the same short utterance can match both on and off states.
- Boundary note: the custom EQ floating window (`layout_eq_user.xml` / `EqUserFloatView`) has its own EQ buttons and seek bars but is a service overlay / secondary UI, not currently treated as part of the Sound main-page visible-speak scope.
- Verification: `.\gradlew :YX_CarControl:assembleDebug --console plain` completed successfully.
## 2026-07-02 YX_CarControl voice page visible-speak short keywords
- Analyzed log `2026_0702_1025_08_858.log` for Voice page visible-speak.
- At `15:08:37`, `VoiceFragment: onStart` and `syncVoiceSettingStatus status = true` were logged.
- TXZ offline list contained Voice page entries such as `打开免唤醒#开启免唤醒`, `免唤醒10秒#设置免唤醒10秒#选择10秒`, `免唤醒15秒#设置免唤醒15秒#选择15秒`, and `免唤醒30秒#设置免唤醒30秒#选择30秒`.
- User utterance `免唤醒` failed because the visible-speak entry only had open/close phrases, not the bare short keyword.
- User utterance `10秒` was recognized by TXZ ASR as `十秒`; the visible-speak entry did not include `十秒`, so `matchedEntry = null`.
- Fix:
- Added bare `语音唤醒` to both voice wakeup switch states.
- Added bare `免唤醒` to both wake-free switch states.
- Added short numeric and Chinese-number forms for duration entries: `10秒#十秒`, `15秒#十五秒`, `30秒#三十秒`.
- Verification: `.\gradlew :YX_CarControl:assembleDebug --console plain` completed successfully.
## 2026-07-02 YX_CarControl sound page visible-speak key tone
- User reported saying bare `按键音` on Sound page did not respond.
- Finding: `SoundFragment` dynamically sets the key-tone switch `contentDescription` based on current state; original entries only exposed `打开按键音#开启按键音` or `关闭按键音#关掉按键音`.
- Root cause inference: TXZ visible-speak matching does not reliably treat `按键音` as a substring match for these longer phrases.
- Fix: added bare `按键音` to both `sound_speak_open_key_tone` and `sound_speak_close_key_tone`; English resources added matching bare `Key tone`.
- Expected verification: on Sound page, say `按键音`; log should show the offline entry containing `按键音#...` and `TxzVisualCommand` matched entry for `按键音`.
## 2026-07-02 YX_CarControl SmartDrive fatigue visible-speak short keywords
- Analyzed log `C:/Users/Administrator/Desktop/徐工日志/2026_07_02_15_25_54/yx_log/2026_0702_1025_08_858.log`.
- Root cause: SmartDrive page offline visible-speak list only exposed state-specific phrases such as `关闭疲劳监测提醒#关掉疲劳监测提醒`; the bare utterance `疲劳监测提醒` produced `matchedEntry = null`.
- Fix: added bare short keywords to both open and close resources for `smart_speak_*_fatigue_monitor` and `smart_speak_*_fatigue_reminder`.
- Verification: `.\gradlew :YX_CarControl:assembleDebug --console plain` completed successfully.
## 2026-07-03 YX_Launcher boot-time Bluetooth music widget play
- User confirmed no backup before editing.
- Requirement: after boot, clicking the Launcher main-page music card play button should play Bluetooth music.
- Root cause:
- `YX_Launcher` main page uses `WidgetsMedia`, whose play/prev/next buttons only sent global media keys.
- On cold boot, Bluetooth music focus/MediaSession may not yet be established, so `KEYCODE_MEDIA_PLAY_PAUSE` can be ignored and does not explicitly trigger Bluetooth AVRCP play.
- `WidgetsMedia.init()` queried current focus package but hard-coded the initial client id as USB music, so `com.yaxon.music` could be treated as USB music even when `SOURCE_ID` was Bluetooth music.
- `SystemUtils.startBtMusic()` sends `is_click_bluetooth_music=true`, but `UsbMainActivity` only handled `VoiceControlManager.PAGE_TYPE`; the legacy Bluetooth-music extra was ignored.
- Fix:
- `WidgetsMedia` now resolves the initial card source from `SOURCE_ID` when focus package is `com.yaxon.music` or null.
- For Bluetooth music source, Launcher card play/prev/next sends explicit broadcasts to `YX_Music` instead of relying only on global media keys.
- Added `BluetoothMusicControlReceiver` in `YX_Music` to call `BtMusicControlManager` directly for play/pause/previous/next.
- Play from stopped/cold state retries for up to 10 attempts at 500 ms intervals, with a request token so a later pause cancels stale retries.
- `UsbMainActivity` now handles `is_click_bluetooth_music=true` in both initial launch and `onNewIntent()` and switches to the Bluetooth music page.
- Verification:
- `.\gradlew :YX_Music:assembleDebug --console plain` completed successfully.
- `.\gradlew :YX_Launcher:assembleDebug --console plain` completed successfully.
## 2026-07-03 YX_Launcher Bluetooth music widget retest log analysis
- User provided retest logs:
- `C:/Users/Administrator/Desktop/徐工日志/2026_07_03_11_12_02/yx_log/2026_0703_1050_00_000/2026_0703_1049_41_742.log`
- `C:/Users/Administrator/Desktop/徐工日志/2026_07_03_11_12_02/yx_log/2026_0703_1050_33_402.log`
- Key evidence:
- At `07-03 11:02:37.547`, `WidgetMedia: KEYCODE_MEDIA_PLAY_PAUSE:` appears, proving the Launcher card play button click reached `WidgetsMedia`.
- No `WidgetMedia: sendBtMusicControl action = ...`, no `BluetoothMusicControlReceiver: onReceive action = ...`, no `BtMusicControlManager: playBtMusic`, and no `BluetoothAvrcpClient: play` appear in the same retest logs.
- Around the click, `NuPlayerDriver` / `MediaPlayer` logs appear, indicating the click path did not execute the explicit Bluetooth music broadcast control path.
- Earlier logs show `WidgetMedia: initMedia: packageName ->null ,clientId: com.yaxon.musicsdk.manager` and `initMedia:onMusicInfo`, so the Launcher widget initially identified the source as USB music.
- Later `WidgetMedia: onBtMusicInfo` logs prove Bluetooth music metadata can still update the card, but metadata display alone does not prove the button control path is using Bluetooth control.
- Current conclusion: this retest log is consistent with either an old Launcher/Music package still running, or `isBtMusicControlSource()` evaluating false when the click happens. The click reached Launcher, but did not reach the new explicit Bluetooth music control receiver.
- Next validation/fix direction:
- On device, confirm the flashed `YX_Launcher.apk` contains the `sendBtMusicControl` code and `YX_Music.apk` contains `BluetoothMusicControlReceiver`.
- Add a click-time diagnostic log for `mCurrentMediaSource` and `Utils.isBtMusicSource()` if another code change is needed.
- If source detection is false at click time, make `WidgetsMedia` prefer the currently displayed `BtMusicInfo` / latest card mode for play/prev/next routing, not only `SOURCE_ID`.
## 2026-07-03 YX_BluetoothSDK Bluetooth music auto-pause after boot
- User asked to re-analyze log `C:/Users/Administrator/Desktop/徐工日志/2026_07_03_11_28_17/yx_log/2026_0703_1127_33_000.log` after reverting `YX_Launcher` `WidgetsMedia` and the `YX_Music` explicit receiver changes.
- Key evidence:
- This log does not contain `WidgetMedia: KEYCODE_MEDIA_PLAY_PAUSE`, so it does not prove the Launcher play button click entered `WidgetsMedia` in this capture.
- At `07-03 11:27:30.608`, Bluetooth A2DP reports `Stream started`.
- At `07-03 11:27:30.618/30.619`, `BluetoothMediaBrowserService` reports `PlaybackState state=3`, meaning Bluetooth music did enter playing state.
- At `07-03 11:27:30.643`, `BtMusicManager: onPlaybackStateChanged isPlaying = true`.
- Immediately after, `BtMusicAudioManager: setA2dpMute`, `informAudioFocusStateNative:0`, `BtMusicManager: onPlaybackStateChanged pauseBtMusic>`, `BtMusicManager: pauseBtMusic`, and `BluetoothAvrcpClient: pause` appear.
- Root cause inference from current code:
- `BtMusicManager.onPlaybackStateChanged()` contains protection logic: if Bluetooth music starts while `!isForeground()` and `!mBtMusicAudioManager.isFocus()`, it mutes A2DP and calls `pauseBtMusic()`.
- During Launcher main-page startup, Bluetooth music fragment is not foreground and Bluetooth music audio focus is not held, so when phone-side/A2DP playback starts, the SDK treats it as unauthorized background playback and actively pauses it.
- Current conclusion: this log points to `YX_BluetoothSDK` foreground/focus guard actively stopping Bluetooth music, not to AVRCP/A2DP inability to play. The system can play briefly, then the SDK pauses it because the Bluetooth music page is not foreground and focus is false.
- Next fix direction: for Launcher card play-after-boot, ensure the Bluetooth music control path first marks/request-focus for Bluetooth music or create a controlled exception in `BtMusicManager` for a user-initiated Launcher card play request; avoid relying only on raw global media key when the Bluetooth music page is not foreground.
## 2026-07-03 YX_Launcher Bluetooth music widget latest 11:33 log analysis
- User provided latest log:
- `C:/Users/Administrator/Desktop/徐工日志/2026_07_03_11_34_00/yx_log/2026_0703_1133_34_699.log`
- Current code state after revert:
- `YX_Launcher/src/main/java/com/yaxon/launcher/view/WidgetsMedia.java` play/previous/next buttons only call `YXUtils.sendKey(...)`.
- No `sendBtMusicControl`, no explicit `YX_Music` receiver, and no current-source based Bluetooth control path exists in the reverted code.
- Key evidence:
- At `07-03 11:33:27.386`, `BtMusicManager` receives Bluetooth metadata: artist `王菲`, title `如愿 (电影《我和我的父辈》主题推广曲)`.
- At `07-03 11:33:27.429`, Bluetooth playback state is stopped: `BtMusicManager: onPlaybackStateChanged isPlaying = false`.
- At `07-03 11:33:27.735`, Launcher receives `WidgetMedia: onBtMusicInfo`, so the card can display Bluetooth music information.
- At `07-03 11:33:42.946` and `07-03 11:33:48.389`, the Launcher card play button click reaches `WidgetsMedia`: `KEYCODE_MEDIA_PLAY_PAUSE`.
- At `07-03 11:33:49.325`, previous button click reaches `WidgetsMedia`: `KEYCODE_MEDIA_PREVIOUS`.
- Immediately after these clicks, logs only show `NuPlayerDriver` / `MediaPlayer` / `bus7_system_sound_out` short system-audio playback and stop/reset.
- The log contains no `BtMusicControlManager: playBtMusic`, no `BtMusicManager: playBtMusic`, no `BluetoothAvrcpClient: play`, no `bluetooth music requestAudioFocus`, and no `setA2dpUnMute` after the Launcher button clicks.
- Current conclusion:
- This log proves the Launcher button click is received, but the click is not converted into a Bluetooth music play command.
- Metadata display is working through `BtInfoPushManager -> WidgetMedia`, but control still depends on global media keys.
- In the boot/main-Launcher scene, Bluetooth music is not foreground and does not hold focus, so the Bluetooth SDK media session is not ready to receive/respond to `KEYCODE_MEDIA_PLAY_PAUSE`.
- Combined with the 11:27 log, the failure is a two-part issue: Launcher sends only global media keys, and `BtMusicManager` blocks or pauses Bluetooth playback when it starts without foreground/focus.
- Next fix direction:
- Do not rely on raw global media keys for Launcher Bluetooth card controls.
- Add a controlled Bluetooth-music play entry that first requests/marks Bluetooth music focus, then calls `playBtMusic()`, or explicitly route Launcher card play/previous/next to a Bluetooth control API when the card is showing Bluetooth music.
- Keep the `BtMusicManager` background-play guard intact unless there is a verified user-initiated Launcher play flag/focus path.
## 2026-07-03 YX_BluetoothSDK media-key fallback for Launcher BT widget
- User clarified: do not modify `YX_Launcher`.
- Fix scope:
- `YX_Launcher` remains unchanged; `WidgetsMedia` still sends `KEYCODE_MEDIA_PLAY_PAUSE` / previous / next.
- Change is limited to `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/audio/BtMusicAudioManager.java`.
- Fix:
- `BtMusicAudioManager` now registers its Bluetooth music `MediaSession` during initialization instead of only after Bluetooth music obtains audio focus.
- The session pre-handles media keys and only takes over when Bluetooth music already has focus, current `SOURCE_ID` is `SOURCE_BT_MUSIC`, or boot-time `SOURCE_ID` is `SOURCE_NULL` while valid `BT_MUSIC_INFO` exists.
- When it takes over a play/previous/next key without focus, it requests Bluetooth music audio focus first, then calls `playBtMusic()` / `preBtMusic()` / `nextBtMusic()`.
- `releaseAudioFocus()` no longer releases the MediaSession; it downgrades the session after focus loss so boot-time Launcher media keys can still reach Bluetooth music later.
- Verification:
- `.\gradlew :YX_BluetoothSDK:assembleDebug --console plain` completed successfully.
- Confirmed no diff in `YX_Launcher/src/main/java/com/yaxon/launcher/view/WidgetsMedia.java`.
- Retest log expectation:
- After clicking Launcher card play, logs should show `BtMusicAudioManager: onMediaButtonEvent pre-handle`, `bluetooth music requestAudioFocus`, `setA2dpUnMute`, `BtMusicManager: playBtMusic`, and `BluetoothAvrcpClient: play`.
- The previous failure pattern should disappear: click should no longer only show `NuPlayerDriver` / `MediaPlayer` short system sound with no Bluetooth play command.
## 2026-07-03 YX_BluetoothSDK first-click media-key routing follow-up
- User provided retest log:
- `C:/Users/Administrator/Desktop/徐工日志/2026_07_03_13_50_05/yx_log/2026_0703_1348_33_113.log`
- Key evidence:
- At `07-03 13:48:28.358`, Launcher already received Bluetooth music info: `WidgetMedia: onBtMusicInfo ... mPlaySts = 0`.
- First click around `07-03 13:48:51.498` only shows `McuService: WindowManagerGlobal = ACTION_DOWN` and system click-audio logs. There is no `MediaSessionService: Sending KeyEvent ... KEYCODE_MEDIA_PLAY_PAUSE`, no `BtMusicAudioManager: onMediaButtonEvent`, and no `BtMusicManager: playBtMusic`.
- After the first click, Android reports `Media button session is changed to com.yaxon.music/Music`.
- Second click around `07-03 13:48:54.527` is routed correctly: `MediaSessionService: Sending KeyEvent ... KEYCODE_MEDIA_PLAY_PAUSE ... to com.yaxon.music/Music`, then `BtMusicAudioManager: onMediaButtonEvent`, `bluetooth music requestAudioFocus: result = 1`, `setA2dpUnMute`, `BtMusicManager: playBtMusic`, and `BluetoothAvrcpClient: play`.
- Updated conclusion:
- The remaining symptom is not AVRCP play failure. The second click proves the Bluetooth SDK play path works.
- The first click fails because the Bluetooth music `MediaSession` becomes the active media-button session only after the first click/system playback activity, so the first media key is not delivered to Bluetooth music.
- Fix update:
- Keep `YX_Launcher` and `YX_Music` unchanged.
- In `BtMusicAudioManager`, allow `FLAG_EXCLUSIVE_GLOBAL_PRIORITY` when Bluetooth music is not focused but is a safe inactive media-button candidate: A2DP connected and either `SOURCE_ID == SOURCE_BT_MUSIC`, or `SOURCE_ID == SOURCE_NULL` with valid Bluetooth music info.
- In `BtMusicManager`, refresh the Bluetooth music MediaSession after A2DP connect, playback-state updates, and metadata updates so the session is ready before the first Launcher card click.
- Verification:
- `.\gradlew :YX_BluetoothSDK:assembleDebug --console plain` completed successfully.
- Confirmed no diff in `YX_Launcher/src/main/java/com/yaxon/launcher/view/WidgetsMedia.java`.
- Confirmed no diff under `YX_Music`.
- Next retest expectation:
- The first Launcher card play click should immediately show `MediaSessionService: Sending KeyEvent ... KEYCODE_MEDIA_PLAY_PAUSE ... to com.yaxon.music/Music` followed by `BtMusicAudioManager: onMediaButtonEvent pre-handle`, `bluetooth music requestAudioFocus`, `setA2dpUnMute`, `BtMusicManager: playBtMusic`, and `BluetoothAvrcpClient: play`.
## 2026-07-03 YX_BluetoothSDK 14:54 log coverage check
- User provided log:
- `C:/Users/Administrator/Desktop/徐工日志/2026_07_03_14_54_53/yx_log/2026_0703_1454_33_435.log`
- Coverage result:
- The log has only about 5720 lines and mainly covers early boot around `01-01 08:00:01` to `01-01 08:00:10` plus Android startup timestamps around `06-30 11:09:15` to `06-30 11:09:16`.
- It does not contain `WidgetMedia`, `KEYCODE_MEDIA_PLAY_PAUSE`, `BtMusicAudioManager`, `BtMusicManager`, `BluetoothAvrcpClient`, `MediaSessionService: Sending KeyEvent`, or `07-03 14:54` runtime events.
- The same directory only contains an empty zipped log entry `2026_0703_1454_05_407.log`.
- Conclusion:
- This capture does not cover the Launcher music-card click or Bluetooth music playback path, so it cannot validate whether the first-click fix succeeded or failed.
- Next log requirement:
- Need a log segment that includes the actual click time and contains at least Launcher `WidgetMedia` or system `MediaSessionService` media-key lines.
## 2026-07-03 YX_BluetoothSDK 15:02 retest validates first-click fix
- User provided log:
- `C:/Users/Administrator/Desktop/徐工日志/2026_07_03_15_03_26/yx_log/2026_0703_1502_33_334.log`
- Key evidence:
- At `07-03 15:02:28.813`, Bluetooth metadata arrived: `BtMusicManager: artist = 伍佰 title = 世界第一等(Live)`.
- At `07-03 15:02:28.878`, before any Launcher click, Android changed the global priority session to `com.yaxon.music/Music`.
- At `07-03 15:02:29.311`, Launcher received Bluetooth info and displayed the card: `WidgetMedia: onBtMusicInfo ... mPlaySts = 0`.
- At `07-03 15:02:49.194`, the first Launcher card play click reached `WidgetMedia: KEYCODE_MEDIA_PLAY_PAUSE`.
- The same first click was routed immediately to Bluetooth music: `MediaSessionService: Sending KeyEvent ... KEYCODE_MEDIA_PLAY_PAUSE ... to com.yaxon.music/Music`.
- `BtMusicAudioManager` received the media button while `isFocus=false`, requested focus, unmuted A2DP, then called `BtMusicManager: playBtMusic` and `BluetoothAvrcpClient: play`.
- At `07-03 15:02:49.633` and continuously afterward, `BtMusicManager: onPlaybackStateChanged isPlaying = true` appears, with no subsequent `pauseBtMusic` or `BluetoothAvrcpClient: pause`.
- Conclusion:
- The SDK fix is effective for this retest. First Launcher card click now starts Bluetooth music directly; the previous "first click only arms the session, second click plays" pattern is gone.
## 2026-07-03 YX_BluetoothSDK 15:04 retest also validates first-click fix
- User provided log:
- `C:/Users/Administrator/Desktop/徐工日志/2026_07_03_15_05_45/yx_log/2026_0703_1504_32_996.log`
- Key evidence:
- At `07-03 15:04:27.802`, Bluetooth metadata arrived: `BtMusicManager: artist = 任贤齐 title = 伤心太平洋 (电视剧《神雕侠侣》片尾曲)`.
- At `07-03 15:04:27.835`, before the click, global priority session changed to `com.yaxon.music/Music`.
- At `07-03 15:04:28.211`, Launcher card displayed Bluetooth music info with `mPlaySts = 0`.
- At `07-03 15:04:50.143`, first Launcher play click logged `WidgetMedia: KEYCODE_MEDIA_PLAY_PAUSE`.
- The first click was immediately routed to Bluetooth music: `MediaSessionService: Sending KeyEvent ... KEYCODE_MEDIA_PLAY_PAUSE ... to com.yaxon.music/Music`.
- `BtMusicAudioManager` received both down/up events while `isFocus=false`, requested focus, unmuted A2DP, called `BtMusicManager: playBtMusic`, and `BluetoothAvrcpClient: play`.
- At `07-03 15:04:50.599` and afterward, playback state remained `isPlaying = true`, with no `pauseBtMusic` or AVRCP pause afterward.
- Conclusion:
- This second retest confirms the fix is stable for repeated boot/Launcher-card first-click scenarios.
## 2026-07-03 YX_BluetoothSDK BT music restore after power off
- New requirement: if Bluetooth music is playing and power-off interrupts it, next power-on should restore Bluetooth music playback.
- Scope remains limited to `YX_BluetoothSDK`; `YX_Launcher` and `YX_Music` are not modified.
- Fix:
- Added SDK private persisted flags `bt_music_had_focus` and `bt_music_was_playing`.
- `BtMusicAudioManager` persists Bluetooth focus ownership after successful focus request.
- `BtMusicManager` persists Bluetooth playback state only when Bluetooth music is the explicit `SOURCE_ID` or has focus.
- Added `ACC_STATE` and `POWER_MODE` observers. `POWER_MODE=0/3` snapshots power-off state; `POWER_MODE=1` and `ACC_STATE=ON` trigger restore checks.
- Restore requires all conditions: persisted had-focus, persisted was-playing, explicit `SOURCE_ID == SOURCE_BT_MUSIC`, ACC/IGN on, A2DP connected, and currently not playing.
- Clear restore state only during normal ACC-on source/focus loss paths, not during `POWER_MODE=0/3/4` power-down phases.
- Risk control:
- Auto-restore does not use the Launcher default Bluetooth-source fallback. Missing `SOURCE_ID` uses `SOURCE_NULL` and will not auto-play.
- USB music and radio are protected because restore requires explicit Bluetooth source.
- Manual pause clears `bt_music_was_playing` while powered on and focused.
- Verification:
- `.\gradlew :YX_BluetoothSDK:assembleDebug --console plain` completed successfully.
- Confirmed `git diff -- YX_Launcher YX_Music` is empty.
## 2026-07-07 YX_BluetoothSDK pause Bluetooth music during TXZ TTS
- User confirmed no backup before editing.
- Log analyzed:
- `C:/Users/Administrator/Desktop/徐工日志/2026_07_07_19_07_57/yx_log/2026_0707_1849_48_792.log`
- Finding:
- TXZ TTS requests `Synthesis` audio focus with `focusGain=2`.
- Bluetooth music receives `AUDIOFOCUS_LOSS_TRANSIENT`, but previous `BtMusicAudioManager` handling only called `setA2dpMute()`, so the phone-side Bluetooth playback could continue progressing.
- Radio and USB music handle transient focus loss as a real paused playback state, which explains the different behavior.
- Fix:
- In `BtMusicAudioManager`, `AUDIOFOCUS_LOSS_TRANSIENT` now records whether Bluetooth music was playing, calls `setA2dpMute()`, and calls `pauseBtMusic()` when it was playing.
- Existing `AUDIOFOCUS_GAIN` recovery logic remains responsible for auto-resuming only when `isPlayingLossFocus` is true and the user did not pause manually.
- Verification:
- `.\gradlew :YX_BluetoothSDK:assembleDebug` completed successfully.