Files
Obsidian/项目文档/徐工/项目md文件/agent.md
T
2026-11-05 14:35:04 +08:00

826 lines
90 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.