first commit

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

No files matched your search

@@ -0,0 +1,3 @@
This directory contains plugin interfaces that launcher listens for and plugins implement. In other words, these are the hooks that specify what plugins launcher currently supports.
Details about how to create a new plugin interface, or to use existing interfaces to write a plugin can be found at go/gnl/plugins.
@@ -0,0 +1,419 @@
# X9HP 开机时间优化分析报告
**日期**: 2026-06-04
**平台**: Semidrive X9HP (Android 10 + FreeRTOS SafetyOS on R5)
**对比仓库**: 徐工 (x9hp_xcmg) vs A6
---
## 一、概述
徐工仓库 (`/users/lintenglong/x9hp/androd10_x9hp_ptg4.3p3`) 的开机时间比 A6 仓库 (`/users/lintenglong/x9hp/androd10_x9hp_ptg4.3`) 慢约 **3.5 秒**。
经过两板 dmesg + logcat 逐段对比,定位到根因为**徐工定制的 Launcher (`com.yaxon.launcher`) 第一帧绘制过慢**。
---
## 二、原始数据
以下为两个板子上直接抓取的全部原始日志(按采集命令组织)。
### 2.1 dmesg 全日志
#### A6 dmesg
```console
console:/ $ dmesg
[ 2.113762] init: init first stage started!
[ 2.543083] init: init second stage started!
[ 2.840928] ueventd: Coldboot took 0.132 seconds
[ 3.266118] start to do gpu hw reset !
[ 3.282734] [drm] Initialized pvr ... on minor 1
[ 3.319165] PVR_K: RGX Firmware image loaded
[ 3.819167] e2fsck: Pass 1: Checking inodes, blocks, and sizes
[ 4.024370] mkswap: Swapspace size: 2369596k
[ 4.154427] apexd: Marking APEXd as starting
[ 7.498030] === Current system mem: ...
[ 7.540411] bcm89883_config_init: start...
[ 7.863817] [A6]=== CPU_info: ...
[ 8.039518] ---------- Current system time: 08:00:09 CST boot_completed checking ----------
[ 9.708556] sdcardfs version 2.0
[ 14.191638] [boot_completed] ANDROID_BOOT_STATUS set: id=16, val=0x10000
```
#### 徐工 dmesg
```console
console:/ $ dmesg
[ 2.269841] init: init first stage started!
[ 2.697998] init: init second stage started!
[ 3.025057] ueventd: Coldboot took 0.194 seconds
[ 3.457942] start to do gpu hw reset !
[ 3.475247] [drm] Initialized pvr ... on minor 1
[ 3.797422] PVR_K: RGX Firmware image loaded
[ 4.010759] e2fsck: Pass 1: Checking inodes, blocks, and sizes
[ 4.148274] Adding 2579104k swap ...
[ 5.136792] update_verifier: Booting slot 0 ...
[ 7.548430] ---------- Current system time: 08:00:08 CST boot_completed checking ----------
[ 9.039796] sdcardfs version 2.0
[ 17.333482] init: Received control message 'stop' for 'bootanim' from pid: 3300
[ 17.521355] [boot_completed] ANDROID_BOOT_STATUS set: id=16, val=0x10000
[ 18.350758] ---------- Current system time: 10:24:17 CST boot_completed done ----------
```
### 2.2 boot_progress 全日志
#### A6 boot_progress
```console
console:/ $ logcat -b events -d | grep boot
01-01 08:00:06.641 2925 2925 I boot_progress_start: 5202
01-01 08:00:07.142 2925 2925 I boot_progress_preload_start: 5703
01-01 08:00:08.328 2925 2925 I boot_progress_preload_end: 6889
01-01 08:00:08.514 3276 3276 I boot_progress_system_run: 7075
01-01 08:00:08.779 3276 3276 I boot_progress_pms_start: 7339
01-01 08:00:09.068 3276 3276 I boot_progress_pms_system_scan_start: 7629
01-01 08:00:09.482 3276 3276 I boot_progress_pms_data_scan_start: 8043
01-01 08:00:09.485 3276 3276 I boot_progress_pms_scan_end: 8046
01-01 08:00:09.703 3276 3276 I boot_progress_pms_ready: 8264
05-25 21:49:45.099 3276 3276 I boot_progress_ams_ready: 9548
05-25 21:49:49.547 2753 2870 I sf_stop_bootanim: 13996
```
#### 徐工 boot_progress
```console
console:/ $ logcat -b events -d | grep boot
01-01 08:00:06.677 2913 2913 I boot_progress_start: 5238
01-01 08:00:07.183 2913 2913 I boot_progress_preload_start: 5744
01-01 08:00:08.196 2913 2913 I boot_progress_preload_end: 6757
01-01 08:00:08.391 3296 3296 I boot_progress_system_run: 6952
01-01 08:00:08.667 3296 3296 I boot_progress_pms_start: 7228
01-01 08:00:08.862 3296 3296 I boot_progress_pms_system_scan_start: 7423
01-01 08:00:09.158 3296 3296 I boot_progress_pms_data_scan_start: 7719
01-01 08:00:09.161 3296 3296 I boot_progress_pms_scan_end: 7722
01-01 08:00:09.354 3296 3296 I boot_progress_pms_ready: 7915
05-30 10:05:07.567 3296 3296 I boot_progress_ams_ready: 8645
05-30 10:05:10.701 3296 3322 I boot_progress_enable_screen: 11779
05-30 10:05:15.944 2734 3343 I sf_stop_bootanim: 17022
05-30 10:05:16.042 2736 4831 I sf_stop_bootanim: 17264
05-30 10:05:16.047 3299 3326 I wm_boot_animation_done: 17268
```
### 2.3 Launcher 启动日志
#### A6 Launcher
未抓取到特定日志(SDDemo 启动快)。
#### 徐工 Launcher
```console
console:/ $ logcat -d | grep "com.yaxon.launcher"
01-01 08:00:09.034 3299 3357 E PackageParser: wangsl find an home app,app is ApplicationInfo{... com.yaxon.launcher}
05-30 10:05:07.916 3299 3331 I ActivityManager: Start proc 3536:com.yaxon.launcher/1000
05-30 10:05:07.980 3299 3299 I ActivityTaskManager: START u0 {act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000100 cmp=com.yaxon.launcher/.main.LauncherActivity}
05-30 10:05:08.409 3536 3536 D IocContainer: inject key:...LauncherApplication
05-30 10:05:08.413 3536 3536 D UserCenterControllerImp: send broadcast
05-30 10:05:08.624 3589 3589 I CAR.AM : Updating top task to: LauncherActivity
05-30 10:05:08.849 3536 3536 D IocContainer: inject key:...SoundSourceViewModel
05-30 10:05:08.897 3536 3536 D IocContainer: inject key:...SoundSourceViewModel
05-30 10:05:08.924 3536 3536 D IocContainer: inject key:...IntelligentDrivingCardViewModel
05-30 10:05:09.037 3536 3536 D IocContainer: inject key:...DisplayShareViewModel
05-30 10:05:09.058 3536 3536 D IocContainer: inject key:...DisplayShareViewModel
05-30 10:05:09.068 3536 3536 D DisplayShareBusyness: resolveDisplayShareViewModel3
05-30 10:05:09.117 3536 3536 D IocContainer: inject key:...IntelligentDrivingCardViewModel
05-30 10:05:09.300 3536 3536 D IocContainer: inject key:...IntelligentDrivingCardViewModel
05-30 10:05:09.862 3536 3536 W ContextImpl: ...LauncherActivity.sendHomeStatusBroadcast ...LauncherActivity.onResume
05-30 10:05:12.540 3892 3892 I ForegroundAppMonitor: Foreground app changed: -> com.yaxon.launcher
05-30 10:05:19.087 3536 3536 D DisplayShareBusyness: resolveDisplayShareViewModel3
05-30 10:05:28.038 3299 3332 I ActivityTaskManager: Displayed com.yaxon.launcher/.main.LauncherActivity: +2s598ms
```
### 2.4 init 服务列表
#### A6 running services
```console
console:/ $ getprop | grep "init.svc" | grep running
[init.svc.adbd]: [running]
[init.svc.apexd]: [running]
...共 75 个服务...
[init.svc.zygote_secondary]: [running]
```
#### 徐工 running services
```console
console:/ $ getprop | grep "init.svc" | grep running
[init.svc.adbd]: [running]
[init.svc.apexd]: [running]
...共 74 个服务...
[init.svc.zygote_secondary]: [running]
# 特有的服务:
[init.svc.check_data_partition]: [running]
[init.svc.logcollector]: [running]
[init.svc.ut-service]: [running]
[init.svc.yx_tbox]: [running]
# A6 有但徐工没有的服务:
# - [init.svc.evs_driver]: [running]
# - [init.svc.evs_manager]: [running]
# - [init.svc.gocsdk_auth]: [running]
# - [init.svc.vendor.sensors-hal-1-0]: [running]
```
### 2.5 进程数量
```console
# A6
console:/ $ ps -A | wc -l
278
# 徐工
console:/ $ ps -A | wc -l
259
```
### 2.6 bootanim 状态
```console
# 两个仓库
console:/ $ getprop init.svc.bootanim
stopped
```
### 2.7 数据分区大小
```console
# A6
console:/ $ df -h /data
Filesystem Size Used Avail Use% Mounted on
/dev/block/mmcblk1p26 44G 438M 43G 1% /data
# 徐工
console:/ $ df -h /data
Filesystem Size Used Avail Use% Mounted on
/dev/block/mmcblk1p25 44G 438M 43G 1% /data
```
---
## 三、对比分析
### 3.1 内核时间戳对比 (dmesg)
| 事件 | A6 | 徐工 | 差 |
|------|------|------|------|
| init first stage | 2.11s | 2.27s | +0.16s |
| init second stage | 2.54s | 2.70s | +0.16s |
| ueventd coldboot | 0.132s (2.84s) | 0.194s (3.03s) | +0.06s |
| GPU 驱动初始化 | 3.28s | 3.48s | +0.20s |
| GPU 固件加载 | 3.32s | 3.80s | +0.48s |
| GPU 完成 | 3.28s→3.32s (0.04s) | 3.48s→3.80s (0.32s) | +0.28s |
| e2fsck data 分区 | 3.82s→3.83s | 4.01s→4.13s | +0.30s |
| swap 开启 | 4.02s | 4.15s | +0.13s |
| update_verifier 完成 | ~5.14s | 5.14s | 持平 |
| sdcardfs 挂载 | 9.71s | 9.04s | **-0.67s ✅** |
| boot_completed checking | 7.50s | 7.55s | +0.05s |
| **boot_completed 到达** | **14.19s** | **17.52s** | **+3.33s ❌** |
### 3.2 Boot Progress 对比
将 logcat 时间戳转为开机后秒数:
| 阶段 | A6 (ms) | A6 (s) | 徐工 (ms) | 徐工 (s) | 差(s) |
|------|---------|--------|-----------|---------|-------|
| boot_progress_start | 5202 | 5.20 | 5238/5385 | 5.39 | +0.19 |
| preload_start | 5703 | 5.70 | 5744/5854 | 5.85 | +0.15 |
| preload_end | 6889 | 6.89 | 6757/6886 | 6.89 | 0.00 |
| system_run | 7075 | 7.08 | 6952/7089 | 7.09 | +0.01 |
| pms_start | 7339 | 7.34 | 7228/7338 | 7.34 | 0.00 |
| pms_system_scan_start | 7629 | 7.63 | 7423/7522 | 7.52 | -0.11 |
| pms_data_scan_start | 8043 | 8.04 | 7719/7850 | 7.85 | -0.19 |
| pms_scan_end | 8046 | 8.05 | 7722/7853 | 7.85 | -0.20 |
| pms_ready | 8264 | 8.26 | 7915/8048 | 8.05 | -0.21 |
| ams_ready | 9548 | **9.55** | 8645/8791 | **8.79** | **-0.76 ✅** |
| enable_screen | - | - | 11779/11813 | **11.81** | - |
| sf_stop_bootanim | 13996 | **14.00** | 17022/17264 | **17.26** | **+3.26 ❌** |
| wm_boot_animation_done | - | - | 17268 | 17.27 | - |
> 注:徐工 boot_progress 有两组时间戳(logcat 时间±开机时间),这里取开机时间换算值。
### 3.3 各阶段耗时对比
| 阶段 | A6 耗时 | 徐工耗时 | 差 | 结论 |
|------|---------|---------|------|------|
| start→preload_start | 0.50s | 0.46s | -0.04s | ✅ 正常 |
| preload_start→end (Zygote预加载) | 1.19s | 1.04s | -0.15s | ✅ 正常 |
| preload_end→system_run | 0.19s | 0.20s | +0.01s | ✅ 正常 |
| system_run→pms_start | 0.26s | 0.25s | -0.01s | ✅ 正常 |
| pms_start→pms_ready (PMS扫描) | 0.93s | 0.69s | -0.24s | ✅ 徐工略快 |
| pms_ready→ams_ready | 1.28s | 0.73s | -0.55s | ✅ 徐工快 |
| **ams_ready→sf_stop (总耗时)** | **4.45s** | **8.47s** | **+4.02s ❌** | **问题区间** |
| ams_ready→enable_screen | - | 3.02s | - | 正常范围 |
| **enable_screen→sf_stop** | - | **5.45s** | - | **等待Launcher** |
| **总 (start→sf_stop)** | **8.80s** | **11.87s** | **+3.07s ❌** | |
### 3.4 init 服务差异
| 差异项 | A6 | 徐工 | 影响 |
|--------|----|------|------|
| 总进程数 | 278 | 259 | −19(正常) |
| running services | 75 | 74 | 基本相同 |
| **特有服务** | evs_driver, evs_manager, gocsdk_auth, sensors-hal | check_data_partition, logcollector, ut-service, yx_tbox, StrLink | 差异微小,非导致3.5s的原因 |
| bootanim 状态 | stopped | stopped | 一致 |
### 3.5 Launcher 启动耗时(根因)
**徐工 `com.yaxon.launcher` 启动时间线:**
| 时间 | 事件 | 距开机(s) |
|------|------|----------|
| 10:05:07.916 | 进程启动 | ~9s |
| 10:05:07.980 | START LauncherActivity | ~9s |
| 10:05:08.409 | IocContainer 注入开始 | ~9.5s |
| 10:05:08.413 | UserCenterController | ~9.5s |
| 10:05:08.624 | CAR.AM 更新前台任务 | ~9.5s |
| 10:05:08.892 | SoundSourceViewModel ×2 | ~10s |
| 10:05:08.924 | IntelligentDrivingCardViewModel | ~10s |
| 10:05:09.037 | DisplayShareViewModel ×N | ~10s |
| 10:05:09.862 | onResume | ~10.5s |
| 10:05:12.540 | ForegroundAppMonitor 确认前台 | ~13.5s |
| 10:05:19.087 | DisplayShareBusyness resolve | ~20s |
| **10:05:28.038** | **Displayed: +2s598ms** | **~29s** |
A6 用的是 `SDDemo MainActivity`(简单 Demo 列表),没有复杂的依赖注入开销,启动极快,不影响 bootanim 停止时间。
---
## 四、根因结论
**前 9.5 秒两个仓库几乎一致,甚至徐工还快 1 秒。3.5 秒的差距全部集中在 `ams_ready` (8.79s) 到 `sf_stop_bootanim` (17.26s) 之间。**
具体链路:
```
ams_ready (8.79s)
→ system_server 启动各种服务
→ Launcher 启动 (9s)
→ enable_screen (11.81s) → 屏幕亮起(R5 彩条/动画)
→ WindowManager 等待 Launcher 绘制第一帧
→ Launcher 还在 IocContainer 注入中...
→ LauncherActivity onResume (10.5s)
→ Displayed (29s) ← 绘制完成!
→ sf_stop_bootanim (17.26s) ← WMS 等不及了,超时停止
→ boot_completed (17.52s)
```
**根本原因:`com.yaxon.launcher` 的 IocContainer 依赖注入太重**,大量 ViewModel 同步初始化(SoundSourceViewModel ×2、IntelligentDrivingCardViewModel ×2、DisplayShareViewModel ×N),拖慢了首帧绘制,导致 WindowManager 等待超时。
---
## 五、优化建议
### 5.1 治本:优化 Launcher 启动(推荐,预期收益 2-3s)
| 优化项 | 说明 | 预期收益 | 难度 |
|--------|------|---------|------|
| 非首屏 ViewModel 懒加载 | DisplayShareViewModel 等延后到主界面显示后注入 | ~1-2s | 中 |
| 减少同步 IocContainer DI 注入 | 改为异步/懒加载 | ~0.5-1s | 中 |
| 移除不必要的 broadcast | sendHomeStatusBroadcast 等 | ~0.1s | 低 |
### 5.2 治标:提前停 bootanim(预期收益 3-4s)
在 `enable_screen` 后就 `stopBootAnim()`,不等 Launcher 绘制。但 `boot_completed` 会在桌面未完全渲染时发出。
### 5.3 其他优化(组合使用可达 1-2s)
| 优化项 | 路径 | 说明 | 预期 |
|--------|------|------|------|
| bootlogo 4s hardcode | `bootlogo.c` `thread_sleep(4000)` | 改 500ms | ~3.5s |
| R5 动画提前结束 | 已实现彩条测试 | 收到 boot_completed 立即清屏 | 视觉改善 |
---
## 六、验证方式
```bash
# 1. boot_completed 内核时间戳
dmesg | grep "boot_completed"
# 2. Launcher 第一帧绘制时间
logcat -d | grep "Displayed.*launcher"
# 3. boot_progress 全链路
logcat -b events -d | grep "boot_progress\|sf_stop"
```
**目标:将 `sf_stop_bootanim` 从 17.26s 降低到 14s 以内。**
---
## 七、附录
### 7.1 R5 开机动画流程
```
LK Boot → FreeRTOS SafetyOS (lk_main)
→ apps_init() 启动 APP_START
├── bootlogo → 显示 YUV 图片, thread_sleep(4000)
├── USM → Unified Service Manager
│ ├── SplashScreen → 显示 RGB 品牌图
│ └── Animation → 播放 MJPEG 动画
│ ├── 新路径: ani_app_5.c (ANI_APP_MODE==5)
│ └── 通过 DCF property 监听 Android 启动状态
│ - DMP_ID_ANDROID_BOOT_STATUS (ID=16)
│ - 收到 boot_completed → 结束动画 → 清屏
```
### 7.2 Android Boot Completed 信号链路
```
sys.boot_completed=1
→ bootnotify.rc: start bootnotify
→ BootNotifyMain.cpp: setAndroidBootStatus(1)
→ ioctl(/dev/property, DCF_IOC_SET_PROPERTY, id=16)
→ 内核 DCF 驱动 (sdrv_property_client.c)
→ rpmsg 跨核通信
→ R5 property 回调 (android_changed_cb)
→ 结束动画 / 刷彩条
```
### 7.3 涉及源码路径
| 路径 | 说明 |
|------|------|
| `android10/vendor/semidrive/tools/BootNotify/` | Boot Completed 通知 |
| `android10/vendor/semidrive/tools/ScreenTrigger/` | 屏幕触发通知 |
| `android10/vendor/semidrive/tools/SDDemo/` | 测试 Demo / A6 Launcher |
| `rtos/freertos_safetyos/application/early_app/Animation/` | R5 动画服务 |
| `rtos/freertos_safetyos/application/early_app/bootlogo/` | Bootlogo 显示 |
| `rtos/freertos_safetyos/application/early_app/SplashScreen/` | 品牌图显示 |
| `rtos/freertos_safetyos/top/main.c` | R5 主入口 |
### 7.4 抓取数据所用命令
```bash
# 内核日志
dmesg
# Android boot stages
logcat -b events -d | grep -E "boot_progress|sf_stop|wm_boot"
# Launcher 日志
logcat -d | grep "com.yaxon.launcher"
# init 服务列表
getprop | grep "init.svc" | grep running
# bootanim 状态
getprop init.svc.bootanim
# 进程数
ps -A | wc -l
# 磁盘
df -h /data
```
@@ -0,0 +1,181 @@
# YX_Voice 架构设计
## 整体架构图
```
┌─────────────────────────────────────────────────────────────────────┐
│ YX_Voice 架构图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ TXZ协议 ┌──────────────────────────────┐ │
│ │ TXZ语音 │◄────────────►│ VoiceService │ │
│ │ 助手模块 │ │ (服务入口 & TXZ连接管理) │ │
│ └──────────────┘ └──────────────┬───────────────┘ │
│ │ │
│ ┌──────────────────────────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌──────────┐│
│ │ ProtocolManager │ │ VoiceManager │ │ Helpers ││
│ │ (协议分发管理) │ │ (核心状态管理) │ │ (辅助类) ││
│ └──────────┬───────────┘ └──────────┬───────────┘ └──────────┘│
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Protocol Implementations │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │ │
│ │ │ Voice │ │ Air │ │ Setting │ │ Media │ │ Car │ │ │
│ │ │Protocol │ │Protocol │ │Protocol │ │Protocol │ │Control │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ IPC Communication Layer │ │
│ │ YxIPC (跨进程通信框架) │ │
│ └──────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 音乐 │ │ 设置 │ │ 空调 │ │
│ │ 模块 │ │ 模块 │ │ 模块 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
## 模块分层
| 层级 | 模块 | 职责 |
|------|------|------|
| 应用层 | VoiceService | 服务入口、生命周期管理 |
| 核心层 | ProtocolManager, VoiceManager | 协议分发、状态管理 |
| 协议层 | BaseProtocol及子类 | 具体协议解析与执行 |
| 辅助层 | Helpers, Receivers, Observers | 状态监听、辅助功能 |
| 通信层 | YxIPC, TxzProtocolManager | 跨进程通信 |
---
## 协议处理流程
```
TXZ语音指令
│
▼
TxzProtocolManager.setDefaultHandler()
│
├── 解析 commandType (协议名称)
├── 解析 Bundle 参数
├── 转换为JSON格式
│
▼
ProtocolManager.onResponse()
│
▼
YxProtocolHandler.onHandleProtocol()
│
▼
具体Protocol实现类.onProtocolLogic()
```
---
## IPC通信架构
```
┌─────────────────┐ YxIPC ┌─────────────────┐
│ YX_Voice │◄──────────────►│ YX_Music │
│ (Server) │ │ (Client) │
└────────┬────────┘ └─────────────────┘
│
│ YxIPC
▼
┌─────────────────┐
│ YX_Setting │
└─────────────────┘
```
**IPC配置**:
```java
YxIpcConfig yxIpcConfig = new YxIpcConfig();
yxIpcConfig.addRemoteStartService(PackageConst.MUSIC_PKG);
yxIpcConfig.addRemoteStartService(PackageConst.SETTING_PKG);
yxIpcConfig.setRemoteListener(ProtocolManager.getInstance());
YxIPC.getInstance().bind(this, yxIpcConfig);
```
---
## TXZ协议通信
```
┌─────────────────┐ TxzProtocolManager ┌─────────────────┐
│ YX_Voice │◄─────────────────────►│ TXZ语音助手 │
│ (ConnectionService) │ (AIDL Service) │
└─────────────────┘ └─────────────────┘
```
**连接流程**:
```java
TxzProtocolManager.initialize(this);
TxzProtocolManager.connect(PackageConst.TXZ_PKG_NEW);
TxzProtocolManager.getConnection(PackageConst.TXZ_PKG_NEW).addConnectionListener(this);
```
---
## 模块依赖关系
```
YX_Voice
├── YX_IPC_SDK (IPC通信)
├── YX_Lib_CommonBase (公共基础库)
├── YX_AutoMotiveSDK (车载SDK)
├── library-release-0127.aar (TXZ协议库)
├── tools.jar (工具库)
├── framework_classes.jar (系统框架)
└── serial-0.1.6_classes.jar (串口库)
```
---
## 包名依赖
| 包名 | 说明 |
|------|------|
| com.txzing.voiceassistant | TXZ语音助手 |
| com.yaxon.music | 音乐模块 |
| com.yaxon.radio | 收音机模块 |
| com.yaxon.hvac | 空调模块 |
| com.yaxon.bluetooth | 蓝牙模块 |
| com.yaxon.setting | 设置模块 |
| com.yaxon.carcontrol | 车辆控制模块 |
| com.yaxon.avm | 360环视模块 |
| com.yaxon.dvr | 行车记录仪模块 |
| com.android.systemui | 系统UI |
---
## 核心模块
### VoiceService
- **继承关系**: `VoiceService extends BaseVoiceService implements ConnectionListener`
- **职责**: 与TXZ语音助手建立连接、接收并分发语音指令
### ProtocolManager
- **设计模式**: 单例模式
- **职责**: 实现IPC消息监听接口、将消息分发给协议处理器
### YxProtocolHandler
- **职责**: 维护包名到协议逻辑类的映射、在后台线程处理协议分发
### VoiceManager
- **设计模式**: 单例模式
- **职责**: 管理TXZ绑定状态、AVM/DVR前台状态、注册车辆状态回调
### VoiceEventHelper
- **职责**: 封装所有语音控制操作、与各模块进行IPC通信
---
*文档生成日期: 2026-03-19*
@@ -0,0 +1,655 @@
# YX_Voice 详细设计文档
## 1. 概述
### 1.1 项目简介
YX_Voice 是车载语音助手服务模块,作为第三方语音助手(同行者TXZ)与车载各功能模块之间的桥梁,负责语音指令的分发、处理以及车辆状态的同步。
### 1.2 项目信息
- **包名**: `com.yaxon.voice`
- **应用类型**: Android System Application
- **进程类型**: 系统持久化进程 (`persistent=true`)
- **共享UID**: `android.uid.system`
### 1.3 主要功能
1. 语音指令接收与分发
2. 车辆控制(空调、车窗、灯光等)
3. 媒体播放控制
4. 系统设置控制
5. 蓝牙电话控制
6. 车辆状态同步
---
## 2. 架构设计
### 2.1 整体架构图
```
┌─────────────────────────────────────────────────────────────────────┐
│ YX_Voice 架构图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ TXZ协议 ┌──────────────────────────────┐ │
│ │ TXZ语音 │◄────────────►│ VoiceService │ │
│ │ 助手模块 │ │ (服务入口 & TXZ连接管理) │ │
│ └──────────────┘ └──────────────┬───────────────┘ │
│ │ │
│ ┌──────────────────────────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌──────────┐│
│ │ ProtocolManager │ │ VoiceManager │ │ Helpers ││
│ │ (协议分发管理) │ │ (核心状态管理) │ │ (辅助类) ││
│ └──────────┬───────────┘ └──────────┬───────────┘ └──────────┘│
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Protocol Implementations │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │ │
│ │ │ Voice │ │ Air │ │ Setting │ │ Media │ │ Car │ │ │
│ │ │Protocol │ │Protocol │ │Protocol │ │Protocol │ │Control │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ IPC Communication Layer │ │
│ │ YxIPC (跨进程通信框架) │ │
│ └──────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 音乐 │ │ 设置 │ │ 空调 │ │
│ │ 模块 │ │ 模块 │ │ 模块 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
### 2.2 模块分层
| 层级 | 模块 | 职责 |
|------|------|------|
| 应用层 | VoiceService | 服务入口、生命周期管理 |
| 核心层 | ProtocolManager, VoiceManager | 协议分发、状态管理 |
| 协议层 | BaseProtocol及子类 | 具体协议解析与执行 |
| 辅助层 | Helpers, Receivers, Observers | 状态监听、辅助功能 |
| 通信层 | YxIPC, TxzProtocolManager | 跨进程通信 |
---
## 3. 核心模块详细设计
### 3.1 应用入口 (YxApplication)
**文件路径**: `com/yaxon/voice/YxApplication.java`
**职责**:
- 继承 `AutoMotiveSDKApplication`,提供车载SDK上下文
- 初始化IPC通信框架
- 初始化崩溃处理机制
- 启动语音服务
**关键代码流程**:
```
onCreate()
├── ToolInit.init() // 工具类初始化
├── CrashExceptionHandler // 崩溃处理
├── YxIPC.bind() // IPC绑定
│ └── ProtocolManager // 设置消息监听
├── PackagePermissionManager // 权限管理
└── startVoiceService() // 启动服务
```
### 3.2 语音服务 (VoiceService)
**文件路径**: `com/yaxon/voice/service/VoiceService.java`
**继承关系**: `VoiceService extends BaseVoiceService implements ConnectionListener`
**职责**:
- 与TXZ语音助手建立连接
- 接收并分发语音指令
- 管理各模块监听器生命周期
**初始化流程**:
```
onCreate()
├── initTxz() // 初始化TXZ连接
│ ├── TxzProtocolManager.initialize()
│ ├── TxzProtocolManager.connect()
│ └── addConnectionListener()
├── registerTxzCommandReceiver() // 注册指令接收器
├── BlueToothHelper.register() // 蓝牙监听
├── VoiceManager.registerCallbacks() // 车辆状态监听
└── 注册各种Receiver和Callback
```
**TXZ指令处理流程**:
```
TXZ语音指令
│
▼
TxzProtocolManager.setDefaultHandler()
│
├── 解析 commandType (协议名称)
├── 解析 Bundle 参数
├── 转换为JSON格式
│
▼
ProtocolManager.onResponse()
│
▼
YxProtocolHandler.onHandleProtocol()
│
▼
具体Protocol实现类.onProtocolLogic()
```
### 3.3 协议管理器 (ProtocolManager)
**文件路径**: `com/yaxon/voice/core/ProtocolManager.java`
**设计模式**: 单例模式
**职责**:
- 实现IPC消息监听接口 (`MessageListener`, `RemoteListener`)
- 将消息分发给协议处理器
**类图**:
```
┌─────────────────────────────────┐
│ ProtocolManager │
├─────────────────────────────────┤
│ - sInstance: ProtocolManager │
│ - mProtocolHandler: IProtocolHandler │
├─────────────────────────────────┤
│ + getInstance() │
│ + setProtocolHandler() │
│ + onResponse() │ ← MessageListener
│ + onBindRemote() │ ← RemoteListener
│ + release() │
└─────────────────────────────────┘
```
### 3.4 协议处理器 (YxProtocolHandler)
**文件路径**: `com/yaxon/voice/core/YxProtocolHandler.java`
**职责**:
- 维护包名到协议逻辑类的映射
- 在后台线程处理协议分发
**协议映射表**:
```java
protocolLogicMap.put(CAR_CAR_CONTROL_PKG, new CarControlProtocol()); // 车辆控制
protocolLogicMap.put(TXZ_PKG_NEW, new VoiceProtocol()); // 语音协议
protocolLogicMap.put(AIR_PKG, new AirProtocol()); // 空调协议
protocolLogicMap.put(BLUETOOTH_PKG, new BluetoothProtocol()); // 蓝牙协议
protocolLogicMap.put(MUSIC_PKG, new MediaProtocol()); // 媒体协议
protocolLogicMap.put(VIDEO_PKG, new MediaProtocol()); // 视频协议
protocolLogicMap.put(PHOTO_PKG, new MediaProtocol()); // 图库协议
protocolLogicMap.put(RADIO_PKG, new MediaProtocol()); // 收音机协议
protocolLogicMap.put(YX_VOICE_PKG, new YxProtocol()); // 内部协议
protocolLogicMap.put(SETTING_PKG, new SettingProtocol()); // 设置协议
protocolLogicMap.put(SYSTEM_UI_PKG, new SystemUIProtocol()); // 系统UI协议
protocolLogicMap.put(FACTORY_PKG, new FactoryProtocol()); // 工厂模式协议
protocolLogicMap.put(AVM_PKG, new AvmProtocol()); // 360环视协议
protocolLogicMap.put(DVR_PKG, new DvrProtocol()); // 行车记录仪协议
```
### 3.5 语音管理器 (VoiceManager)
**文件路径**: `com/yaxon/voice/core/VoiceManager.java`
**设计模式**: 单例模式
**职责**:
- 管理TXZ绑定状态
- 管理AVM/DVR前台状态
- 注册车辆状态回调(空调、设置、智能驾驶)
- 提供AudioTrackPlayer实例
**状态管理**:
```java
private boolean isVoiceBind; // TXZ绑定状态
private AtomicBoolean isAvm; // AVM前台状态
private AtomicBoolean isDvr; // DVR前台状态
```
### 3.6 语音事件助手 (VoiceEventHelper)
**文件路径**: `com/yaxon/voice/helper/VoiceEventHelper.java`
**职责**:
- 封装所有语音控制操作
- 与各模块进行IPC通信
- 通知TXZ状态变化
**主要功能分类**:
| 类别 | 方法示例 | 说明 |
|------|----------|------|
| 空调控制 | sendAirSwitch, sendAcSwitch, sendAirTempTo | 温度、风速、模式控制 |
| 音量控制 | sendVolumeTo, sendVolumeMute, sendVolumeElevator | 音量调节、静音 |
| 屏幕控制 | setMpuBrightnessLevel, sendScreenOff, sendScreenClean | 亮度、熄屏、清洁 |
| 系统设置 | switchSystemLanguage, switchDistanceUnit | 语言、单位切换 |
| 车窗控制 | sendWindowSwitch, sendWindowPercent | 开关、百分比控制 |
| 语音设置 | sendSwitchVoiceWakeup, customWakeupWord, switchVoiceMan | 唤醒词、发音人设置 |
---
## 4. 协议设计
### 4.1 协议接口定义
**IProtocolHandler (协议处理接口)**:
```java
public interface IProtocolHandler {
void onHandleProtocol(long msgSendTime, String remoteName, Bundle data);
}
```
**IProtocolLogic (协议逻辑接口)**:
```java
public interface IProtocolLogic {
void onProtocolLogic(Bundle data);
}
```
### 4.2 协议基类 (BaseProtocol)
```java
public abstract class BaseProtocol implements IProtocolLogic {
@Override
public void onProtocolLogic(Bundle data) {
String cmd = data.getString(VoiceConst.PARAMS_CMD_KEY, "");
switch (cmd) {
case CMD_SWITCH_TXZ_WINDOW: // 切换TXZ窗口
case CMD_PLAY_TTS: // 播放TTS
default:
onProtocolParse(data); // 子类实现具体解析
}
}
protected abstract void onProtocolParse(Bundle data);
}
```
### 4.3 TXZ协议常量定义
协议命令类型(部分):
#### 车辆控制类
| 命令常量 | 命令名称 | 说明 |
|----------|----------|------|
| CMD_OPEN_SUNROOF | openSunroof | 打开天窗 |
| CMD_CLOSE_SUNROOF | closeSunroof | 关闭天窗 |
| CMD_OPEN_WINDOW | openWindow | 打开车窗 |
| CMD_CLOSE_WINDOW | closeWindow | 关闭车窗 |
| CMD_OPEN_MIRROR_HEAT | openMirrorHeat | 打开后视镜加热 |
| CMD_OPEN_AVM | openAvm | 打开360环视 |
#### 空调控制类
| 命令常量 | 命令名称 | 说明 |
|----------|----------|------|
| CMD_OPEN_AC | openAc | 打开空调 |
| CMD_CLOSE_AC | closeAc | 关闭空调 |
| CMD_SET_AC_WIND_SPEED_LEVEL | setAcWindSpeedLevel | 设置风速档位 |
| CMD_SET_AC_TEMP_VALUE | setAcTempValue | 设置温度 |
#### 系统设置类
| 命令常量 | 命令名称 | 说明 |
|----------|----------|------|
| CMD_OPEN_BLUETOOTH | openBluetooth | 打开蓝牙 |
| CMD_OPEN_WIFI | openWifi | 打开WiFi |
| CMD_SET_VOLUME_TO_VALUE | setVolumeToValue | 设置音量 |
| CMD_SET_SCREEN_BRIGHTNESS_VALUE | setScreenBrightnessValue | 设置亮度 |
| CMD_SWITCH_VOICE_WAKEUP | switchVoicewakeup | 语音唤醒开关 |
| CMD_CUSTOM_WAKE_UP_WORD | customWakeupWord | 自定义唤醒词 |
#### 媒体控制类
| 命令常量 | 命令名称 | 说明 |
|----------|----------|------|
| CMD_PLAY_PAUSE | playPause | 暂停播放 |
| CMD_PLAY_RESUME | playResume | 继续播放 |
| CMD_PLAY_PREV | playPrev | 上一首 |
| CMD_PLAY_NEXT | playNext | 下一首 |
| CMD_OPEN_APP | openApp | 打开应用 |
---
## 5. 模块间通信设计
### 5.1 IPC通信架构
YX_Voice 使用 `YxIPC` 框架进行跨进程通信:
```
┌─────────────────┐ YxIPC ┌─────────────────┐
│ YX_Voice │◄──────────────►│ YX_Music │
│ (Server) │ │ (Client) │
└────────┬────────┘ └─────────────────┘
│
│ YxIPC
▼
┌─────────────────┐
│ YX_Setting │
└─────────────────┘
```
**IPC配置**:
```java
YxIpcConfig yxIpcConfig = new YxIpcConfig();
yxIpcConfig.addRemoteStartService(PackageConst.MUSIC_PKG);
yxIpcConfig.addRemoteStartService(PackageConst.SETTING_PKG);
yxIpcConfig.setRemoteListener(ProtocolManager.getInstance());
YxIPC.getInstance().bind(this, yxIpcConfig);
```
### 5.2 TXZ协议通信
TXZ语音助手使用自定义协议框架:
```
┌─────────────────┐ TxzProtocolManager ┌─────────────────┐
│ YX_Voice │◄─────────────────────►│ TXZ语音助手 │
│ (ConnectionService) │ (AIDL Service) │
└─────────────────┘ └─────────────────┘
```
**连接流程**:
```java
TxzProtocolManager.initialize(this);
TxzProtocolManager.connect(PackageConst.TXZ_PKG_NEW);
TxzProtocolManager.getConnection(PackageConst.TXZ_PKG_NEW).addConnectionListener(this);
```
### 5.3 消息格式
**Bundle格式**:
```java
Bundle bundle = new Bundle();
bundle.putString(VoiceConst.PARAMS_CMD_KEY, "命令类型");
bundle.putInt(VoiceConst.PARAMS_NUM_KEY, 数值);
bundle.putBoolean(VoiceConst.PARAMS_SWITCH_KEY, 开关状态);
bundle.putString(VoiceConst.PARAMS_TYPE_KEY, "类型");
```
---
## 6. 接收器与观察者
### 6.1 BroadcastReceiver
| 类名 | 监听Action | 功能 |
|------|------------|------|
| BootReceiver | ACTION_BOOT_COMPLETED | 开机启动服务 |
| PowerReceiver | ACTION_SCREEN_ON/OFF | 屏幕亮灭监听 |
| NetworkChangedReceiver | WIFI_STATE_CHANGED | WiFi状态变化 |
| BluetoothMonitorReceiver | Bluetooth状态广播 | 蓝牙连接状态 |
| HomeStatusReceiver | ACTION_HOME_STATUS_CHANGED | 主页状态变化 |
| VolumeChangedCallback | 音量变化回调 | 音量变化监听 |
| SettingReceiver | STREAM_MUTE_CHANGED_ACTION | 静音状态变化 |
| TestReceiver | 测试相关Action | 测试功能 |
### 6.2 ContentObserver
| 类名 | 监听URI | 功能 |
|------|---------|------|
| ScreenSaversObserver | STATE_URI + STANDBY_DIALOG_STATE | 屏保状态 |
| MediaObserver | MEDIA_SOURCE_URI, MUSIC_INFO_URI | 媒体信息 |
| ReverseObserver | 倒车相关URI | 倒车状态 |
| BrightnessObserver | MPU_BRIGHTNESS_LEVEL | 亮度变化 |
| SettingCommonObserver | VIDEO_LIMIT_URI等 | 通用设置 |
---
## 7. 状态同步机制
### 7.1 初始化同步
当TXZ连接成功时,同步所有状态:
```java
public void onConnected() {
VoiceManager.getInstance().setVoiceBind(true);
VoiceManager.VOICE_EVENT_HELPER.sendVoiceBindSuccess(null);
AppHelper.getInstance().startScanApp();
initNotifyCarConfig(); // 车辆配置
initNotifyBluetoothStatus(); // 蓝牙状态
initNotifyVolumeStatus(); // 音量状态
initNotifyCarSettingStatus(); // 车辆设置状态
sendBrightnessStatus(); // 亮度状态
}
```
### 7.2 状态同步命令
同步类命令(部分):
| 命令 | 说明 |
|------|------|
| syncAcStatus | 同步空调状态 |
| syncBlueToothStatus | 同步蓝牙状态 |
| syncWiFiStatus | 同步WiFi状态 |
| syncMediaVolume | 同步媒体音量 |
| syncScreenBrightness | 同步屏幕亮度 |
| syncVoiceWakeUpStatus | 同步语音唤醒开关 |
---
## 8. 依赖关系
### 8.1 模块依赖
```
YX_Voice
├── YX_IPC_SDK (IPC通信)
├── YX_Lib_CommonBase (公共基础库)
├── YX_AutoMotiveSDK (车载SDK)
├── library-release-0127.aar (TXZ协议库)
├── tools.jar (工具库)
├── framework_classes.jar (系统框架)
└── serial-0.1.6_classes.jar (串口库)
```
### 8.2 包名依赖
| 包名 | 说明 |
|------|------|
| com.txzing.voiceassistant | TXZ语音助手 |
| com.yaxon.music | 音乐模块 |
| com.yaxon.radio | 收音机模块 |
| com.yaxon.hvac | 空调模块 |
| com.yaxon.bluetooth | 蓝牙模块 |
| com.yaxon.setting | 设置模块 |
| com.yaxon.carcontrol | 车辆控制模块 |
| com.yaxon.avm | 360环视模块 |
| com.yaxon.dvr | 行车记录仪模块 |
| com.android.systemui | 系统UI |
---
## 9. 权限声明
```xml
<!-- 系统权限 -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.DEVICE_POWER"/>
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- 存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 其他权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
```
---
## 10. 部署配置
### 10.1 Gradle配置
```groovy
android {
defaultConfig {
applicationId "com.yaxon.voice"
}
aaptOptions {
noCompress "pcm" // 不压缩PCM音频文件
}
buildTypes {
release {
outputFileName = "YX_Voice.apk"
}
}
}
```
### 10.2 Manifest配置
```xml
<application
android:name=".YxApplication"
android:directBootAware="true"
android:persistent="true"
android:sharedUserId="android.uid.system">
```
---
## 11. 附录
### 11.1 文件结构
```
YX_Voice/
├── src/main/java/com/yaxon/voice/
│ ├── YxApplication.java # 应用入口
│ ├── api/ # 接口定义
│ │ ├── IProtocolHandler.java
│ │ └── IProtocolLogic.java
│ ├── constant/ # 常量定义
│ │ ├── PackageConst.java
│ │ ├── VoiceNewProtocolConst.java
│ │ └── VoiceProtocolConst.java
│ ├── core/ # 核心类
│ │ ├── PackagePermissionManager.java
│ │ ├── ProtocolManager.java
│ │ ├── VoiceManager.java
│ │ └── YxProtocolHandler.java
│ ├── crash/ # 崩溃处理
│ │ └── CrashExceptionHandler.java
│ ├── helper/ # 辅助类
│ │ ├── AppHelper.java
│ │ ├── AudioFocusHelper.java
│ │ ├── AudioTrackPlayer.java
│ │ ├── BlueToothHelper.java
│ │ ├── BrightnessHelper.java
│ │ ├── ContactHelper.java
│ │ ├── HandlerHelper.java
│ │ ├── JsonHelper.java
│ │ ├── McuHelper.java
│ │ ├── VoiceEventHelper.java
│ │ ├── VolumeHelper.java
│ │ └── WifiHelper.java
│ ├── impl/ # 回调实现
│ │ ├── CarAirCallback.java
│ │ ├── CarIntelligentDrivingCallBack.java
│ │ └── CarSettingCallback.java
│ ├── protocol/ # 协议实现
│ │ ├── AirProtocol.java
│ │ ├── AvmProtocol.java
│ │ ├── BaseProtocol.java
│ │ ├── BluetoothProtocol.java
│ │ ├── CarControlProtocol.java
│ │ ├── DvrProtocol.java
│ │ ├── FactoryProtocol.java
│ │ ├── MediaProtocol.java
│ │ ├── SettingProtocol.java
│ │ ├── SystemUIProtocol.java
│ │ ├── VoiceProtocol.java
│ │ └── YxProtocol.java
│ ├── provider/ # ContentObserver
│ │ ├── BrightnessObserver.java
│ │ ├── MediaObserver.java
│ │ ├── ReverseObserver.java
│ │ ├── ScreenSaversObserver.java
│ │ └── SettingCommonObserver.java
│ ├── receiver/ # 广播接收器
│ │ ├── BluetoothMonitorReceiver.java
│ │ ├── BootReceiver.java
│ │ ├── HomeStatusReceiver.java
│ │ ├── NetworkChangedReceiver.java
│ │ ├── PowerReceiver.java
│ │ ├── SettingReceiver.java
│ │ ├── TestReceiver.java
│ │ └── VolumeChangedCallback.java
│ ├── service/ # 服务
│ │ ├── BaseVoiceService.java
│ │ └── VoiceService.java
│ └── utils/ # 工具类
│ ├── AppUtils.java
│ ├── BroadcastUtils.java
│ └── DataConvert.java
├── libs/ # 依赖库
│ ├── library-release-0127.aar
│ └── tools.jar
├── assets/tts/ # TTS音频资源
└── build.gradle
```
### 11.2 语音唤醒词设置流程
```
用户语音输入唤醒词
│
▼
TXZ解析并发送协议: customWakeupWord
│
▼
VoiceProtocol.onProtocolLogic()
│
▼
VoiceEventHelper.customWakeupWord()
│
├── 检查当前值是否相同
├── 保存到Settings.System
└── 通过IPC发送到CAR_CAR_CONTROL_PKG
│
▼
车辆控制模块更新唤醒词
```
---
## 12. 版本历史
| 版本 | 日期 | 作者 | 说明 |
|------|------|------|------|
| 1.0 | 2024-01-05 | 张博威 | 初始版本 |
| 1.1 | 2024-02-19 | mango | TXZ协议集成 |
| 1.2 | 2024-07-02 | ZhangBoWei | 协议抽象优化 |
| 1.3 | 2025-03-13 | - | 唤醒词修改功能 |
---
*文档生成日期: 2026-03-18*
@@ -0,0 +1,338 @@
# 蓝牙模块 Git 提交记录汇总
> 统计时间: 2024-01-01 ~ 2026-05-06
> 提交总数: 230条
> 涉及模块: YX_BlueTooth, YX_BluetoothSDK
---
## 一、语音与蓝牙交互 (35条)
### 1.1 语音控制蓝牙功能
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-03-27 | 7e9d994a8 | 修改语音蓝牙拒接逻辑 |
| 2026-03-27 | c0dc00a8b | 蓝牙来电,语音拒接不了问题 |
| 2026-03-24 | 0c7512035 | 新增蓝牙通讯录共享功能 |
| 2026-03-11 | e362ac75d | 同步给语音蓝牙通讯录和通话记录页面打开和关闭状态 |
| 2026-02-11 | 5fc8c14cb | 新增语音蓝牙查看通讯录和通话记录跳转 |
| 2024-03-11 | 8faaffd29 | 增加通过语音控制蓝牙相关功能 |
| 2024-03-08 | ac659eabb | 实现语音对蓝牙音乐的控制 |
| 2024-04-07 | 7cead2675 | 增加语音对蓝牙通讯录同步开关进行控制 |
### 1.2 语音与蓝牙音乐交互
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-03-23 | 22b19dc21 | 修改蓝牙音乐播放没响应问题 |
| 2026-03-17 | ac6575de4 | 新增亿连播放音乐的时候请求蓝牙音频焦点 |
| 2024-10-22 | c258164fd | 修复蓝牙音乐播放过程中,唤醒语音,播放按钮多跳变 |
| 2024-05-08 | d93dd2ede | 修复接听电话挂断后语音唤醒不了 |
| 2024-05-07 | 0a54a8f97 | 修复语音与蓝牙音乐混响的问题 |
| 2024-04-24 | 8b0aa26ba | 修复蓝牙音乐被语音连续打断两次后,停止播放 |
| 2024-08-30 | 2b0735476 | 修改USB音乐和蓝牙音乐传给同行者播放状态有误 |
| 2024-05-21 | 05136e9c3 | 提交语音音乐指令常量修改 |
| 2024-05-21 | c0a852f9a | 去掉无用的媒体指令常量 |
| 2024-04-24 | fd83a2721 | 去掉废弃的语音函数 |
### 1.3 电话状态同步给语音
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-09-25 | 9625bff28 | 拨打电话的状态同步给语音 |
| 2024-03-11 | d7cfc38a6 | 增加电话信息同步给语音 |
| 2024-03-11 | f8317495b | 同步电话拒接信息给语音 |
| 2024-03-11 | 4eed44177 | 蓝牙连接状态通知给语音 |
---
## 二、蓝牙音乐功能 (25条)
### 2.1 音乐播放状态
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-03-25 | 0290c5a84 | 蓝牙音乐新增倒车暂停播放 |
| 2025-12-16 | 916e700c1 | 修复强制杀死蓝牙音乐应用后,launcher播放中状态没改变 |
| 2025-12-24 | 6a3d81c06 | 优化蓝牙音乐逻辑 |
| 2025-03-21 | 3135c7b4d | 重启车机后自动连接,蓝牙音乐界面显示播放状态,实际没播放 |
| 2025-02-24 | 15ba71006 | 播放蓝牙音乐中,电源OFF→ON后,界面显示播放但实际没播放 |
| 2025-02-14 | fb76ce90e | 电源OFF→ON后,再次播放音乐,声音变小 |
| 2025-02-05 | ae6337f0c | 车辆下电再上电后,音乐卡片播放按钮无法操作 |
| 2024-04-15 | ccf44711b | 修复视频播放被蓝牙音乐暂停,点击未恢复播放 |
| 2024-04-08 | 9bcd8929d | 修复手机播放音乐时连接蓝牙蓝牙音乐无声 |
| 2024-03-19 | 82572176f | 重启自动连接后,手机播放,车机显示播放然后暂停 |
### 2.2 音乐投屏与仪表
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-02-20 | e45e30069 | 修复切换蓝牙音乐和USB音乐时,仪表投屏闪烁 |
| 2023-10-08 | 8a749b570 | 修改专辑图片不为空时才传数据给投屏 |
| 2023-08-11 | 99f9fd96a | 数据中心添加蓝牙专辑图片及歌曲进度,供投屏使用 |
### 2.3 音乐焦点与控制
| 日期 | Commit | 描述 |
|------|--------|------|
| 2023-09-27 | daa5146ff | 修改蓝牙音乐不申请焦点前,小控件不显示播放信息 |
| 2023-08-25 | 58a661adb | 蓝牙音乐添加静音设置功能 |
| 2023-08-17 | b42455a51 | 蓝牙音乐添加方控控制 |
| 2023-08-11 | 6c58e6ed3 | 添加蓝牙音频流接口 |
| 2024-05-24 | 39dbd133c | 过滤掉蓝牙音乐 Not Provided |
---
## 三、蓝牙电话功能 (40条)
### 3.1 来电/去电显示
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-05 | 0fb28752a | 优化网络电话功能 |
| 2025-11-05 | 6af47aa37 | 优化网络电话判断逻辑 |
| 2025-10-17 | 184c93201 | 更新蓝牙电话应用外float显示功能 |
| 2025-10-16 | 0fd9c73ec | 优化电话来电不可以切换音频通道 |
| 2025-10-09 | 5a4a9e6c0 | 更新电话拨打页面缩小UI及通讯录点击拨打逻辑 |
| 2025-09-30 | c4018d7e9 | 优化更新应用外电话弹出框功能 |
| 2024-05-21 | 4974f4cb1 | 修复蓝牙去电流程,功能示意图标处没显示当前连接蓝牙名称 |
| 2024-04-20 | 231ae2bb6 | 修复拨打微信电话时提示语显示不完整 |
| 2024-03-25 | 649577900 | 修复语音未及时同步通讯录的问题 |
| 2023-12-23 | d9cd419e2 | 修复第三方来电时,不显示通话时间(全屏) |
| 2023-12-23 | 29ac3327e | 修复来电时,左侧图不显示当前连接蓝牙名称 |
| 2023-12-19 | 55862fb86 | 修复蓝牙电话去电未接通UI错误 |
### 3.2 电话通话状态
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-09-05 | b4452bb57 | 修复蓝牙电话搜索名字结果,点击后应先显示号码 |
| 2024-03-15 | 8d6b61675 | 解决导航过程中方向盘按键暂停蓝牙音乐,导航播完后自动继续播放 |
| 2024-03-19 | 810509299 | 修复蓝牙通话记录同步时,圈圈一卡一卡的问题 |
| 2024-02-23 | ee6f6780c | 修复蓝牙通话时点击非通话悬浮框区域也跳转到通话页面 |
| 2024-01-25 | a5fc79803 | 修复连接蓝牙,呼出电话,方控ok按键没有挂断电话 |
| 2024-01-17 | b9f71dc21 | 修复通话记录中不应该记录网络通话记录的问题 |
| 2023-12-13 | 18a297d93 | 修复蓝牙电话通话中,acc off,手机还显示蓝牙连接状态 |
| 2023-11-11 | 4d72baadb | 修复蓝牙电话去电仪表没有投屏效果 |
| 2023-11-07 | 96a1efe59 | 修复蓝牙电话挂断后挂断键没有置灰 |
| 2023-10-08 | 0e6124187 | 传输蓝牙电话结束的状态给投屏 |
| 2023-10-08 | 753ff3f7f | 把电话投屏数据通过json格式进行传输 |
### 3.3 电话投屏接口
| 日期 | Commit | 描述 |
|------|--------|------|
| 2023-08-22 | fc81093dd | 蓝牙电话投屏接口添加号码归属地及通话时长功能 |
| 2023-08-11 | 803a170df | 添加蓝牙电话各状态至数据中心供投屏使用 |
| 2023-08-14 | d19784227 | 联调蓝牙电话投屏状态错误问题 |
---
## 四、通讯录同步 (30条)
### 4.1 同步流程优化
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-07-21 | 6421f38b7 | 联系人和通话记录同步完成后,修正isPhoneBookPulling为false |
| 2024-04-10 | 9deeb99c4 | 修复提示同步失败后,蓝牙通话记录一直在加载同步 |
| 2024-04-04 | faaa29039 | 修复同步通讯录完成后,再次点击同步,界面卡在正在同步界面 |
| 2024-03-30 | 271a40273 | 修复点击拨号键,界面闪现同步通讯录画面的问题 |
| 2024-03-25 | 649577900 | 修复语音未及时同步通讯录的问题 |
| 2024-03-12 | a731df32b | 修复系统设置界面开启自动同步通讯录开关后,蓝牙电话记录自动同步太慢 |
| 2024-01-29 | 2b2f2e130 | 修复手机通话记录为空时,中控显示通话记录未同步的问题 |
| 2024-01-25 | 8d541765c | 修复偶现同步联系人和通话记录后缺少一个通话记录的问题 |
| 2024-01-22 | 6155a30fc | 修复联系人没有存电话号码,车机端不能同步该联系人 |
| 2024-01-04 | 2c95dca9c | 修复在同步联系人过程中,同步时间很久的问题 |
| 2024-01-03 | 0354c65d3 | 修复连接蓝牙后未同步通话记录下用车机拨号,通话记录消失 |
| 2023-12-20 | 9dcbac50b | 修复蓝牙关闭自动同步,还能同步成功的问题 |
| 2023-08-22 | a7e216453 | 通讯录盖尾限制2000条 |
| 2023-08-17 | c02339467 | 通讯录限制在2500条,超出部分去除 |
### 4.2 通讯录UI与交互
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-03-21 | d2309fb29 | 修复配对完蓝牙,切换到通讯录界面,界面显示异常有重叠 |
| 2025-10-13 | 0680db5b1 | 优化通讯录个数UI显示 |
| 2025-10-16 | 0b947f3a4 | 更新蓝牙通讯录默认选中第一个 |
| 2025-10-08 | 00415fb2d | 优化蓝牙通讯录页面连接断开逻辑 |
| 2024-06-11 | 4c4092983 | 修复点击刷新按钮右侧会闪过联系人详情页 |
| 2024-05-21 | 5b73c1cd0 | 修复多次点击通讯录刷新按钮,会跳出通讯录未同步 |
| 2024-04-22 | 4e4c7d2f6 | 修复蓝牙电话滑动联系人列表,联系人详情页依然显示 |
| 2024-04-19 | a7fb60fe3 | 修复蓝牙电话通讯录显示有中文 |
| 2023-12-25 | 159ed069b | 修复连接蓝牙,点击最近使用的电话进入通讯录页面,加载不出来 |
| 2023-12-15 | 3304c57a5 | 修复未连接蓝牙时,无法切换到通讯录页面 |
| 2023-12-14 | 3f389a05b | 修复蓝牙电话-通讯录-字母查询-通讯录中没有的字母,无反应 |
| 2023-08-11 | 409bc14f6 | 按照新的设计要求修改通讯录列表及添加通讯录搜索功能 |
| 2023-07-26 | 991c8eea3 | 蓝牙添加通讯录和通话记录功能 |
---
## 五、蓝牙连接状态管理 (20条)
### 5.1 ACC电源状态处理
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-20 | 295607af2 | 修改acc开关,蓝牙不做断开或重连操作,保持原状态 |
| 2024-01-15 | 3ee792ae9 | 修复负一屏点击整车下电后,踩刹车上电,蓝牙没有自动连接 |
| 2024-01-09 | dd6d12c31 | 修复蓝牙上电后第一次进入蓝牙音乐界面时,蓝牙音乐会暂停一下 |
| 2024-01-02 | 9258088f0 | 修复上电后,手机端播放蓝牙音乐,从主界面进入后蓝牙音乐暂停 |
| 2023-12-07 | fdfe2c820 | 修复播放蓝牙音乐过程中断开ACC再重新连接,进度条显示00:00 |
| 2023-12-13 | 18a297d93 | 修复蓝牙电话通话中,acc off,手机还显示蓝牙连接状态 |
### 5.2 蓝牙可见性与配对
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-01-28 | a0e15fe92 | 进入蓝牙设置页面,手机就搜不到车机的蓝牙问题 |
| 2026-01-28 | c1a490f80 | 修改蓝牙可见性 |
| 2025-12-29 | 6d491ec73 | 新增蓝牙配对设备最大数量限制为10;配对请求5s未响应自动拒绝 |
| 2024-01-12 | d89e68cd7 | 修复连接一个蓝牙后,另一个手机配对成功,通讯录界面蓝牙名称没更新 |
| 2023-12-21 | fbf1637bb | 修复打开蓝牙然后关闭开放蓝牙监测,手机还能搜索到车机 |
| 2023-12-08 | a815b4bef | 修复车机连接手机的话,蓝牙连接和手机配对成功,连接失败 |
| 2023-10-19 | 60c5779cd | 修复蓝牙可见性不可见性修改 |
### 5.3 蓝牙名称管理
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-09-05 | e238b8d4c | 修改默认蓝牙和WiFi名称 |
| 2024-01-05 | 6a75240d7 | 连接时实时刷新蓝牙名称 |
| 2023-12-21 | 42b72c8f2 | 修复写入新的vin码,蓝牙名称还显示原来的 |
| 2023-11-14 | 5f3d5e800 | 修复蓝牙名称恢复出厂设置显示错误问题 |
| 2023-09-27 | 538f326a6 | 蓝牙名称修改成vin后6位 |
| 2023-08-17 | a5d1f78d2 | 修复蓝牙名称加vin码默认名称 |
---
## 六、UI适配与优化 (35条)
### 6.1 深色模式适配
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-21 | 49a6ca185 | 修改深色模式状态监听模式 |
| 2025-11-14 | 2175a4bd8 | 修改联系人字母显示dark模式颜色 |
| 2025-11-14 | f7999efe6 | 添加深色模式适配功能 |
### 6.2 右舵/国际化适配
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-01-17 | 9c43ac6ad | 添加适配12.3寸屏幕功能 |
| 2025-02-26 | 83fb691b6 | 修改蓝牙电话右舵UI效果 |
| 2025-02-25 | 8ef5fb690 | 蓝牙新增右舵项目布局资源 |
| 2024-03-11 | 1b292b8ed | 新增阿拉伯语 |
| 2023-08-15 | afb4eb70a | 修改通话记录两天以上,一周以内显示星期 |
### 6.3 界面UI更新
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-17 | 6c6a39f36 | 优化蓝牙电话背景图片以及搜索功能 |
| 2025-11-04 | e3413202b | 修改蓝牙连接按键跳转到最新的页面 |
| 2025-11-04 | 6b0c946ad | 更新通讯录文案为联系人 |
| 2025-10-16 | 449e78098 | 修改拨号按键不可用时,透明度为0.6 |
| 2025-10-16 | 5f97d7f07 | 根据最新UE调整蓝牙电话页面显示 |
| 2025-10-15 | 8eb033ea3 | 优化拨号按键板功能 |
| 2025-10-15 | 118b7c74c | 优化按键板搜索同时搜索通话记录和通讯录 |
| 2025-10-08 | 85b3fc57e | 修改通话记录时间显示格式 |
| 2025-09-28 | be6c7aa5e | 更新蓝牙UI |
| 2025-09-26 | 5fedd8aea | 更新蓝牙UI相关功能 |
| 2025-07-21 | 3ff34003d | 修改空联系人提示UI位置错误 |
| 2024-05-24 | 738188f67 | 修复蓝牙来电显示界面,文言重叠 |
| 2024-03-21 | 13bc1ecdf | 修复了蓝牙文字重叠的问题 |
| 2023-12-21 | a3b1ba89f | 修复未连接蓝牙时通话界面和UI不一致 |
| 2023-12-04 | dea35c78f | 修复蓝牙通话记录没有滚动条,与UI设计不符 |
| 2023-10-31 | 5e6a03a0b | 蓝牙电话通话页面V1.3UI适配修改 |
| 2023-10-27 | d9acac409 | 蓝牙通话记录和通讯录V1.3UI修改适配 |
| 2023-08-31 | d893ecdf4 | 修复蓝牙相关的走查UI问题 |
---
## 七、国际化/翻译 (15条)
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-07-11 | 07d05e2c0 | 修复蓝牙电话翻译显示格式错误 |
| 2024-07-05 | a418fc475 | 修复未找到"%s"的相关结果的英文和需求对应不上 |
| 2024-05-13 | b1d5b4b3b | 蓝牙翻译文本更新 |
| 2024-04-18 | 67c6c9aa6 | 修复蓝牙(未找到"%s"的相关结果)的英文和需求对应不上 |
| 2024-03-28 | 9dfce2f80 | 修复通话名称Dial和Deepway多语言翻译不一致 |
| 2023-11-22 | c2d68a84d | 蓝牙数字统一设置DINGPRO格式 |
| 2023-07-19 | da0d81c9f | 参照UE调整通讯录和通话记录 |
---
## 八、代码优化与重构 (15条)
### 8.1 工具类统一
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-12-24 | 653c4b6b3 | 统一使用YX_log工具打印 |
| 2025-12-17 | 48c6f2d2d | 修改toast调用,统一调用自定义toast |
| 2025-12-17 | 0f688b8fc | 不用系统toast,添加自定义toast |
| 2024-03-25 | b232e10ad | 优化日志打印 |
### 8.2 构建优化
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-05 | 7ebb3b171 | 优化sdk build.gradle |
| 2025-11-05 | b5b840b98 | 添加SharedPrefsManager.init |
| 2024-12-24 | abf1034fd | 删除无效资源,png图片转webp,优化apk体积 |
| 2024-12-25 | e39ee2e1e | 修改打包资源错误导致没有打包到中文资源 |
| 2023-12-12 | 18a297d93 | 去掉依赖库,可以通过脚本编译release的apk |
| 2024-03-05 | 6447ff187 | 优化代码,去掉无用的资源引用 |
---
## 九、Bug修复 (杂项) (15条)
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-04-18 | 2ffb50935 | 修复出现蓝牙ANR |
| 2024-01-18 | e948b2e69 | 修复麦克风禁用问题 |
| 2024-01-08 | 4e5eb00d7 | 修复monkey crash问题 |
| 2023-11-02 | f5ef447c6 | 修复SQLiteCursor未关闭引起的异常 |
| 2023-11-23 | 0bc5caee4 | 修复跑monkey时出现蓝牙ANR问题 |
| 2023-10-13 | b61120c85 | 处理专辑封面闪烁问题 |
| 2023-10-14 | b7cc4717a | 修复蓝牙电话切换音源,仪表会出现消失一下 |
| 2023-10-20 | 8040146aa | 解决偶现断连后,状态栏不显示蓝牙图标 |
| 2023-09-21 | ebf265af1 | 处理蓝牙电话和蓝牙音乐方向盘控制功能 |
| 2023-09-21 | c6784f0ef | 处理空指针报错 |
| 2023-09-06 | d2fd3d9be | 修复蓝牙跑monkey崩溃问题 |
| 2023-08-24 | 6c58e6ed3 | 添加蓝牙音频流接口 |
---
## 十、关键修改点总结 (便于下次修改)
### 10.1 语音交互入口类
- `VoiceEventHelper.java` - 语音事件处理,所有语音蓝牙操作入口
- `MediaProtocol.java` - 媒体协议,蓝牙音乐状态同步
- `VoiceNewProtocolConst.java` - TXZ语音协议常量
### 10.2 蓝牙音乐核心类
- `BluetoothHfpClient.java` - HFP协议,电话状态
- 音频焦点管理 - 与语音、导航、视频的焦点竞争
- 投屏数据格式 - JSON格式传输给仪表
### 10.3 通讯录同步核心类
- `BluetoothPbapClient.java` - PBAP协议入口
- `BtContactsManager.java` - 通讯录管理
- `isPhoneBookPulling` - 同步状态标志,需在完成后置false
- 同步限制: 通讯录2500条/2000条盖尾,通话记录50条
### 10.4 ACC电源处理要点
- ACC OFF时不主动断开蓝牙,保持原状态
- ACC ON后恢复播放状态需校验实际播放状态
- 下电再上电后播放按钮状态同步
### 10.5 UI适配要点
- 深色模式: 联系人字母颜色、监听模式
- 右舵项目: 需要单独布局资源
- 12.3寸屏幕适配
### 10.6 国际化要点
- 阿拉伯语已支持
- 通话记录时间格式: "昨天"根据语言切换
- 数字格式统一DINGPRO格式
---
## 十一、常见问题快速定位
| 问题类型 | 关键类/方法 | 日志关键字 |
|---------|------------|-----------|
| 通讯录不同步 | BluetoothPbapClient.phoneBookPullRequest() | BtContactsManager, onContactsPullCompleted |
| 音乐状态异常 | 音频焦点处理 | AudioManager, onFocusChange |
| 语音拒接失败 | VoiceEventHelper拒接逻辑 | 蓝牙拒接, HFP |
| ACC状态处理 | 电源状态监听 | ACC_OFF, ACC_ON |
| 投屏数据 | JSON数据传输 | 投屏, DataCenter |
| ANR问题 | 主线程阻塞 | ANR, SQLiteCursor |
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,204 @@
# 三方应用对接授权文档
一、概述
徐工车机项目的隐私授权系统通过 Android Settings.System 存储授权状态(0/1值),第三方应用通过读取这些
Key 的值来判断用户是否已授权,进而决定是否允许对应功能的访问。
二、读取授权状态
2.1 权限授权状态
全局开关和有效期
┌────────────────────────────────────────┬──────┬──────────────────────┐
│ Key │ 类型 │ 说明 │
├────────────────────────────────────────┼──────┼──────────────────────┤
│ xcmg_permission_global_switch │ int │ 0 = 关闭,1 = 开启 │
├────────────────────────────────────────┼──────┼──────────────────────┤
│ xcmg_permission_authorization_duration │ int │ 1 = 180天,2 = 360天 │
├────────────────────────────────────────┼──────┼──────────────────────┤
│ xcmg_permission_authorization_date │ long │ 授权开始时间戳(毫秒) │
└────────────────────────────────────────┴──────┴──────────────────────┘
具体权限Key及使用场景
┌────────────────────────────────────────────┬──────────────────────────────────────┐
│ Key │ 使用场景 │
├────────────────────────────────────────────┼──────────────────────────────────────┤
│ xcmg_permission_contacts_bluetooth_phone │ 联系人权限 - 蓝牙电话读取联系人 │
├────────────────────────────────────────────┼──────────────────────────────────────┤
│ xcmg_permission_microphone_voice_assistant │ 麦克风权限 - 语音助手录音 │
├────────────────────────────────────────────┼──────────────────────────────────────┤
│ xcmg_permission_microphone_bluetooth_phone │ 麦克风权限 - 蓝牙电话通话 │
├────────────────────────────────────────────┼──────────────────────────────────────┤
│ xcmg_permission_location_voice_assistant │ 位置权限 - 语音助手获取位置 │
├────────────────────────────────────────────┼──────────────────────────────────────┤
│ xcmg_permission_location_map │ 位置权限 - 地图/导航获取位置 │
├────────────────────────────────────────────┼──────────────────────────────────────┤
│ xcmg_permission_location_carlink │ 位置权限 - 亿连手机互联获取位置 │
├────────────────────────────────────────────┼──────────────────────────────────────┤
│ xcmg_permission_camera_dvr │ 摄像头权限 - 行车记录仪录像 │
└────────────────────────────────────────────┴──────────────────────────────────────┘
读取示例(Java):
// 读取某个具体权限状态
int locationMapPermission = Settings.System.getInt(
context.getContentResolver(), "xcmg_permission_location_map", 0);
// 1 = 已授权, 0 = 未授权
// 判断授权是否有效(完整判断逻辑)
boolean isAuthorized = false;
int globalSwitch = Settings.System.getInt(
context.getContentResolver(), "xcmg_permission_global_switch", 0);
if (globalSwitch == 1) {
long authDate = Settings.System.getLong(
context.getContentResolver(), "xcmg_permission_authorization_date", 0);
int duration = Settings.System.getInt(
context.getContentResolver(), "xcmg_permission_authorization_duration", 0);
int totalDays = (duration == 1) ? 180 : 360;
long daysPassed = (System.currentTimeMillis() - authDate) / (1000 * 60 * 60 * 24);
if (daysPassed < totalDays) {
// 有效期未过,读取具体权限
int permission = Settings.System.getInt(
context.getContentResolver(), "xcmg_permission_location_map", 0);
isAuthorized = (permission == 1);
}
}
三、判断逻辑
流程图:
┌─────────────┐ 否 ┌──────────┐
│ 隐私已授权? ├────────────►│ 未授权 │
└──────┬──────┘ └──────────┘
│是
▼
┌─────────────┐ 否 ┌──────────┐
│ 全局开关开启?├────────────►│ 未授权 │
└──────┬──────┘ └──────────┘
│是
▼
┌─────────────┐ 是 ┌──────────┐
│ 授权已过期? ├────────────►│ 未授权 │
└──────┬──────┘ └──────────┘
│否
▼
┌─────────────┐ 否 ┌──────────┐
│ 具体权限=1? ├────────────►│ 未授权 │
└──────┬──────┘ └──────────┘
│是
▼
┌──────────┐
│ 已授权 │
└──────────┘
四、具体弹窗代码
三方应用在读取权限Key为0(未授权)后,需弹出"允许/不允许"授权提示框。推荐通过广播触发系统弹窗(方式1),
系统已内置对话框和处理逻辑;
方式1:广播触发系统弹窗(推荐)
发送广播后,系统 PrivacyPolicyMonitorService 会自动弹出权限授权请求对话框,包含"允许"和"不允许"按钮,
用户点击"允许"后系统自动将对应 Settings.System Key 设为1,并继续检查下一个未授权权限。
/**
* 请求权限授权,触发系统弹出"允许/不允许"对话框
* @param context 上下文
* @param targetPackageName 目标应用包名(用于系统判断所需权限列表)
* @param permissionKey 具体的权限Key
*/
public static void requestPermission(Context context, String targetPackageName, String permissionKey) {
Intent intent = new Intent("com.yaxon.carcontrol.ACTION_OPEN_PERMISSION_UI");
intent.putExtra("package_name", targetPackageName);
intent.putExtra("permission_key", permissionKey);
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
context.sendBroadcast(intent);
}
// 使用示例:百度地图检测到位置权限未授权
if (locationPermission == 0) {
requestPermission(context, "com.title.mapautosdk", "xcmg_permission_location_map");
}
系统弹窗样式:
┌─────────────────────────────────────┐
│ 提示 │
│ │
│ 位置权限-地图:是否允许开启 │
│ 位置权限? │
│ │
│ 开启后,地图将被授权可获取位置信息 │
│ │
│ [允许] [不允许] │
└─────────────────────────────────────┘
用户操作结果:
- 点击"允许" → Settings.System 写入该 Key=1 → 继续检查下一个未授权权限 → 全部授权后自动启动目标应用
- 点击"不允许" → Settings.System 写入该 Key=0 → 流程结束
五、应用包名与权限Key对照表
┌──────────────────────────────┬──────────────────────────────────────────────────────────┐
│ 应用包名 │ 权限Key │
├──────────────────────────────┼──────────────────────────────────────────────────────────┤
│ com.yaxon.bluetooth │ xcmg_permission_contacts_bluetooth_phone │
│ │ xcmg_permission_microphone_bluetooth_phone │
├──────────────────────────────┼──────────────────────────────────────────────────────────┤
│ com.txznet.txz │ xcmg_permission_microphone_voice_assistant │
│ (同行者语音助手) │ xcmg_permission_location_voice_assistant │
├──────────────────────────────┼──────────────────────────────────────────────────────────┤
│ com.txznet.adapter │ xcmg_permission_microphone_voice_assistant │
│ (同行者语音适配) │ xcmg_permission_location_voice_assistant │
├──────────────────────────────┼──────────────────────────────────────────────────────────┤
│ com.txzing.voiceassistant │ xcmg_permission_microphone_voice_assistant │
│ (语音助手) │ xcmg_permission_location_voice_assistant │
├──────────────────────────────┼──────────────────────────────────────────────────────────┤
│ com.title.mapautosdk │ xcmg_permission_location_map │
│ (百度地图) │ │
├──────────────────────────────┼──────────────────────────────────────────────────────────┤
│ net.easyconn │ xcmg_permission_location_carlink │
│ (亿连手机互联) │ │
├──────────────────────────────┼──────────────────────────────────────────────────────────┤
│ com.yaxon.dvr │ xcmg_permission_camera_dvr │
│ (行车记录仪) │ │
└──────────────────────────────┴──────────────────────────────────────────────────────────┘
---
六、Key 速查(可直接复制)
**全局控制:**
```
xcmg_permission_global_switch // 全局开关 0=关闭 1=开启
xcmg_permission_authorization_duration // 有效期 1=180天 2=360天
xcmg_permission_authorization_date // 授权开始时间戳(毫秒)
```
**权限 Key:**
```
xcmg_permission_contacts_bluetooth_phone // 联系人权限 - 蓝牙电话读取联系人
xcmg_permission_microphone_voice_assistant // 麦克风权限 - 语音助手录音
xcmg_permission_microphone_bluetooth_phone // 麦克风权限 - 蓝牙电话通话
xcmg_permission_location_voice_assistant // 位置权限 - 语音助手获取位置
xcmg_permission_location_map // 位置权限 - 地图/导航获取位置
xcmg_permission_location_carlink // 位置权限 - 亿连手机互联获取位置
xcmg_permission_camera_dvr // 摄像头权限 - 行车记录仪录像
```
**应用包名:**
```
com.yaxon.bluetooth // 蓝牙
com.txznet.txz // 同行者语音助手
com.txznet.adapter // 同行者语音适配
com.txzing.voiceassistant // 语音助手
com.title.mapautosdk // 百度地图
net.easyconn // 亿连手机互联
com.yaxon.dvr // 行车记录仪
```
@@ -0,0 +1 @@
日志分析开机时间: logcat -b events | grep "boot_progress"
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

@@ -0,0 +1,115 @@
# 徐工项目相关文档
> 整理自腾讯文档/WPS云文档
---
## 参考文档
- [腾讯文档:徐工项目(软件)](https://docs.qq.com/sheet/DZHNRTm1rQ29OTlpa?tab=BB08J2)
- [金山文档:徐工项目车辆设置需要的CAN信号2-周帅](https://www.kdocs.cn/l/ctqaRN8zXfeR)
---
## 一、新版刷机软件
![[徐工相关_图片/image_1.png]]
---
## 二、源码编译
### 2.1 编译步骤
| 步骤 | 命令 | 说明 |
| --- | ----------------- | ----------- |
| 1 | `./build_apk.sh` | 先编译APK |
| 2 | `./build.sh all` | 生成线刷包 |
| 3 | `./build.sh ota2` | 生成U盘升级包和线刷包 |
编译前先clean下:
![[徐工相关_图片/image_2.png]]
![[徐工相关_图片/image_3.png]]
![[徐工相关_图片/image_4.png]]
![[徐工相关_图片/image_5.png]]
---
## 三、系统属性配置
### 3.1 ro.product.xg.hardware
| 值 | 说明 |
|----|------|
| 1 | 高配 |
| 2 | 低配 |
![[徐工相关_图片/image_6.png]]
![[徐工相关_图片/image_7.png]]
![[徐工相关_图片/image_8.png]]
---
### 3.2 ro.product.xg.device.type
为上层应用获取简便,新增属性:
| 值 | 说明 |
|----|------|
| 1 | 低配123 |
| 2 | 低配156 |
| 3 | 高配123 |
| 4 | 高配156 |
![[徐工相关_图片/image_9.png]]
固件路径:
高配路径:Z:\znzc\034_xcmg\x9hp_xcmg\androd10_x9hp_ptg4.3p3\android10\device\semidrive\x9hp\x9hp_ms\rg440_sdk
Y:\xugong_high\androd10_x9hp_ptg4.3p3\android10\device\semidrive\x9hp\x9hp_ms\rg440_sdk
低配路径:Z:\znzc\034_xcmg\x9m_xcmg\androd10_x9hp_ptg4.3p3\android10\device\semidrive\x9hp\x9hp_ms\rg440_sdk
Y:\xugong_low\androd10_x9hp_ptg4.3p3\android10\device\semidrive\x9hp\x9hp_ms\rg440_sdk
语音替换路径:   Z:\znzc\034_xcmg\x9hp_xcmg\androd10_x9hp_ptg4.3p3\android10\vendor\yaxon
高配路径:Z:\znzc\034_xcmg\x9hp_xcmg\androd10_x9hp_ptg4.3p3\android10\vendor\yaxon\realtek_rn440\bluetooth
Y:\xugong_high\androd10_x9hp_ptg4.3p3\android10\vendor\yaxon\realtek_rn440\bluetooth
低配路径:Z:\znzc\034_xcmg\x9m_xcmg\androd10_x9hp_ptg4.3p3\android10\vendor\yaxon\realtek_rn440\bluetooth
@@ -0,0 +1,338 @@
# 蓝牙模块 Git 提交记录汇总
> 统计时间: 2024-01-01 ~ 2026-05-06
> 提交总数: 230条
> 涉及模块: YX_BlueTooth, YX_BluetoothSDK
---
## 一、语音与蓝牙交互 (35条)
### 1.1 语音控制蓝牙功能
| 日期 | Commit | 描述 |
| ---------- | --------- | ------------------------ |
| 2026-03-27 | 7e9d994a8 | 修改语音蓝牙拒接逻辑 |
| 2026-03-27 | c0dc00a8b | 蓝牙来电,语音拒接不了问题 |
| 2026-03-24 | 0c7512035 | 新增蓝牙通讯录共享功能 |
| 2026-03-11 | e362ac75d | 同步给语音蓝牙通讯录和通话记录页面打开和关闭状态 |
| 2026-02-11 | 5fc8c14cb | 新增语音蓝牙查看通讯录和通话记录跳转 |
| 2024-03-11 | 8faaffd29 | 增加通过语音控制蓝牙相关功能 |
| 2024-03-08 | ac659eabb | 实现语音对蓝牙音乐的控制 |
| 2024-04-07 | 7cead2675 | 增加语音对蓝牙通讯录同步开关进行控制 |
### 1.2 语音与蓝牙音乐交互
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-03-23 | 22b19dc21 | 修改蓝牙音乐播放没响应问题 |
| 2026-03-17 | ac6575de4 | 新增亿连播放音乐的时候请求蓝牙音频焦点 |
| 2024-10-22 | c258164fd | 修复蓝牙音乐播放过程中,唤醒语音,播放按钮多跳变 |
| 2024-05-08 | d93dd2ede | 修复接听电话挂断后语音唤醒不了 |
| 2024-05-07 | 0a54a8f97 | 修复语音与蓝牙音乐混响的问题 |
| 2024-04-24 | 8b0aa26ba | 修复蓝牙音乐被语音连续打断两次后,停止播放 |
| 2024-08-30 | 2b0735476 | 修改USB音乐和蓝牙音乐传给同行者播放状态有误 |
| 2024-05-21 | 05136e9c3 | 提交语音音乐指令常量修改 |
| 2024-05-21 | c0a852f9a | 去掉无用的媒体指令常量 |
| 2024-04-24 | fd83a2721 | 去掉废弃的语音函数 |
### 1.3 电话状态同步给语音
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-09-25 | 9625bff28 | 拨打电话的状态同步给语音 |
| 2024-03-11 | d7cfc38a6 | 增加电话信息同步给语音 |
| 2024-03-11 | f8317495b | 同步电话拒接信息给语音 |
| 2024-03-11 | 4eed44177 | 蓝牙连接状态通知给语音 |
---
## 二、蓝牙音乐功能 (25条)
### 2.1 音乐播放状态
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-03-25 | 0290c5a84 | 蓝牙音乐新增倒车暂停播放 |
| 2025-12-16 | 916e700c1 | 修复强制杀死蓝牙音乐应用后,launcher播放中状态没改变 |
| 2025-12-24 | 6a3d81c06 | 优化蓝牙音乐逻辑 |
| 2025-03-21 | 3135c7b4d | 重启车机后自动连接,蓝牙音乐界面显示播放状态,实际没播放 |
| 2025-02-24 | 15ba71006 | 播放蓝牙音乐中,电源OFF→ON后,界面显示播放但实际没播放 |
| 2025-02-14 | fb76ce90e | 电源OFF→ON后,再次播放音乐,声音变小 |
| 2025-02-05 | ae6337f0c | 车辆下电再上电后,音乐卡片播放按钮无法操作 |
| 2024-04-15 | ccf44711b | 修复视频播放被蓝牙音乐暂停,点击未恢复播放 |
| 2024-04-08 | 9bcd8929d | 修复手机播放音乐时连接蓝牙蓝牙音乐无声 |
| 2024-03-19 | 82572176f | 重启自动连接后,手机播放,车机显示播放然后暂停 |
### 2.2 音乐投屏与仪表
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-02-20 | e45e30069 | 修复切换蓝牙音乐和USB音乐时,仪表投屏闪烁 |
| 2023-10-08 | 8a749b570 | 修改专辑图片不为空时才传数据给投屏 |
| 2023-08-11 | 99f9fd96a | 数据中心添加蓝牙专辑图片及歌曲进度,供投屏使用 |
### 2.3 音乐焦点与控制
| 日期 | Commit | 描述 |
|------|--------|------|
| 2023-09-27 | daa5146ff | 修改蓝牙音乐不申请焦点前,小控件不显示播放信息 |
| 2023-08-25 | 58a661adb | 蓝牙音乐添加静音设置功能 |
| 2023-08-17 | b42455a51 | 蓝牙音乐添加方控控制 |
| 2023-08-11 | 6c58e6ed3 | 添加蓝牙音频流接口 |
| 2024-05-24 | 39dbd133c | 过滤掉蓝牙音乐 Not Provided |
---
## 三、蓝牙电话功能 (40条)
### 3.1 来电/去电显示
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-05 | 0fb28752a | 优化网络电话功能 |
| 2025-11-05 | 6af47aa37 | 优化网络电话判断逻辑 |
| 2025-10-17 | 184c93201 | 更新蓝牙电话应用外float显示功能 |
| 2025-10-16 | 0fd9c73ec | 优化电话来电不可以切换音频通道 |
| 2025-10-09 | 5a4a9e6c0 | 更新电话拨打页面缩小UI及通讯录点击拨打逻辑 |
| 2025-09-30 | c4018d7e9 | 优化更新应用外电话弹出框功能 |
| 2024-05-21 | 4974f4cb1 | 修复蓝牙去电流程,功能示意图标处没显示当前连接蓝牙名称 |
| 2024-04-20 | 231ae2bb6 | 修复拨打微信电话时提示语显示不完整 |
| 2024-03-25 | 649577900 | 修复语音未及时同步通讯录的问题 |
| 2023-12-23 | d9cd419e2 | 修复第三方来电时,不显示通话时间(全屏) |
| 2023-12-23 | 29ac3327e | 修复来电时,左侧图不显示当前连接蓝牙名称 |
| 2023-12-19 | 55862fb86 | 修复蓝牙电话去电未接通UI错误 |
### 3.2 电话通话状态
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-09-05 | b4452bb57 | 修复蓝牙电话搜索名字结果,点击后应先显示号码 |
| 2024-03-15 | 8d6b61675 | 解决导航过程中方向盘按键暂停蓝牙音乐,导航播完后自动继续播放 |
| 2024-03-19 | 810509299 | 修复蓝牙通话记录同步时,圈圈一卡一卡的问题 |
| 2024-02-23 | ee6f6780c | 修复蓝牙通话时点击非通话悬浮框区域也跳转到通话页面 |
| 2024-01-25 | a5fc79803 | 修复连接蓝牙,呼出电话,方控ok按键没有挂断电话 |
| 2024-01-17 | b9f71dc21 | 修复通话记录中不应该记录网络通话记录的问题 |
| 2023-12-13 | 18a297d93 | 修复蓝牙电话通话中,acc off,手机还显示蓝牙连接状态 |
| 2023-11-11 | 4d72baadb | 修复蓝牙电话去电仪表没有投屏效果 |
| 2023-11-07 | 96a1efe59 | 修复蓝牙电话挂断后挂断键没有置灰 |
| 2023-10-08 | 0e6124187 | 传输蓝牙电话结束的状态给投屏 |
| 2023-10-08 | 753ff3f7f | 把电话投屏数据通过json格式进行传输 |
### 3.3 电话投屏接口
| 日期 | Commit | 描述 |
|------|--------|------|
| 2023-08-22 | fc81093dd | 蓝牙电话投屏接口添加号码归属地及通话时长功能 |
| 2023-08-11 | 803a170df | 添加蓝牙电话各状态至数据中心供投屏使用 |
| 2023-08-14 | d19784227 | 联调蓝牙电话投屏状态错误问题 |
---
## 四、通讯录同步 (30条)
### 4.1 同步流程优化
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-07-21 | 6421f38b7 | 联系人和通话记录同步完成后,修正isPhoneBookPulling为false |
| 2024-04-10 | 9deeb99c4 | 修复提示同步失败后,蓝牙通话记录一直在加载同步 |
| 2024-04-04 | faaa29039 | 修复同步通讯录完成后,再次点击同步,界面卡在正在同步界面 |
| 2024-03-30 | 271a40273 | 修复点击拨号键,界面闪现同步通讯录画面的问题 |
| 2024-03-25 | 649577900 | 修复语音未及时同步通讯录的问题 |
| 2024-03-12 | a731df32b | 修复系统设置界面开启自动同步通讯录开关后,蓝牙电话记录自动同步太慢 |
| 2024-01-29 | 2b2f2e130 | 修复手机通话记录为空时,中控显示通话记录未同步的问题 |
| 2024-01-25 | 8d541765c | 修复偶现同步联系人和通话记录后缺少一个通话记录的问题 |
| 2024-01-22 | 6155a30fc | 修复联系人没有存电话号码,车机端不能同步该联系人 |
| 2024-01-04 | 2c95dca9c | 修复在同步联系人过程中,同步时间很久的问题 |
| 2024-01-03 | 0354c65d3 | 修复连接蓝牙后未同步通话记录下用车机拨号,通话记录消失 |
| 2023-12-20 | 9dcbac50b | 修复蓝牙关闭自动同步,还能同步成功的问题 |
| 2023-08-22 | a7e216453 | 通讯录盖尾限制2000条 |
| 2023-08-17 | c02339467 | 通讯录限制在2500条,超出部分去除 |
### 4.2 通讯录UI与交互
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-03-21 | d2309fb29 | 修复配对完蓝牙,切换到通讯录界面,界面显示异常有重叠 |
| 2025-10-13 | 0680db5b1 | 优化通讯录个数UI显示 |
| 2025-10-16 | 0b947f3a4 | 更新蓝牙通讯录默认选中第一个 |
| 2025-10-08 | 00415fb2d | 优化蓝牙通讯录页面连接断开逻辑 |
| 2024-06-11 | 4c4092983 | 修复点击刷新按钮右侧会闪过联系人详情页 |
| 2024-05-21 | 5b73c1cd0 | 修复多次点击通讯录刷新按钮,会跳出通讯录未同步 |
| 2024-04-22 | 4e4c7d2f6 | 修复蓝牙电话滑动联系人列表,联系人详情页依然显示 |
| 2024-04-19 | a7fb60fe3 | 修复蓝牙电话通讯录显示有中文 |
| 2023-12-25 | 159ed069b | 修复连接蓝牙,点击最近使用的电话进入通讯录页面,加载不出来 |
| 2023-12-15 | 3304c57a5 | 修复未连接蓝牙时,无法切换到通讯录页面 |
| 2023-12-14 | 3f389a05b | 修复蓝牙电话-通讯录-字母查询-通讯录中没有的字母,无反应 |
| 2023-08-11 | 409bc14f6 | 按照新的设计要求修改通讯录列表及添加通讯录搜索功能 |
| 2023-07-26 | 991c8eea3 | 蓝牙添加通讯录和通话记录功能 |
---
## 五、蓝牙连接状态管理 (20条)
### 5.1 ACC电源状态处理
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-20 | 295607af2 | 修改acc开关,蓝牙不做断开或重连操作,保持原状态 |
| 2024-01-15 | 3ee792ae9 | 修复负一屏点击整车下电后,踩刹车上电,蓝牙没有自动连接 |
| 2024-01-09 | dd6d12c31 | 修复蓝牙上电后第一次进入蓝牙音乐界面时,蓝牙音乐会暂停一下 |
| 2024-01-02 | 9258088f0 | 修复上电后,手机端播放蓝牙音乐,从主界面进入后蓝牙音乐暂停 |
| 2023-12-07 | fdfe2c820 | 修复播放蓝牙音乐过程中断开ACC再重新连接,进度条显示00:00 |
| 2023-12-13 | 18a297d93 | 修复蓝牙电话通话中,acc off,手机还显示蓝牙连接状态 |
### 5.2 蓝牙可见性与配对
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-01-28 | a0e15fe92 | 进入蓝牙设置页面,手机就搜不到车机的蓝牙问题 |
| 2026-01-28 | c1a490f80 | 修改蓝牙可见性 |
| 2025-12-29 | 6d491ec73 | 新增蓝牙配对设备最大数量限制为10;配对请求5s未响应自动拒绝 |
| 2024-01-12 | d89e68cd7 | 修复连接一个蓝牙后,另一个手机配对成功,通讯录界面蓝牙名称没更新 |
| 2023-12-21 | fbf1637bb | 修复打开蓝牙然后关闭开放蓝牙监测,手机还能搜索到车机 |
| 2023-12-08 | a815b4bef | 修复车机连接手机的话,蓝牙连接和手机配对成功,连接失败 |
| 2023-10-19 | 60c5779cd | 修复蓝牙可见性不可见性修改 |
### 5.3 蓝牙名称管理
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-09-05 | e238b8d4c | 修改默认蓝牙和WiFi名称 |
| 2024-01-05 | 6a75240d7 | 连接时实时刷新蓝牙名称 |
| 2023-12-21 | 42b72c8f2 | 修复写入新的vin码,蓝牙名称还显示原来的 |
| 2023-11-14 | 5f3d5e800 | 修复蓝牙名称恢复出厂设置显示错误问题 |
| 2023-09-27 | 538f326a6 | 蓝牙名称修改成vin后6位 |
| 2023-08-17 | a5d1f78d2 | 修复蓝牙名称加vin码默认名称 |
---
## 六、UI适配与优化 (35条)
### 6.1 深色模式适配
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-21 | 49a6ca185 | 修改深色模式状态监听模式 |
| 2025-11-14 | 2175a4bd8 | 修改联系人字母显示dark模式颜色 |
| 2025-11-14 | f7999efe6 | 添加深色模式适配功能 |
### 6.2 右舵/国际化适配
| 日期 | Commit | 描述 |
|------|--------|------|
| 2026-01-17 | 9c43ac6ad | 添加适配12.3寸屏幕功能 |
| 2025-02-26 | 83fb691b6 | 修改蓝牙电话右舵UI效果 |
| 2025-02-25 | 8ef5fb690 | 蓝牙新增右舵项目布局资源 |
| 2024-03-11 | 1b292b8ed | 新增阿拉伯语 |
| 2023-08-15 | afb4eb70a | 修改通话记录两天以上,一周以内显示星期 |
### 6.3 界面UI更新
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-17 | 6c6a39f36 | 优化蓝牙电话背景图片以及搜索功能 |
| 2025-11-04 | e3413202b | 修改蓝牙连接按键跳转到最新的页面 |
| 2025-11-04 | 6b0c946ad | 更新通讯录文案为联系人 |
| 2025-10-16 | 449e78098 | 修改拨号按键不可用时,透明度为0.6 |
| 2025-10-16 | 5f97d7f07 | 根据最新UE调整蓝牙电话页面显示 |
| 2025-10-15 | 8eb033ea3 | 优化拨号按键板功能 |
| 2025-10-15 | 118b7c74c | 优化按键板搜索同时搜索通话记录和通讯录 |
| 2025-10-08 | 85b3fc57e | 修改通话记录时间显示格式 |
| 2025-09-28 | be6c7aa5e | 更新蓝牙UI |
| 2025-09-26 | 5fedd8aea | 更新蓝牙UI相关功能 |
| 2025-07-21 | 3ff34003d | 修改空联系人提示UI位置错误 |
| 2024-05-24 | 738188f67 | 修复蓝牙来电显示界面,文言重叠 |
| 2024-03-21 | 13bc1ecdf | 修复了蓝牙文字重叠的问题 |
| 2023-12-21 | a3b1ba89f | 修复未连接蓝牙时通话界面和UI不一致 |
| 2023-12-04 | dea35c78f | 修复蓝牙通话记录没有滚动条,与UI设计不符 |
| 2023-10-31 | 5e6a03a0b | 蓝牙电话通话页面V1.3UI适配修改 |
| 2023-10-27 | d9acac409 | 蓝牙通话记录和通讯录V1.3UI修改适配 |
| 2023-08-31 | d893ecdf4 | 修复蓝牙相关的走查UI问题 |
---
## 七、国际化/翻译 (15条)
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-07-11 | 07d05e2c0 | 修复蓝牙电话翻译显示格式错误 |
| 2024-07-05 | a418fc475 | 修复未找到"%s"的相关结果的英文和需求对应不上 |
| 2024-05-13 | b1d5b4b3b | 蓝牙翻译文本更新 |
| 2024-04-18 | 67c6c9aa6 | 修复蓝牙(未找到"%s"的相关结果)的英文和需求对应不上 |
| 2024-03-28 | 9dfce2f80 | 修复通话名称Dial和Deepway多语言翻译不一致 |
| 2023-11-22 | c2d68a84d | 蓝牙数字统一设置DINGPRO格式 |
| 2023-07-19 | da0d81c9f | 参照UE调整通讯录和通话记录 |
---
## 八、代码优化与重构 (15条)
### 8.1 工具类统一
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-12-24 | 653c4b6b3 | 统一使用YX_log工具打印 |
| 2025-12-17 | 48c6f2d2d | 修改toast调用,统一调用自定义toast |
| 2025-12-17 | 0f688b8fc | 不用系统toast,添加自定义toast |
| 2024-03-25 | b232e10ad | 优化日志打印 |
### 8.2 构建优化
| 日期 | Commit | 描述 |
|------|--------|------|
| 2025-11-05 | 7ebb3b171 | 优化sdk build.gradle |
| 2025-11-05 | b5b840b98 | 添加SharedPrefsManager.init |
| 2024-12-24 | abf1034fd | 删除无效资源,png图片转webp,优化apk体积 |
| 2024-12-25 | e39ee2e1e | 修改打包资源错误导致没有打包到中文资源 |
| 2023-12-12 | 18a297d93 | 去掉依赖库,可以通过脚本编译release的apk |
| 2024-03-05 | 6447ff187 | 优化代码,去掉无用的资源引用 |
---
## 九、Bug修复 (杂项) (15条)
| 日期 | Commit | 描述 |
|------|--------|------|
| 2024-04-18 | 2ffb50935 | 修复出现蓝牙ANR |
| 2024-01-18 | e948b2e69 | 修复麦克风禁用问题 |
| 2024-01-08 | 4e5eb00d7 | 修复monkey crash问题 |
| 2023-11-02 | f5ef447c6 | 修复SQLiteCursor未关闭引起的异常 |
| 2023-11-23 | 0bc5caee4 | 修复跑monkey时出现蓝牙ANR问题 |
| 2023-10-13 | b61120c85 | 处理专辑封面闪烁问题 |
| 2023-10-14 | b7cc4717a | 修复蓝牙电话切换音源,仪表会出现消失一下 |
| 2023-10-20 | 8040146aa | 解决偶现断连后,状态栏不显示蓝牙图标 |
| 2023-09-21 | ebf265af1 | 处理蓝牙电话和蓝牙音乐方向盘控制功能 |
| 2023-09-21 | c6784f0ef | 处理空指针报错 |
| 2023-09-06 | d2fd3d9be | 修复蓝牙跑monkey崩溃问题 |
| 2023-08-24 | 6c58e6ed3 | 添加蓝牙音频流接口 |
---
## 十、关键修改点总结 (便于下次修改)
### 10.1 语音交互入口类
- `VoiceEventHelper.java` - 语音事件处理,所有语音蓝牙操作入口
- `MediaProtocol.java` - 媒体协议,蓝牙音乐状态同步
- `VoiceNewProtocolConst.java` - TXZ语音协议常量
### 10.2 蓝牙音乐核心类
- `BluetoothHfpClient.java` - HFP协议,电话状态
- 音频焦点管理 - 与语音、导航、视频的焦点竞争
- 投屏数据格式 - JSON格式传输给仪表
### 10.3 通讯录同步核心类
- `BluetoothPbapClient.java` - PBAP协议入口
- `BtContactsManager.java` - 通讯录管理
- `isPhoneBookPulling` - 同步状态标志,需在完成后置false
- 同步限制: 通讯录2500条/2000条盖尾,通话记录50条
### 10.4 ACC电源处理要点
- ACC OFF时不主动断开蓝牙,保持原状态
- ACC ON后恢复播放状态需校验实际播放状态
- 下电再上电后播放按钮状态同步
### 10.5 UI适配要点
- 深色模式: 联系人字母颜色、监听模式
- 右舵项目: 需要单独布局资源
- 12.3寸屏幕适配
### 10.6 国际化要点
- 阿拉伯语已支持
- 通话记录时间格式: "昨天"根据语言切换
- 数字格式统一DINGPRO格式
---
## 十一、常见问题快速定位
| 问题类型 | 关键类/方法 | 日志关键字 |
| ------- | ------------------------------------------ | ------------------------------------------ |
| 通讯录不同步 | BluetoothPbapClient.phoneBookPullRequest() | BtContactsManager, onContactsPullCompleted |
| 音乐状态异常 | 音频焦点处理 | AudioManager, onFocusChange |
| 语音拒接失败 | VoiceEventHelper拒接逻辑 | 蓝牙拒接, HFP |
| ACC状态处理 | 电源状态监听 | ACC_OFF, ACC_ON |
| 投屏数据 | JSON数据传输 | 投屏, DataCenter |
| ANR问题 | 主线程阻塞 | ANR, SQLiteCursor |
@@ -0,0 +1,544 @@
# 蓝牙模块开发熟悉手册
> 整理日期:2026-07-06
> 整理范围:`F:\zdCode\xugong` 当前代码、`agent.md` 历史线索、蓝牙目录现有 Markdown、docx、xlsx、PDF 可抽取文本。
> 目的:帮助新接手开发者快速理解徐工项目蓝牙模块的架构、主流程、代码入口、日志排查路径和历史高风险点。
## 一句话结论
徐工蓝牙不是单一 APK 功能,而是一条跨 `YX_BlueTooth`、`YX_BluetoothSDK`、`YX_Music`、`YX_Voice`、`YX_Launcher`、`YX_SystemUI`、Android Frameworks、系统 Bluetooth 应用、顾凯 SDK/芯片的链路。开发时优先从 `YX_BluetoothSDK` 看协议封装,再看上层应用如何订阅 SDK 回调和 `DataManager` 数据。
## 1. 模块地图
| 模块 | 定位 | 重点入口 |
| ------------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| `YX_BluetoothSDK` | 蓝牙核心 SDK,封装 HFP/A2DP/AVRCP/PBAP/MAP、数据库、状态推送、语音同步 | `manager`、`modules`、`platform`、`audio`、`database` |
| `YX_BlueTooth` | 蓝牙电话主业务应用,联系人、通话记录、拨号、通话页、常驻服务、widget | `MainActivity`、`ContactsFragment`、`CallLogsFragment`、`BluetoothPhoneActivity`、`BluetoothService` |
| `YX_Music` | 蓝牙音乐页面所在应用,负责蓝牙音乐页面 UI、播放控制、封面显示 | `BluetoothMusicFragment`、`BluetoothMusicViewModel` |
| `YX_Voice` | 语音控制蓝牙开关、电话、音乐、通讯录/通话记录状态同步 | `VoiceProtocol`、`BlueToothHelper`、`BluetoothMonitorReceiver`、`VoiceEventHelper` |
| `YX_Launcher` | 首页音乐卡片、蓝牙音乐信息展示和媒体按键入口 | `WidgetsMedia`、`DisplayShareViewModel`、`Id3Info` |
| `YX_SystemUI` | 状态栏/导航媒体区、通话状态显示、系统级入口 | `MediaControlView`、`VirtualManager`、SystemUI 蓝牙封装类 |
| `YX_CarControl` | 配对弹窗、自动同步开关等外围联动 | 注意不要重复初始化 PBAP 联系人同步 |
| Frameworks / Bluetooth Process | Android 蓝牙 API、Binder、系统 Bluetooth app、JNI、协议栈 | `android.bluetooth.*`、`packages/apps/Bluetooth` |
| 顾凯 SDK/芯片 | 蓝牙协议栈供应商实现和 HCI 交互 | 底层联调、btsnoop/顾凯日志 |
## 2. 总体分层
PDF《蓝牙框架原理以及通讯数据交互流程》给出的系统层级可落到项目代码中:
```mermaid
flowchart TD
A["上层应用<br/>YX_BlueTooth / YX_Music / YX_Voice / Launcher / SystemUI"] --> B["YX_BluetoothSDK<br/>ControlManager / Manager / Platform Client"]
B --> C["Frameworks API<br/>android.bluetooth.*"]
C --> D["Bluetooth Process<br/>packages/apps/Bluetooth"]
D --> E["JNI<br/>packages/apps/Bluetooth/jni"]
E --> F["AOSP 蓝牙协议栈<br/>system/bt"]
F --> G["顾凯 SDK / HAL / HIDL"]
G --> H["蓝牙芯片"]
```
反向回调通常是:芯片/协议栈事件 -> JNI -> 系统 Bluetooth 服务 -> Frameworks 广播/API -> `YX_BluetoothSDK platform` 接收 -> `modules` 整理状态 -> 上层 UI、语音、Launcher/SystemUI。
## 3. SDK 内部分层
`YX_BluetoothSDK` 是最值得先读的模块,建议按下面顺序看:
| 层 | 包路径 | 职责 |
|---|---|---|
| 对外控制层 | `com.yaxon.bluetoothsdk.manager` | 单例入口,给 App/语音/音乐模块调用,例如 `BtPhoneControlManager`、`BtContactsControlManager`、`BtMusicControlManager` |
| 业务模块层 | `com.yaxon.bluetoothsdk.modules` | 真正组织业务状态机、监听、数据库、语音/投屏同步,例如 `BtContactsManager`、`BtPhoneManager`、`BtMusicManager`、`BtInfoPushManager` |
| 平台适配层 | `com.yaxon.bluetoothsdk.platform` | 封装 Android 蓝牙 API 和广播,例如 `BluetoothHfpClient`、`BluetoothPbapClient`、`BluetoothAvrcpClient`、`BluetoothConnectListenerClient` |
| 音频层 | `com.yaxon.bluetoothsdk.audio` | 蓝牙音乐音频焦点、A2DP mute/unmute、MediaSession 媒体键 |
| 数据层 | `com.yaxon.bluetoothsdk.database` | 联系人、通话记录、配对设备、收藏等本地 SQLite 数据 |
| 工具层 | `utils` | 系统联系人/通话记录读取、号码处理、日志、权限等 |
开发时一般从 `ControlManager` 找入口,然后下钻到 `modules`,再看 `platform` 是调用系统 API 还是接收广播。
## 4. 启动与初始化
蓝牙电话应用开机常驻入口:
```mermaid
flowchart LR
BootReceiver["YX_BlueTooth BootReceiver"] --> Service["BluetoothService.onCreate"]
Service --> SDK["初始化 BtPhone/BtContacts/BtCallRecords 等控制器"]
Main["MainActivity.onCreate/initData"] --> VM["SettingViewModel / Fragment ViewModel"]
VM --> Managers["BtSettingControlManager / BtContactsControlManager / BtPhoneControlManager"]
```
关键文件:
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/receiver/BootReceiver.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/service/BluetoothService.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/MainActivity.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtSettingManager.java`
`BtSettingManager.init()` 会初始化 `BluetoothHfpClient`、`BluetoothA2dpSinkClient`、`BluetoothDeviceClient`、`BluetoothMapConnectClient`、`BluetoothConnectListenerClient`,并在蓝牙已打开时初始化已配对设备列表。它还会延迟检查已连接设备,并把当前活跃蓝牙 MAC 同步给语音模块。
## 5. 蓝牙开关、扫描、配对、连接
### 5.1 状态来源
蓝牙开关状态来自 `BluetoothAdapter.ACTION_STATE_CHANGED`,SDK 侧由 `BluetoothConnectListenerClient.setBluetoothSwitchState()` 处理:
| 状态值 | Android 常量 | 含义 |
|---|---|---|
| `10` | `BluetoothAdapter.STATE_OFF` | 蓝牙已关闭 |
| `11` | `BluetoothAdapter.STATE_TURNING_ON` | 正在打开 |
| `12` | `BluetoothAdapter.STATE_ON` | 蓝牙已打开 |
| `13` | `BluetoothAdapter.STATE_TURNING_OFF` | 正在关闭 |
注意:`BluetoothAdapter.enable()` / `disable()` 返回 `true` 只代表请求被接受,不代表最终状态已切到 `ON/OFF`。语音场景必须看后续 `blueState -> 12` 或 `blueState -> 10`。
### 5.2 连接状态
连接状态由 `BluetoothConnectListenerClient` 聚合 HFP、A2DP、A2DP Sink 等 profile 广播,再通知各模块:
- `notifyBluetoothConnected()`:写入连接 MAC、连接名、连接状态,并回调 `onConnect`
- `notifyBluetoothDisconnect()`:清理蓝牙音乐信息,写入断开状态,并回调 `onDisconnect`
- `setBondState()`:处理配对状态
- `setPairingRequest()`:处理配对请求
### 5.3 HFP 与 A2DP 的关系
交接说明里特别强调:蓝牙连接包含 `hfp`(电话)和 `a2dp`(音乐/音频连接);通讯录同步需要再连 `pbap`,同步完成后需要断开 `pbap`。
## 6. 通讯录与通话记录同步(PBAP)
PBAP 是 Phone Book Access Profile,用于从手机拉取通讯录和通话记录。本项目中通讯录和通话记录共享一次 PBAP 同步窗口:联系人完成、通话记录完成后,才认为本轮电话本下载整体完成,并主动断开 PBAP,避免阻塞和卡死。
### 6.1 主流程
```mermaid
sequenceDiagram
participant UI as ContactsFragment/CallLogsFragment
participant CM as BtContactsControlManager
participant M as BtContactsManager
participant P as BluetoothPbapClient
participant SYS as Android PBAP/System Provider
participant DB as DataContactsHelper/DataCallRecordHelper
participant V as ViewModel/Voice
UI->>CM: phoneBookPullRequest(context)
CM->>M: phoneBookPullRequest(context)
M->>P: connectPbap 或 phoneBookPullRequest
P->>SYS: 删除系统 Contacts/CallLog 缓存
P->>SYS: 发送 Intent.ACTION_USER_UNLOCKED
SYS-->>P: contacts.download.complete
P-->>M: onContactsPullCompleted()
M->>SYS: ContactUtils.getAllContacts()
M->>DB: deleteContacts + saveContactsList
M-->>V: onContactsPhoneBook(list)
SYS-->>P: calllogs.download.complete
P-->>CallRecords: onCallLogsPullCompleted()
CallRecords->>SYS: CallLogUtils.getCallLogList()
CallRecords->>DB: clearCallRecord + saveCallRecordList
CallRecords-->>V: onCallRecords(list)
M->>P: contacts + calllogs 都完成后 disconnectPbap()
```
### 6.2 关键代码入口
| 场景 | 文件/方法 |
|---|---|
| UI 点击同步联系人 | `ContactsFragment.phoneBookPullRequest()` |
| UI 点击/通话页触发同步 | `CallLogsFragment` -> `CallHistoryViewModel.phoneBookPullRequest()` |
| SDK 对外入口 | `BtContactsControlManager.phoneBookPullRequest()` |
| 同步状态、权限、自动同步 | `BtContactsManager.phoneBookPullRequest()`、`isAutoSync()`、`checkContactsPermissionGranted()` |
| PBAP 连接和系统广播 | `BluetoothPbapClient.connectPbap()`、`phoneBookPullRequest()`、`PbapClientReceiver` |
| 联系人读取与保存 | `ContactUtils.getAllContacts()`、`DataContactsHelper.savePhoneBookList()` |
| 通话记录读取与保存 | `CallLogUtils.getCallLogList()`、`BtCallRecordsManager.loadCallsLog()`、`DataCallRecordHelper.saveCallRecordList()` |
| UI 状态判断 | `ContactsFragment.syncPhoneBookSynchronized()`、`CallLogsFragment.notifyPhoneRecordList()` |
### 6.3 关键状态位
| 状态 | 说明 | 风险 |
|---|---|---|
| `isPhoneBookPulling` | PBAP 本轮同步是否正在进行 | UI loading 和重复点击判断要优先看它 |
| `isContactsPullCompeted` / `getContactLoadWonType()` | SDK 本轮联系人读取、写库、回调是否完成 | 比 `isPhoneBookSynchronized()` 更能代表本轮同步完成 |
| `isPhoneBookSynchronized()` | 本地 DB 当前设备是否已有至少一条联系人 | 只能代表“本地有数据”,不代表本轮 PBAP 完成 |
| `isCallRecordUpdated()` | 本地 DB 当前设备是否已有通话记录 | 通话记录页显示和 loading 判断会用到 |
### 6.4 已验证历史结论
- 清空蓝牙本地 SQLite 表不是通讯录同步失败的直接原因;正常清空后 PBAP 会重新写入。
- `isPhoneBookSynchronized()` 只表示 DB 有联系人,不能作为“本轮同步完成”的唯一条件。
- 曾出现 `YX_CarControl` 和 `YX_BlueTooth` 双进程同时初始化 PBAP,导致重复 `phoneBookPullRequest`、重复删写联系人表;当前应统一由 `com.yaxon.bluetooth` 负责 PBAP 同步。
- 联系人同步中应优先显示 loading,避免在旧库数据或第一条联系人写入后提前显示“0 个联系人”或旧列表。
- 通话记录 Adapter 曾因传入同一个 list 导致 `clear()` 自清空,已通过传 `new ArrayList<>(mCallRecordList)` 规避。
### 6.5 排查日志关键字
| 阶段 | 关键字 |
|---|---|
| 请求开始 | `BluetoothPbapClient: phoneBookPullRequest` |
| PBAP 连接变化 | `PbapClientReceiver device name`、`prevState`、`state` |
| 联系人下载完成 | `BtContactsManager: onContactsPullCompleted` |
| 联系人读取完成 | `contacts read completed size =` |
| 联系人 UI 收到 | `ContactListViewModel: onContactsPhoneBook =` |
| 通话记录读取 | `mCallRecordBeanList size =` |
| 通话记录保存 | `saveCallRecordList size =` |
| 通话记录 UI | `CallLogsFragment: notifyPhoneRecordList list ==` |
| 失败 | `onContactsPullFailed`、`contacts synchronization failed`、`通讯录同步失败` |
## 7. 蓝牙电话(HFP)
HFP 是 Hands-Free Profile,用于拨号、接听、拒接、挂断、通话音频状态等。
### 7.1 电话状态
| 状态值 | 常量 | 说明 |
|---|---|---|
| `0` | `CALL_STATE_ACTIVE` | 通话中 |
| `1` | `CALL_STATE_HELD` | 保持中 |
| `2` | `CALL_STATE_DIALING` | 正在拨号 |
| `3` | `CALL_STATE_ALERTING` | 正在响铃,项目中通常合并到拨号处理 |
| `4` | `CALL_STATE_INCOMING` | 来电 |
| `5` | `CALL_STATE_WAITING` | 第三方等待 |
| `6` | `CALL_STATE_HELD_BY_RESPONSE_AND_HOLD` | 响应并保持,项目中通常合并到保持处理 |
| `7` | `CALL_STATE_TERMINATED` | 挂断 |
### 7.2 拨号调用链
PDF《蓝牙电话交互流程解析》给出的拨号链路,对应当前项目可理解为:
```mermaid
flowchart TD
A["YX_BlueTooth UI / BluetoothService / Voice"] --> B["BtPhoneControlManager.dialing"]
B --> C["BtPhoneManager.dialing"]
C --> D["BluetoothHfpClient.dial"]
D --> E["BluetoothHeadsetClient.dial"]
E --> F["BluetoothHeadsetClientBinder.dial"]
F --> G["HeadsetClientService.dial"]
G --> H["HeadsetClientStateMachine.Connected.dial"]
H --> I["NativeInterface.dial"]
I --> J["com_android_bluetooth_hfpclient.dialNative"]
J --> K["顾凯 SDK / 蓝牙芯片"]
```
接听、拒接、挂断与拨号类似,只是 Frameworks API 分别换成:
- `BluetoothHeadsetClient.acceptCall()`
- `BluetoothHeadsetClient.rejectCall()`
- `BluetoothHeadsetClient.terminateCall()`
### 7.3 状态回调链
```mermaid
sequenceDiagram
participant FW as BluetoothHeadsetClient 广播
participant HFP as BluetoothHfpClient
participant M as BtPhoneManager
participant UI as BluetoothPhoneViewModel/UI/Window
participant Push as BtInfoPushManager
participant Voice as BtVoiceControlManager
FW-->>HFP: ACTION_CALL_CHANGED
HFP->>HFP: getCurrentCalls()
HFP-->>M: OnCallPhoneStateListener.callState(list)
M->>M: 转成 CallingState
M-->>UI: onHfpCallChanged(list)
M->>Push: setPhoneState / setPhoneCallState / setPhoneGroups
M->>Voice: notifyBtInComing / notifyBtCall / notifyBtAccept / notifyBtHangup
```
### 7.4 关键文件
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/manager/BtPhoneControlManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtPhoneManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/BluetoothHfpClient.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/viewmodel/BluetoothPhoneViewModel.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/main/BluetoothPhoneActivity.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/window/TelephoneWindowControl.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/window/ThirdCallingControl.java`
- `YX_BlueTooth/src/main/java/com/yaxon/bluetooth/service/BluetoothService.java`
### 7.5 排查日志关键字
| 问题 | 关键字 |
|---|---|
| 拨号没有发出 | `BluetoothHfpClient: dial phoneNumber`、`start dial phoneNumber` |
| HFP 未连接 | `hfp_phone_protocol_not_connected`、`isHfpConnect disconnect` |
| 来电/去电 UI 不刷新 | `ACTION_CALL_CHANGED`、`clientCallList`、`onHfpCallChanged` |
| 挂断/拒接失败 | `hangupCall`、`rejectCall`、`acceptCall flag` |
| 通话音频异常 | `ACTION_AUDIO_STATE_CHANGED`、`audio connection state`、`connectAudio`、`disconnectAudio` |
## 8. 蓝牙音乐(A2DP/AVRCP/MediaSession)
蓝牙音乐涉及两条线:
- A2DP:音频流链路,决定声音是否从手机到车机。
- AVRCP/MediaBrowser/MediaSession:播放控制、播放状态、歌曲元数据、媒体按键。
### 8.1 主流程
```mermaid
sequenceDiagram
participant UI as YX_Music/BluetoothMusicFragment
participant CM as BtMusicControlManager
participant M as BtMusicManager
participant Audio as BtMusicAudioManager
participant AVRCP as BluetoothAvrcpClient/MediaBrowserClient
participant Push as BtInfoPushManager/DataManager
participant L as Launcher/SystemUI/Voice
UI->>CM: requestAudioFocus/playBtMusic
CM->>M: requestAudioFocus/playBtMusic
M->>Audio: requestAudioFocus()
Audio->>M: setA2dpUnMute / informAudioFocusStateNative=1
M->>AVRCP: play()
AVRCP-->>M: onPlaybackStateChanged / onMetadataChanged
M-->>UI: onPlayMusicState/onPlayMusicInfo
M->>Push: sendMusicInfoToLauncher(BT_MUSIC_INFO)
Push-->>L: ContentObserver/DataManager 读取 BT_MUSIC_INFO
```
### 8.2 关键代码入口
| 场景 | 文件/方法 |
|---|---|
| 蓝牙音乐页面 | `YX_Music/src/music/java/com/yaxon/musicplay/view/music/BluetoothMusicFragment.java` |
| 音乐 ViewModel | `YX_Music/src/main/java/com/yaxon/musicplay/viewmodel/BluetoothMusicViewModel.java` |
| SDK 控制入口 | `BtMusicControlManager.playBtMusic()`、`pauseBtMusic()`、`requestAudioFocus()` |
| 音乐业务状态 | `BtMusicManager.onPlaybackStateChanged()`、`onMetadataChanged()` |
| 音频焦点和媒体键 | `BtMusicAudioManager.requestAudioFocus()`、`onMediaButtonEvent()` |
| AVRCP 控制 | `BluetoothAvrcpClient.play/pause/previous/next()` |
| A2DP 音频状态 | `BluetoothA2dpSinkClient` |
| Launcher/SystemUI 显示 | `BtInfoPushManager.sendMusicInfoToLauncher()`、`DataUtils.BT_MUSIC_INFO` |
### 8.3 焦点与保护逻辑
`BtMusicManager.onPlaybackStateChanged()` 中有一条关键保护:如果蓝牙音乐开始播放,但当前蓝牙音乐不在前台且没有蓝牙音乐焦点,就会 mute A2DP 并 `pauseBtMusic()`。这是为了防止手机端或后台异常播放抢声道,但也会影响 Launcher 首页音乐卡片冷启动首次播放场景。
历史上已经通过 SDK 侧 `BtMusicAudioManager` 的 MediaSession 兜底处理过 Launcher 首次点击:
- MediaSession 在无焦点但蓝牙音乐是当前音源时也可接媒体键。
- 收到 `KEYCODE_MEDIA_PLAY_PAUSE` 后先申请焦点,再 `playBtMusic()`。
- A2DP 连接、元数据、播放状态变化后刷新 MediaSession,避免第一次点击只激活 session、第二次才播放。
### 8.4 封面高风险点
蓝牙音乐封面风险集中在固定路径:
```text
/sdcard/ID3/id3cover.JPEG
```
问题模式:
- 系统/底层复用同一个封面路径。
- 歌曲元数据先到,封面路径后到。
- 文件可能短时间为 0 字节或半写入。
- 空 `albumUrl` 或无效文件可能覆盖当前有效封面。
排查关键字:
- `albumUrl = /sdcard/ID3/id3cover.JPEG`
- `length = 0`
- `Failed to create image decoder`
- `获取bitmap为null`
- `skip invalid album file`
- `loadFileImageToView success`
## 9. 语音与蓝牙交互
### 9.1 语音开关蓝牙
```mermaid
flowchart TD
A["TXZ / VoiceService 请求<br/>openBluetooth / closeBluetooth"] --> B["VoiceProtocol.handleBluetooth"]
B --> C["BlueToothHelper.openBlueToothByVoice / closeBlueToothByVoice"]
C --> D["BluetoothAdapter.enable / disable"]
D --> E["BluetoothMonitorReceiver.ACTION_STATE_CHANGED"]
E --> F["BlueToothHelper.syncBluetoothStatusToTxzByStateChanged"]
F --> G["VoiceEventHelper.notifyBluetoothOpenStaus / notifyBluetoothConnectStaus"]
```
关键文件:
- `YX_Voice/src/main/java/com/yaxon/voice/protocol/VoiceProtocol.java`
- `YX_Voice/src/main/java/com/yaxon/voice/helper/BlueToothHelper.java`
- `YX_Voice/src/main/java/com/yaxon/voice/receiver/BluetoothMonitorReceiver.java`
- `YX_Voice/src/main/java/com/yaxon/voice/helper/VoiceEventHelper.java`
重点:
- `openBlueToothByVoice()` / `closeBlueToothByVoice()` 做了同目标去抖、相反目标保护、有限重试、延迟状态回填。
- `BluetoothMonitorReceiver` 收到 `STATE_ON/OFF` 后立即同步 TXZ,并延迟补发状态。
- 不能以 `enable()/disable()` 返回值作为最终成功依据。
### 9.2 语音电话与通讯录
语音电话/通讯录状态同步主要由 SDK 侧 `BtVoiceManager` 和 `BtVoiceControlManager` 承接:
- 电话来电:`BtPhoneManager` -> `BtVoiceControlManager.notifyBtInComing`
- 去电:`notifyBtCall`
- 接通:`notifyBtAccept`
- 挂断:`notifyBtHangup`
- 联系人同步完成:`BtVoiceManager.syncBtActiveDeviceMac`、`BtVoiceControlManager.notifyBtContactReload`
- 通话记录变化:`BtVoiceManager.notifyCallRecordChange`
## 10. Launcher/SystemUI/投屏联动
### 10.1 数据通道
SDK 通过 `BtInfoPushManager` 写入公共数据,外围模块通过 `ContentObserver` 或 `DataManager.getObject()` 读取。
| 数据 | 写入方 | 读取方 |
|---|---|---|
| `DataUtils.BT_MUSIC_INFO` | `BtInfoPushManager` | `YX_Launcher`、`YX_SystemUI`、`YX_Voice` |
| `DataUtils.BT_INFO` | `BtInfoPushManager` | Launcher/SystemUI/投屏相关 |
| 电话状态、联系人、号码、通话时长 | `BtPhoneManager` -> `BtInfoPushManager` | 仪表、Launcher、SystemUI、语音 |
### 10.2 常见现象
- Launcher 卡片能显示蓝牙音乐元数据,不代表按钮控制一定会走蓝牙音乐控制链路;按钮可能只是全局媒体键。
- SystemUI/Launcher 读取封面路径时要防空路径、0 字节、解码失败、同一首歌空封面覆盖已有封面。
- 电话投屏状态依赖 `BtInfoPushManager.setPhoneState()`、`setPhoneCallState()`、`setPhoneGroups()`。
## 11. 数据存储
| 数据 | 类 | 说明 |
|---|---|---|
| 联系人 | `DataContactsHelper` | 按蓝牙设备 MAC 存储联系人 |
| 通话记录 | `DataCallRecordHelper` | 按蓝牙设备 MAC 存储通话记录,过滤网络电话等异常号码 |
| 已配对设备 | `DataPairedHelper` | 配对设备列表 |
| 收藏 | `DataCollectionHelper` | 收藏联系人/号码 |
| 系统 Provider | `ContactUtils`、`CallLogUtils` | PBAP 下载完成后从系统联系人/通话记录 Provider 读取 |
| 跨模块状态 | `DataManager`、`YxIPC` | 音乐、电话、语音、Launcher/SystemUI 状态同步 |
开发时要分清:
- 系统联系人/通话记录 Provider 是 PBAP 下载中间数据。
- SDK 自己的 SQLite 是业务最终缓存。
- `DataManager` 是跨应用状态和展示数据的同步通道。
## 12. 常见问题排查索引
| 问题 | 先看模块 | 关键日志 |
|---|---|---|
| 蓝牙打不开/关不掉,语音已回复成功 | `YX_Voice`、`BluetoothConnectListenerClient` | `openBlueTooth`、`closeBlueTooth`、`blueState -> 10/11/12/13`、`syncBluetoothStatusToTxz` |
| 手机能连但电话不可用 | HFP | `hfp onServiceConnected`、`updateHfpConnectedState`、`isHfpConnect` |
| 手机能播但车机无声 | A2DP/焦点 | `bluetooth music requestAudioFocus`、`setA2dpUnMute`、`informAudioFocusStateNative` |
| Launcher 首页点播放无效 | MediaSession/焦点 | `WidgetMedia: KEYCODE_MEDIA_PLAY_PAUSE`、`MediaSessionService: Sending KeyEvent`、`BtMusicAudioManager: onMediaButtonEvent` |
| 通讯录不同步 | PBAP | `phoneBookPullRequest`、`onContactsPullCompleted`、`contacts read completed size`、`onContactsPhoneBook` |
| 通话记录为空 | PBAP/Adapter/UI | `mCallRecordBeanList size`、`saveCallRecordList size`、`notifyPhoneRecordList list ==` |
| 封面闪烁/消失/上一首封面 | 蓝牙音乐封面 | `albumUrl`、`length = 0`、`decodeFile`、`skip invalid album file` |
| 电话 UI 不弹/不消失 | HFP/UI/悬浮窗 | `ACTION_CALL_CHANGED`、`onHfpCallChanged`、`CallingState`、`TelephoneWindowControl` |
| 断电上电状态错 | ACC/POWER/SOURCE_ID | `POWER_MODE`、`ACC_STATE`、`SOURCE_ID`、`restoreBtMusicIfNeeded` |
## 13. 开发路线图
建议按这个顺序熟悉:
1. 先读现有文档:`蓝牙模块流程概览.md`、`蓝牙框架原理以及数据通讯交互流程.pdf`、`蓝牙电话交互流程解析.pdf`、`徐工项目蓝牙模块交接说明.docx`。
2. 看 `YX_BluetoothSDK` 分层:先 `manager`,再 `modules`,最后 `platform/audio/database`。
3. 跟一遍 PBAP:从 `ContactsFragment.phoneBookPullRequest()` 跟到 `BluetoothPbapClient.phoneBookPullRequest()`,再跟回 `onContactsPullCompleted()`。
4. 跟一遍 HFP:从 `BluetoothPhoneViewModel.acceptCall/rejectCall` 或 `BtPhoneControlManager.dialing()` 跟到 `BluetoothHfpClient`。
5. 跟一遍蓝牙音乐:从 `BluetoothMusicFragment` 跟到 `BtMusicControlManager`、`BtMusicManager`、`BtMusicAudioManager`、`BluetoothAvrcpClient`。
6. 跟语音开关蓝牙:从 `VoiceProtocol.handleBluetooth()` 跟到 `BlueToothHelper` 和 `BluetoothMonitorReceiver`。
7. 看外围联动:`BtInfoPushManager` 写 `BT_MUSIC_INFO/BT_INFO`,再看 Launcher/SystemUI 的读取和显示。
8. 最后看历史问题:`agent.md` 中的 PBAP 双进程、联系人 loading、通话记录自清空、封面固定路径、Launcher 首次点击播放。
## 14. 修改代码时的注意事项
- PBAP 同步不要在多个进程重复初始化监听,联系人/通话记录同步应以 `YX_BlueTooth` 进程为主。
- UI 判断“同步完成”不要只看 `isPhoneBookSynchronized()`,本轮同步期间还要看 `isPhoneBookPulling` 和 `getContactLoadWonType()`。
- PBAP 完成后要断开 PBAP,否则历史上有“阻塞、软件卡死”的风险。
- 蓝牙开关语音反馈要等最终 `STATE_ON/OFF`,不能只看 `enable/disable result=true`。
- 蓝牙音乐播放控制必须先处理音频焦点和 A2DP unmute,再发 AVRCP play。
- 封面路径下发/展示前要校验文件存在、非 0 字节、可解码,且要处理固定路径重写竞态。
- 通话记录/联系人传给 Adapter 或回调前尽量使用快照,避免共享 list 被清空或并发修改。
- `destroy()` 类清理要注意真正 `shutdown()` 线程池,历史代码里存在只调用 `isShutdown()` 的可疑写法。
## 15. 已知潜在风险
| 风险 | 说明 |
|---|---|
| `BtMusicControlManager.isA2dpSinkConnected()` | 历史排查中怀疑可能固定返回 false,需要结合当前代码确认 |
| 多处线程池销毁 | 部分 `destroy()` 里可能只调用 `isShutdown()` 而不是 `shutdown()` |
| `BluetoothMapConnectClient.isMapConnect()` | 历史排查中提到潜在 NPE |
| PBAP 同步耗时 | 现场 1973 联系人约 85 秒级,日志时间还可能因设备校时跳变 |
| 固定封面路径 | `/sdcard/ID3/id3cover.JPEG` 会复用、清零、半写入,跨 Music/Launcher/SystemUI 都要防 |
| 全局媒体键路由 | Launcher 卡片点击可能先被系统媒体 session 路由影响,需看 `MediaSessionService` 日志 |
## 16. 构建、部署、底层日志
常用构建:
```powershell
.\gradlew :YX_BluetoothSDK:assembleDebug --console plain
.\gradlew :YX_BlueTooth:assembleDebug --console plain
.\gradlew :YX_Music:assembleDebug --console plain
.\gradlew :YX_Voice:assembleDebug --console plain
```
交接说明里的部署注意点:
- 蓝牙音乐修改后,需手动打包更新到 `third_apps` 目录下。
- 配合顾凯 SDK 修改时,可以打开顾凯 SDK 日志:
```shell
adb shell setprop persist.bluetooth.btsnoopenable true
```
日志生成位置:
```text
/data/misc/bluetooth/logs
```
烧录文档补充:
- `pac` 格式通过 `SDToolBox -> SDFactoryTool` 烧录。
- `zip` 格式为 U 盘升级,将 `totalUpdate.zip` 放 U 盘根目录,系统弹窗升级。
## 17. 参考资料
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙模块流程概览.md`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\bluetooth_git_summary.md`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目蓝牙模块交接说明.docx`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目蓝牙模块主流程.xlsx`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙框架原理以及数据通讯交互流程.pdf`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\蓝牙电话交互流程解析.pdf`
- `F:\AI\Obsidian\项目文档\徐工\蓝牙\徐工项目软件烧录文档.pdf`
## 18. 快速入口清单
### SDK
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtSettingManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtPhoneManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtContactsManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtCallRecordsManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtMusicManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\modules\BtInfoPushManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\audio\BtMusicAudioManager.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothConnectListenerClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothHfpClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothPbapClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\BluetoothAvrcpClient.java`
- `F:\zdCode\xugong\YX_BluetoothSDK\src\main\java\com\yaxon\bluetoothsdk\platform\MediaBrowserClient.java`
### 蓝牙电话应用
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\MainActivity.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\main\BluetoothPhoneActivity.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\ContactsFragment.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\fragment\CallLogsFragment.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\ContactListViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\CallHistoryViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\viewmodel\BluetoothPhoneViewModel.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\service\BluetoothService.java`
- `F:\zdCode\xugong\YX_BlueTooth\src\main\java\com\yaxon\bluetooth\provider\BluetoothContactsProvider.java`
### 音乐、语音和外围
- `F:\zdCode\xugong\YX_Music\src\music\java\com\yaxon\musicplay\view\music\BluetoothMusicFragment.java`
- `F:\zdCode\xugong\YX_Music\src\main\java\com\yaxon\musicplay\viewmodel\BluetoothMusicViewModel.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\protocol\VoiceProtocol.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\helper\BlueToothHelper.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\receiver\BluetoothMonitorReceiver.java`
- `F:\zdCode\xugong\YX_Voice\src\main\java\com\yaxon\voice\helper\VoiceEventHelper.java`
- `F:\zdCode\xugong\YX_Launcher\src\main\java\com\yaxon\launcher\view\WidgetsMedia.java`
- `F:\zdCode\xugong\YX_SystemUI\src\main\java\com\android\systemui\sdk\view\navigation\MediaControlView.java`
@@ -0,0 +1,297 @@
# 徐工项目蓝牙模块流程概览
> 整理目的:帮助快速理解蓝牙模块架构和核心流程
---
## 一、整体架构
### 1.1 模块组成
| 模块 | 说明 |
|------|------|
| **YX_BlueTooth** | 上层应用,蓝牙电话、音乐界面 |
| **YX_BluetoothSDK** | SDK模块,封装蓝牙协议 |
| **Frameworks中间件** | android.bluetooth API |
| **系统蓝牙应用** | packages/apps/Bluetooth/ |
| **蓝牙协议栈** | system/bt,底层协议实现 |
| **第三方SDK** | 顾凯芯片商提供 |
### 1.2 调用层级(从上到下)
```
┌─────────────────────────────────────────────────────────┐
│ YX_Bluetooth (上层应用) │
│ ↓ android.bluetooth API │
│ Frameworks中间件 │
│ ↓ Binder IPC │
│ Bluetooth Process (系统蓝牙应用) │
│ ↓ JNI │
│ 蓝牙协议栈 (system/bt) │
│ ↓ HIDL │
│ 供应商实现 (顾凯SDK) │
│ ↓ HCI │
│ 蓝牙芯片 │
└─────────────────────────────────────────────────────────┘
```
---
## 二、蓝牙电话流程
### 2.1 电话状态(7种)
| 状态值 | 名称 | 说明 |
|--------|------|------|
| 0 | ACTIVE | 通话中 |
| 1 | HELD | 保持中 |
| 2 | DIALING | 正在拨号 |
| 3 | ALERTING | 正在响铃 |
| 4 | INCOMING | 正在来电 |
| 5 | WAITING | 等待中 |
| 7 | TERMINATED | 已挂断 |
### 2.2 状态变化图
```
来电 拨号
┌─────────→ INCOMING ─────────→ DIALING
│ │ │
│ 接听来电 对方接通
│ ↓ ↓
IDLE ─────────── ACTIVE ───────────────┘
│ │
│ 挂断
│ ↓
└─────── TERMINATED
```
### 2.3 拨号调用流程
```
BluetoothHfpClient.dial() ← 上层应用入口
↓
BluetoothHeadsetClient.dial() ← Frameworks API
↓ Binder
BluetoothHeadsetClientBinder.dial()
↓
HeadsetClientService.dial() ← 系统蓝牙服务(中转)
↓
HeadsetClientStateMachine.Connected.dial()
↓
NativeInterface.dial() ← JNI接口
↓
com_android_bluetooth_hfpclient.dialNative()
↓
第三方SDK → 蓝牙芯片
```
### 2.4 数据回调流程(反向)
```
蓝牙芯片 → 第三方SDK
↓ JNI回调
NativeInterface (事件上报)
↓
HeadsetClientService (中转分发)
↓ Event.type判断
HeadsetClientStateMachine (业务处理)
↓ 广播
上层应用 BroadcastReceiver
```
---
## 三、蓝牙音乐流程
### 3.1 核心功能
| 功能 | 说明 |
|------|------|
| 播放控制 | 播放/暂停/上一曲/下一曲 |
| 音频焦点 | 与语音、导航、视频竞争焦点 |
| 状态同步 | 播放状态同步给Launcher/投屏 |
| 方控控制 | 方向盘按键控制音乐 |
### 3.2 焦点竞争场景
| 场景 | 处理 |
|------|------|
| 语音唤醒 | 蓝牙音乐暂停,释放焦点 |
| 导航播报 | 蓝牙音乐降低音量/暂停 |
| 视频播放 | 蓝牙音乐暂停 |
| 倒车状态 | 蓝牙音乐暂停 |
---
## 四、通讯录同步流程
### 4.1 核心协议
| 协议 | 说明 |
|------|------|
| **PBAP** | Phone Book Access Profile,通讯录访问协议 |
| **HFP** | Hands-Free Profile,电话免提协议 |
### 4.2 同步限制
| 类型 | 限制 |
|------|------|
| 通讯录 | 2500条,超出截断 |
| 通话记录 | 50条 |
### 4.3 同步流程
```
1. 连接蓝牙 → 触发PBAP连接请求
2. 手机授权 → 开始拉取通讯录
3. 分批接收 → 存入数据库
4. 完成回调 → isPhoneBookPulling=false
5. UI刷新 → 显示通讯录列表
```
### 4.4 核心类
| 类名 | 作用 |
|------|------|
| BluetoothPbapClient | PBAP协议入口 |
| BtContactsManager | 通讯录管理 |
| isPhoneBookPulling | 同步状态标志 |
---
## 五、语音与蓝牙交互
### 5.1 语音控制蓝牙功能
| 功能 | 入口类 |
|------|------|
| 语音拨打电话 | VoiceEventHelper |
| 语音拒接来电 | VoiceEventHelper |
| 语音播放蓝牙音乐 | MediaProtocol |
| 语音同步通讯录状态 | VoiceNewProtocolConst |
### 5.2 状态同步给语音
| 状态 | 同步方式 |
|------|----------|
| 蓝牙连接状态 | 广播通知语音 |
| 来电状态 | 电话信息同步 |
| 通讯录同步状态 | 页面打开/关闭状态 |
---
## 六、ACC电源处理
### 6.1 处理策略
| 场景 | 处理 |
|------|------|
| ACC OFF | 不主动断开蓝牙,保持原状态 |
| ACC ON | 恢复连接状态,校验播放状态 |
| 下电上电 | 播放按钮状态需重新同步 |
### 6.2 常见问题
| 问题 | 原因 | 解决 |
|------|------|------|
| 上电后显示播放但实际没播 | 状态未校验 | 检查实际播放状态 |
| 进蓝牙音乐界面会暂停 | 状态未同步 | 延迟校验播放状态 |
---
## 七、核心代码类索引
### 7.1 电话相关
| 类名 | 路径 | 作用 |
| ------------------------- | ------------ | ------ |
| BluetoothHfpClient | YX_Bluetooth | 电话功能入口 |
| HeadsetClientService | 系统应用 | 电话服务中转 |
| HeadsetClientStateMachine | 系统应用 | 电话状态机 |
### 7.2 音乐相关
| 类名 | 作用 |
| ------ | ----------------- |
| 音频焦点管理 | AudioManager,焦点竞争 |
| 投屏数据格式 | JSON格式传输给仪表 |
### 7.3 语音交互
| 类名 | 作用 |
| --------------------- | ----------- |
| VoiceEventHelper | 语音事件处理入口 |
| MediaProtocol | 媒体协议,蓝牙音乐状态 |
| VoiceNewProtocolConst | TXZ语音协议常量 |
---
## 八、常见问题快速定位
| 问题类型 | 关键类/方法 | 日志关键字 |
|----------|-------------|------------|
| 通讯录不同步 | BluetoothPbapClient.phoneBookPullRequest() | BtContactsManager, onContactsPullCompleted |
| 音乐状态异常 | 音频焦点处理 | AudioManager, onFocusChange |
| 语音拒接失败 | VoiceEventHelper拒接逻辑 | 蓝牙拒接, HFP |
| ACC状态处理 | 电源状态监听 | ACC_OFF, ACC_ON |
| 投屏数据异常 | JSON数据传输 | 投屏, DataCenter |
| ANR问题 | 主线程阻塞 | ANR, SQLiteCursor |
---
## 九、开发注意事项
### 9.1 通讯录同步
- 完成后必须设置 `isPhoneBookPulling = false`
- 同步过程需异步处理,避免阻塞UI
- 通讯录超过2500条会截断
### 9.2 音频焦点
- 语音唤醒时蓝牙音乐必须释放焦点
- 导航播报后需恢复播放状态
- 倒车暂停需在倒车结束后恢复
### 9.3 ACC电源
- 不要在ACC OFF时主动断开蓝牙
- 上电后需校验实际播放状态,避免状态不同步
### 9.4 UI适配
- 深色模式:联系人字母颜色需适配
- 右舵项目:需单独布局资源
- 12.3寸屏幕:需单独适配
---
## 十、参考文档
| 文档 | 说明 |
|------|------|
| [[蓝牙框架原理以及数据通讯交互流程.pdf]] | 蓝牙框架层级详解 |
| [[蓝牙电话交互流程解析.pdf]] | 电话状态和调用流程 |
| [[徐工项目蓝牙模块主流程.xlsx]] | 主流程表格 |
| [[徐工项目蓝牙模块交接说明.docx]] | 交接说明文档 |
| [[bluetooth_git_summary.md]] | Git提交记录汇总 |
---
## 十一、Git提交统计
> 统计时间: 2024-01-01 ~ 2026-05-06
> 提交总数: 230条
| 功能模块 | 提交数 | 主要内容 |
|----------|--------|----------|
| 语音与蓝牙交互 | 35条 | 语音控制、状态同步、音频焦点 |
| 蓝牙音乐 | 25条 | 播放状态、投屏、焦点控制 |
| 蓝牙电话 | 40条 | 来电/去电、通话状态、投屏 |
| 通讯录同步 | 30条 | 同步流程、UI交互 |
| 连接状态管理 | 20条 | ACC处理、可见性、配对 |
| UI适配 | 35条 | 深色模式、右舵、国际化 |
| 国际化 | 15条 | 翻译、多语言 |
| 代码优化 | 15条 | 工具类统一、构建优化 |
| Bug修复 | 15条 | ANR、崩溃、空指针 |
@@ -0,0 +1,103 @@
# 蓝牙电话模块权限适配文档
## 一、权限 Key
| Key | 关联功能 |
| -------------------------------------------- | ----------- |
| `xcmg_permission_contacts_bluetooth_phone` | 联系人同步、通话记录 |
| `xcmg_permission_microphone_bluetooth_phone` | 去电/接听时麦克风采集 |
值:`0` = 未授权,`1` = 已授权
读取方式:
```java
boolean authorized = Settings.System.getInt(
context.getContentResolver(), "xcmg_permission_xxx", 0) == 1;
```
---
## 二、权限关联逻辑
### 2.1 拨号键(去电前校验麦克风权限)
```
点击拨号键
│
├── 麦克风权限 == 1 → 正常去电
│
└── 麦克风权限 == 0 → 弹出多权限勾选弹窗
├── 勾选 + 确定授权 → 权限写入 → 正常去电
└── 取消 → 不去电
```
```java
public void onDialClick(String phoneNumber) {
if (!isMicAuthorized()) {
showPermissionDialog(); // 发广播 ACTION_SHOW_MULTI_PERMISSION_DIALOG, package_name="com.yaxon.bluetooth"
return;
}
doDial(phoneNumber);
}
```
### 2.2 一键同步 / 刷新(同步前校验联系人权限)
```
点击"一键同步" / "刷新"
│
├── 联系人权限 == 1 → 开始同步
│
└── 联系人权限 == 0 → 弹出多权限勾选弹窗
├── 勾选 + 确定授权 → 权限写入 → 执行同步
└── 取消 → 不执行同步
```
```java
public void onSyncClick() {
if (!isContactsAuthorized()) {
showPermissionDialog(); // 同上广播
return;
}
doSyncContacts();
}
```
### 2.3 联系人权限控制的数据同步
联系人权限 == 0 时,不同步/存储联系人和通话记录到车机端。权限状态直接读 `Settings.System` 实时判断,无需回调。
---
## 三、弹窗触发方式
发送广播即可,CarControl 模块自动弹出对应权限的勾选框弹窗:
```java
Intent intent = new Intent("com.yaxon.carcontrol.ACTION_SHOW_MULTI_PERMISSION_DIALOG");
intent.putExtra("package_name", "com.yaxon.bluetooth");
intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
context.sendBroadcast(intent);
```
## 四、监听授权结果
如果需要授权后自动继续操作(如授权后自动拨号/同步),用 ContentObserver 监听:
```java
private void registerPermissionObserver() {
mObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
@Override
public void onChange(boolean selfChange) {
if (isMicAuthorized()) {
doDial(mPendingPhoneNumber); // 授权后自动去电
unregisterObserver();
}
}
};
context.getContentResolver().registerContentObserver(
Settings.System.getUriFor("xcmg_permission_microphone_bluetooth_phone"),
false, mObserver);
}
```
+219
View File
@@ -0,0 +1,219 @@
# 徐工语音模块开发文档
## 概述
本文档记录徐工项目语音模块的开发调试相关内容,包括常用命令、协议定义、问题排查等。
---
## 常用调试命令
### 测试账号
- 新账号:`T9999911`
- 旧账号:`xgtest010002`
### 问题汇总文档
[腾讯文档 - 徐工语音问题汇总](https://docs.qq.com/sheet/DRmh6ZXRVbGR4cFpk?tab=BB08J2)
### 电源模式设置
```bash
adb shell
content insert --uri content://com.yaxon.data.provider/state/ --bind POWER_MODE:i:1
```
### 缓存清理
```bash
# 清理同行者缓存
adb shell pm clear com.txzing.voiceassistant
# 清理语音模块缓存
adb shell pm clear com.yaxon.voice
```
### 语音唤醒与调用
```bash
# 直接调用语音指令
adb shell am broadcast -a com.txznet.txz.invoke -d txznet://com.txznet.txz/comm.asr.startWithRawText?指令
# 方控唤醒
adb shell am broadcast -a android.intent.action.start_txzvoice
# 拉起语音界面
adb shell am start com.txzing.voiceassistant/com.txzing.assistant.core.console.ui.ConsoleActivity
# 查询天气示例
adb shell am broadcast -a com.txznet.txz.invoke -d "txznet://com.txznet.txz/comm.asr.startWithRawText?今天的天气"
```
### 驾驶行为弹框 TTS 测试命令
| 测试命令 | 对应常量 | 弹框标题 | 弹框内容 |
| --------------------------------------------------------- | -------------------------- | ------ | -------------------------------------------- |
| adb shell am broadcast -a com.voice.test.tts.drowsy | `DROWSY_DRIVING` | `疲劳监测` | `您看起来有点累了,请注意休息!\n您可以说“稍后提醒”关闭弹窗` |
| `adb shell am broadcast -a com.voice.test.tts.distracted` | `DISTRACTED_DRIVING` | `分心驾驶` | `为了您的骂驶安全,请集中注意力!\n您可以说“稍后提醒”关闭弹窗` |
| `adb shell am broadcast -a com.voice.test.tts.smoking` | `SMOKING_RECKLESS_DRIVING` | `危险驾驶` | `主人,开车抽烟危险,请不要在开车时吸烟。\n您可以说“稍后提醒”关闭弹腐` |
| `adb shell am broadcast -a com.voice.test.tts.calling` | `CALLING_RECKLESS_DRIVING` | `危险驾驶` | `开快车打电话很危险,下次可以对我说"给小明打电话"。\n您可以说“稍后提醒”关闭弹腐` |
### 工厂模式
```bash
adb shell am start com.yaxon.factory/.test.view.FactoryMainActivity
```
### 日志过滤
```bash
# 过滤中间件报文日志
processBodyControl
# 过滤语音模块日志
package:com.yaxon.voice tag:sendCommandTxz tag:VoiceEventHelper
# 语音协议日志
package:com.yaxon.voice VoiceProtocol
# 同行者状态同步日志
package:com.txzing.voiceassistant tag:defaultRegisterHandler
# 过滤激活成功日志
onActivationSuccess
# 过滤初始化日志
initialize: versions
```
---
## 资源提交路径
### 同行者资源路径
```
Z:\znzc\034_xcmg\x9hp_xcmg\androd10_x9hp_ptg4.3p3\android10\vendor\yaxon\txz
```
### 编译输出路径
```
Z:\znzc\034_xcmg\x9hp_xcmg\androd10_x9hp_ptg4.3p3\android10\out\target\product\x9hp_ms\obj\PACKAGING\target_files_intermediates\x9hp_ms-target_files-userdebug.znzc\SYSTEM\txz
```
---
## 协议定义
### 车辆控制协议
| 功能 | 协议Key | CAN ID | 说明 |
|------|---------|--------|------|
| 自适应巡航控制模式 | FCS_AdpvCrsSetDstMod | 0x10FE6F2A | - |
| 预警灵敏度 | FCS_FCWSnvtyFb | 0x18FFD02A | - |
| 自动紧急制动 | FCS_AEBSt | 0xCF02FA0 | - |
| 车道偏离预警 | FCS_LaneDepartureIndcnEnaSts | 0x18FE5B2A | - |
| 车道偏离灵敏度 | FCS_LdwSnvtyFb | 0x18FFD02A | - |
| 主驾车窗 | DDCM_DrvrWinPosn | 0x18F668EC | - |
| 副驾车窗 | BDM_PassWinPosn | 0x18A12721 | - |
### 媒体控制协议
| 功能 | 协议Key | 参数 |
|------|---------|------|
| 上一首 | playPrev | `{"type":"music"}` |
| 同步媒体焦点 | notifySyncMediaFocus | - |
| 同步播放状态 | notifySyncFocusPlayStatus | - |
---
## 问题排查
### 录音唤醒问题
1. 开启录音保存:
```bash
setprop persist.pcm.dump 9
```
2. 讲话唤醒同行者,可以多讲几句
3. 关闭录音保存:
```bash
setprop persist.pcm.dump 0
```
4. 录音文件路径:
```
/data/misc/audioserver/dump_audio_9.pcm
```
> 注意:不及时关闭会导致录音文件持续增大
### 常见错误日志
```
tiplogRequestHandlerUtil: Request unsupported!, method = syncAcDefrostStatus
```
---
## 相关截图
### 语音界面
![[语音图片/语音截图_1.png]]
![[语音图片/语音截图_2.png]]
![[语音图片/语音截图_3.png]]
![[语音图片/语音截图_4.png]]
### 资源路径
![[语音图片/语音截图_5.png]]
![[语音图片/语音截图_6.png]]
![[语音图片/语音截图_7.png]]
### 协议相关
![[语音图片/语音截图_8.png]]
![[语音图片/语音截图_9.png]]
![[语音图片/语音截图_10.png]]
![[语音图片/语音截图_11.png]]
### 状态同步
![[语音图片/语音截图_12.png]]
![[语音图片/语音截图_13.png]]
![[语音图片/语音截图_14.png]]
![[语音图片/语音截图_15.png]]
![[语音图片/语音截图_16.png]]
![[语音图片/语音截图_17.png]]
![[语音图片/语音截图_18.png]]
![[语音图片/语音截图_19.png]]
![[语音图片/语音截图_20.png]]
![[语音图片/语音截图_21.png]]
---
*文档整理日期: 2026-05-27*
你好,徐工, 车窗开一点 主驾有问题,副驾正常
车窗全打开, 车窗关一点点 副驾是对的。主驾有问题
@@ -0,0 +1,76 @@
# 徐工语音项目总览
## 一句话结论
徐工语音项目的核心是 `YX_Voice` 系统应用,它作为同行者 TXZ 语音助手与车载业务模块之间的桥接层,负责语音指令接收、协议分发、IPC 调用、车辆状态同步和语音相关问题排查。
## 当前定位
| 项目项 | 内容 |
|---|---|
| 项目名称 | 徐工语音项目 |
| 核心模块 | `YX_Voice` |
| 包名 | `com.yaxon.voice` |
| 应用类型 | Android System Application |
| 进程属性 | 系统持久化进程,`persistent=true` |
| 运行身份 | `android.uid.system` |
| 语音方案 | 同行者 TXZ 语音助手 |
| 主要职责 | TXZ 连接、语音指令分发、协议处理、IPC 调用、状态同步 |
## 核心链路
```text
用户语音
-> TXZ 语音助手
-> VoiceService
-> ProtocolManager
-> YxProtocolHandler
-> 具体 Protocol
-> VoiceEventHelper
-> YxIPC / 业务模块
-> 状态同步回 TXZ
```
## 已有资料索引
| 文档 | 用途 |
|---|---|
| [[YX_Voice_架构文档]] | 系统架构、模块分层、核心链路 |
| [[YX_Voice_模块详细文档]] | 应用入口、服务、管理器、Helper、Receiver 等详细说明 |
| [[YX_Voice_协议设计文档]] | 协议接口、命令映射、Bundle 消息格式、状态同步 |
| [[YX_Voice_配置文档]] | Gradle、Manifest、权限、IPC、TXZ 连接配置 |
| [[同行者可见即可说自动注册框架对接方案_快速阅读]] | 可见即可说接入原则、控件要求、AIDL/广播链路 |
| [[../语音.md]] | 调试命令、账号、问题汇总、资源路径 |
| [[../语音_整理.md]] | 原始整理记录 |
| [[../语音唤醒异常问题分析.md]] | AudioFlinger dump 与唤醒异常排查 |
| [[../log 录音分析.md]] | 录音/音频相关原始分析材料 |
## 当前核心难点
| 难点 | 说明 | 风险 |
|---|---|---|
| TXZ 连接与状态同步 | 连接成功后需同步车辆配置、蓝牙、音量、亮度、设置等状态 | 状态不同步会导致语音反馈与车机实际状态不一致 |
| 协议映射完整性 | 语音指令需要映射到正确协议和业务模块 | 漏映射会出现语音识别成功但功能无响应 |
| IPC 可靠性 | VoiceEventHelper 通过 IPC 控制音乐、设置、空调等模块 | 业务模块未绑定或接口变更会导致调用失败 |
| 可见即可说接入 | 控件需要唯一 ID、文本/隐藏标签、状态型描述动态更新 | UI 可见但语音无法命中或命中错误状态 |
| 音频唤醒问题 | 涉及 AudioFlinger、AudioPolicy、录音 dump 和硬件输入链路 | 需要日志、dump 和录音文件联合判断 |
## 近期优先级
1. 固化调试命令和日志过滤方式,减少重复查找。
2. 维护 Bug 记录,区分现象、根因、修复和验证结果。
3. 建立协议/模块变更记录,避免接口变更后文档失真。
4. 用测试验证记录沉淀每次构建、安装、语音唤醒、指令执行、状态同步结果。
5. 后续进入代码修改时,再联动 `F:\ZDCode\xugong` 源码工程。
## Agent 使用入口
每次让 Agent 处理本项目任务,建议先说:
```text
先读取 agent.md 和 项目文档/徐工/语音/00_项目总览.md。
本次目标是:……
涉及日志/代码/文档是:……
先判断风险等级;如果要改文件,先问我要不要备份。
完成后把原因、处理方式、验证结果、遗留问题写入对应记录文档。
```
@@ -0,0 +1,51 @@
# 徐工语音模块清单
## 模块总览
| 模块 | 文件/类 | 职责 | 关联文档 |
|---|---|---|---|
| 应用入口 | `YxApplication` | 初始化车载 SDK、IPC、崩溃处理和语音服务 | [[YX_Voice_模块详细文档]] |
| 语音服务 | `VoiceService` | TXZ 连接、指令接收、状态初始化、Receiver 注册 | [[YX_Voice_模块详细文档]] |
| 协议管理 | `ProtocolManager` | 接收 IPC 消息并分发到协议处理器 | [[YX_Voice_模块详细文档]] |
| 协议处理 | `YxProtocolHandler` | 维护包名到协议逻辑类映射,后台线程分发协议 | [[YX_Voice_模块详细文档]] |
| 状态管理 | `VoiceManager` | 管理 TXZ 绑定、AVM/DVR 状态、车辆状态回调 | [[YX_Voice_模块详细文档]] |
| 事件封装 | `VoiceEventHelper` | 封装语音控制操作,通过 IPC 调业务模块 | [[YX_Voice_模块详细文档]] |
| 协议实现 | `BaseProtocol` 及子类 | 实现车辆、空调、设置、媒体等协议逻辑 | [[YX_Voice_协议设计文档]] |
| TXZ 通信 | `TxzProtocolManager` | 与同行者语音助手建立协议通信 | [[YX_Voice_架构文档]] |
| IPC 通信 | `YxIPC` | 跨进程调用音乐、设置、空调等模块 | [[YX_Voice_架构文档]] |
| 可见即可说 | 无障碍扫描 / AIDL / 广播 | 控件语音热词注册和命中回调 | [[同行者可见即可说自动注册框架对接方案_快速阅读]] |
## 关键依赖
| 依赖 | 用途 |
|---|---|
| `YX_IPC_SDK` | 跨进程通信 |
| `YX_Lib_CommonBase` | 公共基础能力 |
| `YX_AutoMotiveSDK` | 车载 SDK |
| `library-release-0127.aar` | TXZ 协议库 |
| `tools.jar` | 工具库 |
| `framework_classes.jar` | 系统框架编译依赖 |
| `serial-0.1.6_classes.jar` | 串口相关依赖 |
## 协议类别
| 类别 | 示例 | 验证重点 |
|---|---|---|
| 车辆控制 | 巡航、车道偏离、车窗等 | 指令是否映射到正确 CAN/业务接口 |
| 空调控制 | 开关、温度、风速、模式 | 状态反馈是否同步到 TXZ |
| 系统设置 | 语言、单位、亮度、熄屏 | 设置值是否落库并广播 |
| 媒体控制 | 上一首、播放状态、媒体焦点 | 媒体焦点和播放状态是否一致 |
| 蓝牙相关 | 蓝牙状态、电话、连接 | 蓝牙状态同步和权限边界 |
## 模块变更记录模板
| 日期 | 模块 | 变更类型 | 变更内容 | 影响范围 | 验证方式 | 结论 |
|---|---|---|---|---|---|---|
| 待填写 | 待填写 | 新增/修改/删除 | 待填写 | 待填写 | 待填写 | 待验证 |
## 模块验收标准
- 每个语音指令都有明确协议入口、参数格式和目标业务模块。
- 每个状态同步都有触发时机、数据来源和 TXZ 通知路径。
- 每个跨模块调用都有失败处理和日志。
- 每次模块变更都在本文件记录影响范围和验证方式。
+137
View File
@@ -0,0 +1,137 @@
# 徐工语音 Bug 记录
## 使用规则
每个 Bug 都按“现象 -> 证据 -> 根因 -> 修复 -> 验证 -> 遗留风险”记录,避免只留下口头结论。
## Bug 列表
| ID | 日期 | 问题现象 | 模块 | 优先级 | 状态 | 负责人/来源 | 关联记录 |
|---|---|---|---|---|---|---|---|
| VOICE-001 | 2026-06-15 | 语音唤醒异常 | 音频/TXZ/AudioFlinger | P1 | 待复现 | 已有文档沉淀 | [[../语音唤醒异常问题分析]]、[[../log 录音分析]] |
| VOICE-002 | 待填写 | 语音识别成功但功能无响应 | 协议/IPC | P1 | 待分析 | 待填写 | 待补充 |
| VOICE-003 | 待填写 | 状态同步不准确 | 状态同步/TXZ | P2 | 待分析 | 待填写 | 待补充 |
## Bug 详情
### VOICE-001:语音唤醒异常
| 字段 | 内容 |
|---|---|
| 发现日期 | 历史问题,2026-06-15 补充到 Bug 记录 |
| 影响版本 | 待确认 |
| 问题模块 | 音频输入链路、TXZ 唤醒、AudioFlinger/AudioPolicy |
| 触发条件 | 用户尝试语音唤醒同行者时,出现唤醒失败或录音链路异常 |
| 用户影响 | 无法稳定通过语音唤醒进入后续指令流程 |
| 当前状态 | 待复现 / 待根因确认 |
#### 现象
已有资料将该问题归类为音频唤醒异常,排查方向集中在 AudioFlinger dump、AudioPolicy、录音 dump 和硬件输入链路。当前 Bug 记录先沉淀入口,具体现场表现还需要结合最新 logcat、dump 和录音文件补齐。
#### 复现步骤
1. 确认 TXZ/同行者语音助手处于可唤醒状态。
2. 多次使用唤醒词触发语音唤醒。
3. 记录是否出现无法唤醒、唤醒不稳定、录音异常或 TXZ 无响应。
4. 同步抓取 logcat、AudioFlinger、AudioPolicy、audio dumpsys 和 PCM dump。
#### 证据
| 证据类型 | 路径/命令 | 关键内容 |
|---|---|---|
| 分析文档 | [[../语音唤醒异常问题分析]] | 记录 AudioFlinger dump 识别方式、常用 dump 命令和唤醒异常排查步骤 |
| 原始分析材料 | [[../log 录音分析]] | 录音/音频相关原始分析材料 |
| AudioFlinger dump | `adb shell dumpsys media.audio_flinger > audio_flinger.log` | 关注 Input thread、Frames read、采样率、通道、设备和错误统计 |
| AudioPolicy dump | `adb shell dumpsys media.audio_policy > audio_policy.log` | 关注输入设备选择、路由策略和音频源 |
| 完整音频状态 | `adb shell dumpsys audio > audio.log` | 关注音频服务整体状态 |
| PCM dump | `setprop persist.pcm.dump 9` 后复现,再 `adb pull /data/misc/audioserver/dump_audio_9.pcm` | 判断麦克风是否有有效输入音频 |
#### 根因分析
| 候选根因 | 证据 | 可信度 |
|---|---|---|
| 输入链路没有采集到有效音频 | 需要检查 Input thread 状态、Frames read 变化和 PCM dump 内容 | 中 |
| 音频设备或路由选择异常 | 需要对比 AudioPolicy dump 中的 input device / audio source | 中 |
| 采样率、通道或 HAL 格式与 TXZ 期望不一致 | 需要检查 AudioFlinger 中 Sample rate、HAL format、Channel count | 低 |
| TXZ 唤醒侧状态异常 | 需要补充 TXZ/VoiceService 相关 logcat | 低 |
#### 修复方案
暂不直接给代码修复。下一步先按证据清单复现并抓取日志,确认问题落在音频输入链路、AudioPolicy 路由、TXZ 唤醒状态,还是 `YX_Voice` 与 TXZ 的连接/状态同步链路。
#### 验证结果
| 验证项 | 命令/步骤 | 结果 |
|---|---|---|
| 唤醒复现 | 多次触发唤醒词并记录成功率 | 待验证 |
| 输入采集 | 检查 `adb shell dumpsys media.audio_flinger` 中 Input thread 和 Frames read | 待验证 |
| PCM 有效性 | 拉取 `/data/misc/audioserver/dump_audio_9.pcm` 并听取/分析波形 | 待验证 |
| TXZ 状态 | 过滤 TXZ、VoiceService、唤醒相关 logcat | 待验证 |
#### 遗留风险
当前只有历史分析文档和排查方法,还缺少同一现场下的完整 logcat、dump 和 PCM 文件,因此不能直接判定根因。后续每次复现都应把日志、dump、录音文件路径回填到本 Bug。
## Bug 详情模板
### VOICE-XXX:问题标题
| 字段 | 内容 |
|---|---|
| 发现日期 | 待填写 |
| 影响版本 | 待填写 |
| 问题模块 | 待填写 |
| 触发条件 | 待填写 |
| 用户影响 | 待填写 |
| 当前状态 | 待分析 / 修复中 / 待验证 / 已关闭 |
#### 现象
待填写。
#### 复现步骤
1. 待填写。
2. 待填写。
3. 待填写。
#### 证据
| 证据类型 | 路径/命令 | 关键内容 |
|---|---|---|
| logcat | 待填写 | 待填写 |
| dumpsys | 待填写 | 待填写 |
| 截图/录音 | 待填写 | 待填写 |
#### 根因分析
| 候选根因 | 证据 | 可信度 |
|---|---|---|
| 待填写 | 待填写 | 高/中/低 |
#### 修复方案
待填写。
#### 验证结果
| 验证项 | 命令/步骤 | 结果 |
|---|---|---|
| 正常路径 | 待填写 | 待验证 |
| 异常路径 | 待填写 | 待验证 |
| 回归项 | 待填写 | 待验证 |
#### 遗留风险
待填写。
## 常用问题分类
| 分类 | 典型表现 | 优先看 |
|---|---|---|
| TXZ 连接 | 初始化失败、绑定状态异常 | `VoiceService.onConnected()`、TXZ 初始化日志 |
| 协议分发 | 指令收到但无动作 | `ProtocolManager`、`YxProtocolHandler`、协议映射 |
| IPC 调用 | 业务模块无响应 | `VoiceEventHelper`、YxIPC 绑定状态 |
| 状态同步 | TXZ 反馈和车机状态不一致 | 初始化状态同步、通知方法 |
| 音频唤醒 | 唤醒失败、录音异常 | AudioFlinger、AudioPolicy、PCM dump |
@@ -0,0 +1,96 @@
# 徐工语音日志分析记录
## 日志分析流程
```text
确认问题现象
-> 确认时间点
-> 收集 logcat / dumpsys / 录音 / 截图
-> 提取关键字
-> 建立时间线
-> 区分现象、直接原因、根因
-> 给出验证动作
```
## 常用调试命令
### 语音唤醒与调用
```bash
adb shell am broadcast -a android.intent.action.start_txzvoice
adb shell am start com.txzing.voiceassistant/com.txzing.assistant.core.console.ui.ConsoleActivity
adb shell am broadcast -a com.txznet.txz.invoke -d "txznet://com.txznet.txz/comm.asr.startWithRawText?打开空调"
```
### 缓存清理
```bash
adb shell pm clear com.txzing.voiceassistant
adb shell pm clear com.yaxon.voice
```
### 音频排查
```bash
adb shell setprop persist.pcm.dump 9
adb shell dumpsys media.audio_flinger > audio_flinger.log
adb shell dumpsys media.audio_policy > audio_policy.log
adb shell dumpsys audio > audio.log
adb shell setprop persist.pcm.dump 0
adb pull /data/misc/audioserver/dump_audio_9.pcm
```
## 常用过滤关键字
| 场景 | 关键字 |
|---|---|
| 中间件报文 | `processBodyControl` |
| 语音模块 | `package:com.yaxon.voice`、`VoiceEventHelper`、`sendCommandTxz` |
| 协议逻辑 | `VoiceProtocol`、`ProtocolManager`、`YxProtocolHandler` |
| 同行者状态 | `defaultRegisterHandler`、`TxzProtocolManager` |
| 激活状态 | `onActivationSuccess` |
| 初始化 | `initialize: versions` |
| 音频 dump | `AudioFlinger`、`Input thread`、`Output thread`、`Frames read` |
## 分析记录模板
### LOG-YYYYMMDD-XXX:问题标题
| 字段 | 内容 |
|---|---|
| 日期 | 待填写 |
| 关联 Bug | 待填写 |
| 日志来源 | 待填写 |
| 时间范围 | 待填写 |
| 设备/版本 | 待填写 |
| 分析状态 | 待分析 / 已定位 / 待复测 |
#### 问题现象
待填写。
#### 关键时间线
| 时间 | 进程/模块 | 日志摘要 | 判断 |
|---|---|---|---|
| 待填写 | 待填写 | 待填写 | 待填写 |
#### 关键证据
```text
粘贴关键日志片段,避免贴整份日志。
```
#### 初步结论
待填写。
#### 需要补充的证据
- 待填写。
## 已有关联资料
- [[../语音唤醒异常问题分析]]
- [[../log 录音分析]]
- [[../语音]]
@@ -0,0 +1,80 @@
# 徐工语音测试验证记录
## 验证目标
将每次语音模块修改、配置调整、资源替换和问题修复都沉淀为可复用的验证记录,覆盖主流程、异常流程和回归项。
## 基础验证环境
| 项 | 内容 |
|---|---|
| 项目 | 徐工 X9HP / 语音 |
| 语音助手 | 同行者 TXZ |
| 语音包名 | `com.txzing.voiceassistant` |
| 语音模块包名 | `com.yaxon.voice` |
| 测试账号 | `T9999911` / `xgtest010002` |
| 代码工程 | `F:\ZDCode\xugong` |
| 文档工程 | `F:\AI\Obsidian\项目文档\徐工\语音` |
## 通用验证前置
```bash
adb devices
adb shell pm clear com.txzing.voiceassistant
adb shell pm clear com.yaxon.voice
adb shell am start com.txzing.voiceassistant/com.txzing.assistant.core.console.ui.ConsoleActivity
```
## 验证清单
| 验证项 | 操作 | 期望结果 | 状态 |
|---|---|---|---|
| TXZ 初始化 | 启动语音助手 | 初始化成功,有版本和连接日志 | 待验证 |
| 语音服务启动 | 重启设备或启动服务 | `VoiceService` 正常创建并连接 TXZ | 待验证 |
| 方控唤醒 | `adb shell am broadcast -a android.intent.action.start_txzvoice` | 语音界面或唤醒状态正常 | 待验证 |
| 直接语音指令 | 发送 `startWithRawText` 指令 | TXZ 识别并分发到 YX_Voice | 待验证 |
| 协议分发 | 执行车辆/空调/媒体指令 | `ProtocolManager` 和目标 Protocol 有日志 | 待验证 |
| IPC 调用 | 执行业务控制 | 目标业务模块收到并执行 | 待验证 |
| 状态同步 | 连接成功后同步状态 | 蓝牙、音量、亮度、车辆设置状态正确 | 待验证 |
| 可见即可说 | 当前界面说出控件名称 | 控件被命中并触发点击 | 待验证 |
| 异常恢复 | 清缓存后重新初始化 | 能恢复连接和指令处理 | 待验证 |
## 验证记录模板
### TEST-YYYYMMDD-XXX:验证标题
| 字段 | 内容 |
|---|---|
| 日期 | 待填写 |
| 关联 Bug/需求 | 待填写 |
| 版本/构建号 | 待填写 |
| 测试设备 | 待填写 |
| 测试人员 | 待填写 |
| 结论 | 通过 / 不通过 / 部分通过 |
#### 验证步骤
1. 待填写。
2. 待填写。
3. 待填写。
#### 实际结果
待填写。
#### 关键日志
```text
待填写。
```
#### 遗留问题
待填写。
## 回归要求
- 修改协议映射后,至少验证目标协议、相邻协议和未知指令处理。
- 修改 TXZ 连接逻辑后,至少验证清缓存、冷启动、重启后连接。
- 修改状态同步后,至少验证初始同步和状态变化后的再次同步。
- 修改音频/唤醒相关逻辑后,至少保留 AudioFlinger/AudioPolicy dump 和录音文件结论。
@@ -0,0 +1,52 @@
# 徐工语音决策记录
## 使用规则
本文件记录影响项目方向、接口契约、模块边界、测试策略、资源路径和风险取舍的决策。不要删除旧决策;如果结论变化,用“更新标记”追加。
## 决策列表
| ID | 日期 | 决策 | 原因 | 影响范围 | 状态 |
| ------- | ---------- | ---------------------------------------------------- | ---------------------------- | ----------- | --- |
| ADR-001 | 2026-06-15 | 先在 Obsidian 建立语音项目工程化文档骨架,不直接改 `F:\ZDCode\xugong` 源码 | 当前目标是建立长期记忆和协作流程,不是修具体代码 Bug | 文档工程 | 生效 |
| ADR-002 | 2026-06-15 | 语音项目入口文档使用 `00_项目总览.md` | 让 Agent 和人都能先读一个入口文件快速进入上下文 | Obsidian 文档 | 生效 |
| ADR-003 | 2026-06-15 | Bug、日志、测试、决策分文件记录 | 避免问题分析、验证结论和需求设计混在同一文档中 | 项目管理方式 | 生效 |
## 决策模板
### ADR-XXX:标题
| 字段 | 内容 |
|---|---|
| 日期 | 待填写 |
| 背景 | 待填写 |
| 决策 | 待填写 |
| 备选方案 | 待填写 |
| 选择原因 | 待填写 |
| 影响范围 | 待填写 |
| 风险 | 待填写 |
| 验证方式 | 待填写 |
| 状态 | 生效 / 废弃 / 被替代 |
#### 背景
待填写。
#### 决策
待填写。
#### 影响
待填写。
#### 更新记录
- 待填写。
## 当前约束
- 项目文档放在 `F:\AI\Obsidian\项目文档\徐工\语音`。
- 代码工程位于 `F:\ZDCode\xugong`,只有进入具体开发/修复任务时才修改。
- 高风险变更包括协议契约、权限、状态同步、TXZ 连接、IPC 接口、并发/生命周期逻辑。
- 高风险变更必须先给计划并等待确认。
@@ -0,0 +1,91 @@
# 徐工语音 Agent 协作说明
## 目标
让 Agent 在处理徐工语音项目时,固定遵循“读取上下文、判断风险、执行任务、验证结果、记录结论”的工程闭环。
## 默认上下文读取顺序
1. 根目录 `agent.md`
2. [[00_项目总览]]
3. 与任务相关的专项文档:
- 架构/模块:[[YX_Voice_架构文档]]、[[YX_Voice_模块详细文档]]
- 协议:[[YX_Voice_协议设计文档]]
- 配置:[[YX_Voice_配置文档]]
- 可见即可说:[[同行者可见即可说自动注册框架对接方案_快速阅读]]
- Bug:[[02_Bug记录]]
- 日志:[[03_日志分析记录]]
- 测试:[[04_测试验证记录]]
- 决策:[[05_决策记录]]
## 常用任务口令
### 分析日志
```text
先读取 agent.md 和 项目文档/徐工/语音/00_项目总览.md。
按日志分析流程分析以下日志/现象:……
请输出时间线、证据链、根因候选、修复建议和验证方案。
如果需要写入记录,追加到 03_日志分析记录.md。
```
### 记录 Bug
```text
先读取 agent.md、00_项目总览.md 和 02_Bug记录.md。
把下面的问题按 Bug 模板记录:……
区分现象、复现步骤、证据、根因假设、修复方案、验证结果和遗留风险。
```
### 设计模块
```text
先读取 agent.md、00_项目总览.md、YX_Voice_架构文档.md 和 YX_Voice_模块详细文档.md。
围绕以下需求做模块设计:……
请输出模块目标、接口、数据结构、控制流、异常路径、测试用例和 ASPICE 对齐建议。
```
### 准备改代码
```text
先读取 agent.md 和 00_项目总览.md。
本次准备修改 F:\ZDCode\xugong 中的代码,目标是:……
请先判断风险等级,列出涉及文件、备份建议、修改方案和验证命令。
在我确认前不要改代码。
```
### 做验证
```text
先读取 agent.md、00_项目总览.md 和 04_测试验证记录.md。
按测试验证模板验证:……
请输出验证步骤、实际结果、关键日志、结论和遗留问题。
```
## 风险分级
| 等级 | 判定条件 | 处理方式 |
|---|---|---|
| 高风险 | 协议契约、权限、状态同步、TXZ 连接、IPC 接口、并发、生命周期、架构调整 | 先给计划,等待确认 |
| 中风险 | 单模块多点修改、已有逻辑重构、测试影响不明确 | 先给简要计划 |
| 低风险 | 文档整理、命令记录、单处说明补充、非功能性笔记整理 | 可直接执行 |
## 写入规则
- 修改文件前必须确认是否备份。
- 不删除旧结论;结论变化时使用“更新标记”追加。
- Bug 修复后必须记录验证结果。
- 无法验证时必须明确写“未验证”,不能假装通过。
- 涉及真实代码工程 `F:\ZDCode\xugong` 时,必须先确认目标和风险。
## 推荐输出格式
```text
结论
范围
依据
执行/建议
验证
风险
下一步
```
@@ -0,0 +1,136 @@
# YX_Voice 协议设计文档
## 1. 协议接口定义
### 1.1 IProtocolHandler (协议处理接口)
```java
public interface IProtocolHandler {
void onHandleProtocol(long msgSendTime, String remoteName, Bundle data);
}
```
### 1.2 IProtocolLogic (协议逻辑接口)
```java
public interface IProtocolLogic {
void onProtocolLogic(Bundle data);
}
```
## 2. 协议基类 (BaseProtocol)
```java
public abstract class BaseProtocol implements IProtocolLogic {
@Override
public void onProtocolLogic(Bundle data) {
String cmd = data.getString(VoiceConst.PARAMS_CMD_KEY, "");
switch (cmd) {
case CMD_SWITCH_TXZ_WINDOW: // 切换TXZ窗口
case CMD_PLAY_TTS: // 播放TTS
default:
onProtocolParse(data); // 子类实现具体解析
}
}
protected abstract void onProtocolParse(Bundle data);
}
```
## 3. 协议映射表
```java
protocolLogicMap.put(CAR_CAR_CONTROL_PKG, new CarControlProtocol()); // 车辆控制
protocolLogicMap.put(TXZ_PKG_NEW, new VoiceProtocol()); // 语音协议
protocolLogicMap.put(AIR_PKG, new AirProtocol()); // 空调协议
protocolLogicMap.put(BLUETOOTH_PKG, new BluetoothProtocol()); // 蓝牙协议
protocolLogicMap.put(MUSIC_PKG, new MediaProtocol()); // 媒体协议
protocolLogicMap.put(VIDEO_PKG, new MediaProtocol()); // 视频协议
protocolLogicMap.put(PHOTO_PKG, new MediaProtocol()); // 图库协议
protocolLogicMap.put(RADIO_PKG, new MediaProtocol()); // 收音机协议
protocolLogicMap.put(YX_VOICE_PKG, new YxProtocol()); // 内部协议
protocolLogicMap.put(SETTING_PKG, new SettingProtocol()); // 设置协议
protocolLogicMap.put(SYSTEM_UI_PKG, new SystemUIProtocol()); // 系统UI协议
protocolLogicMap.put(FACTORY_PKG, new FactoryProtocol()); // 工厂模式协议
protocolLogicMap.put(AVM_PKG, new AvmProtocol()); // 360环视协议
protocolLogicMap.put(DVR_PKG, new DvrProtocol()); // 行车记录仪协议
```
## 4. 协议命令常量
### 4.1 车辆控制类
| 命令常量 | 命令名称 | 说明 |
|----------|----------|------|
| CMD_OPEN_SUNROOF | openSunroof | 打开天窗 |
| CMD_CLOSE_SUNROOF | closeSunroof | 关闭天窗 |
| CMD_OPEN_WINDOW | openWindow | 打开车窗 |
| CMD_CLOSE_WINDOW | closeWindow | 关闭车窗 |
| CMD_OPEN_MIRROR_HEAT | openMirrorHeat | 打开后视镜加热 |
| CMD_OPEN_AVM | openAvm | 打开360环视 |
### 4.2 空调控制类
| 命令常量 | 命令名称 | 说明 |
|----------|----------|------|
| CMD_OPEN_AC | openAc | 打开空调 |
| CMD_CLOSE_AC | closeAc | 关闭空调 |
| CMD_SET_AC_WIND_SPEED_LEVEL | setAcWindSpeedLevel | 设置风速档位 |
| CMD_SET_AC_TEMP_VALUE | setAcTempValue | 设置温度 |
### 4.3 系统设置类
| 命令常量 | 命令名称 | 说明 |
|----------|----------|------|
| CMD_OPEN_BLUETOOTH | openBluetooth | 打开蓝牙 |
| CMD_OPEN_WIFI | openWifi | 打开WiFi |
| CMD_SET_VOLUME_TO_VALUE | setVolumeToValue | 设置音量 |
| CMD_SET_SCREEN_BRIGHTNESS_VALUE | setScreenBrightnessValue | 设置亮度 |
| CMD_SWITCH_VOICE_WAKEUP | switchVoicewakeup | 语音唤醒开关 |
| CMD_CUSTOM_WAKE_UP_WORD | customWakeupWord | 自定义唤醒词 |
### 4.4 媒体控制类
| 命令常量 | 命令名称 | 说明 |
|----------|----------|------|
| CMD_PLAY_PAUSE | playPause | 暂停播放 |
| CMD_PLAY_RESUME | playResume | 继续播放 |
| CMD_PLAY_PREV | playPrev | 上一首 |
| CMD_PLAY_NEXT | playNext | 下一首 |
| CMD_OPEN_APP | openApp | 打开应用 |
## 5. 消息格式
### 5.1 Bundle格式
```java
Bundle bundle = new Bundle();
bundle.putString(VoiceConst.PARAMS_CMD_KEY, "命令类型");
bundle.putInt(VoiceConst.PARAMS_NUM_KEY, 数值);
bundle.putBoolean(VoiceConst.PARAMS_SWITCH_KEY, 开关状态);
bundle.putString(VoiceConst.PARAMS_TYPE_KEY, "类型");
```
### 5.2 参数说明
| 参数Key | 类型 | 说明 |
|---------|------|------|
| PARAMS_CMD_KEY | String | 命令类型 |
| PARAMS_NUM_KEY | int | 数值参数 |
| PARAMS_SWITCH_KEY | boolean | 开关状态 |
| PARAMS_TYPE_KEY | String | 类型参数 |
## 6. 状态同步命令
| 命令 | 说明 |
|------|------|
| syncAcStatus | 同步空调状态 |
| syncBlueToothStatus | 同步蓝牙状态 |
| syncWiFiStatus | 同步WiFi状态 |
| syncMediaVolume | 同步媒体音量 |
| syncScreenBrightness | 同步屏幕亮度 |
| syncVoiceWakeUpStatus | 同步语音唤醒开关 |
---
*文档整理日期: 2026-05-27*
@@ -0,0 +1,156 @@
# YX_Voice 架构文档
## 1. 概述
YX_Voice 是车载语音助手服务模块,作为第三方语音助手(同行者TXZ)与车载各功能模块之间的桥梁,负责语音指令的分发、处理以及车辆状态的同步。
| 项目信息 | 说明 |
|----------|------|
| 包名 | `com.yaxon.voice` |
| 应用类型 | Android System Application |
| 进程类型 | 系统持久化进程 (`persistent=true`) |
| 共享UID | `android.uid.system` |
## 2. 整体架构
```
┌─────────────────────────────────────────────────────────────────────┐
│ YX_Voice 架构图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ TXZ协议 ┌──────────────────────────────┐ │
│ │ TXZ语音 │◄────────────►│ VoiceService │ │
│ │ 助手模块 │ │ (服务入口 & TXZ连接管理) │ │
│ └──────────────┘ └──────────────┬───────────────┘ │
│ │ │
│ ┌──────────────────────────┼────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────┐ ┌──────────┐│
│ │ ProtocolManager │ │ VoiceManager │ │ Helpers ││
│ │ (协议分发管理) │ │ (核心状态管理) │ │ (辅助类) ││
│ └──────────┬───────────┘ └──────────┬───────────┘ └──────────┘│
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Protocol Implementations │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ │ │
│ │ │ Voice │ │ Air │ │ Setting │ │ Media │ │ Car │ │ │
│ │ │Protocol │ │Protocol │ │Protocol │ │Protocol │ │Control │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ IPC Communication Layer │ │
│ │ YxIPC (跨进程通信框架) │ │
│ └──────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┼───────────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 音乐 │ │ 设置 │ │ 空调 │ │
│ │ 模块 │ │ 模块 │ │ 模块 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
## 3. 模块分层
| 层级 | 模块 | 职责 |
|------|------|------|
| 应用层 | VoiceService | 服务入口、生命周期管理 |
| 核心层 | ProtocolManager, VoiceManager | 协议分发、状态管理 |
| 协议层 | BaseProtocol及子类 | 具体协议解析与执行 |
| 辅助层 | Helpers, Receivers, Observers | 状态监听、辅助功能 |
| 通信层 | YxIPC, TxzProtocolManager | 跨进程通信 |
## 4. 核心模块
| 模块 | 职责 | 设计模式 |
|------|------|----------|
| VoiceService | 与TXZ语音助手建立连接、接收并分发语音指令 | - |
| ProtocolManager | 实现IPC消息监听接口、将消息分发给协议处理器 | 单例模式 |
| YxProtocolHandler | 维护包名到协议逻辑类的映射、在后台线程处理协议分发 | - |
| VoiceManager | 管理TXZ绑定状态、AVM/DVR前台状态、注册车辆状态回调 | 单例模式 |
| VoiceEventHelper | 封装所有语音控制操作、与各模块进行IPC通信 | - |
## 5. 通信架构
### 5.1 协议处理流程
```
TXZ语音指令
│
▼
TxzProtocolManager.setDefaultHandler()
│
├── 解析 commandType (协议名称)
├── 解析 Bundle 参数
├── 转换为JSON格式
│
▼
ProtocolManager.onResponse()
│
▼
YxProtocolHandler.onHandleProtocol()
│
▼
具体Protocol实现类.onProtocolLogic()
```
### 5.2 IPC通信
```
┌─────────────────┐ YxIPC ┌─────────────────┐
│ YX_Voice │◄──────────────►│ YX_Music │
│ (Server) │ │ (Client) │
└────────┬────────┘ └─────────────────┘
│
│ YxIPC
▼
┌─────────────────┐
│ YX_Setting │
└─────────────────┘
```
### 5.3 TXZ协议通信
```
┌─────────────────┐ TxzProtocolManager ┌─────────────────┐
│ YX_Voice │◄─────────────────────►│ TXZ语音助手 │
│ (ConnectionService) │ (AIDL Service) │
└─────────────────┘ └─────────────────┘
```
## 6. 模块依赖
```
YX_Voice
├── YX_IPC_SDK (IPC通信)
├── YX_Lib_CommonBase (公共基础库)
├── YX_AutoMotiveSDK (车载SDK)
├── library-release-0127.aar (TXZ协议库)
├── tools.jar (工具库)
├── framework_classes.jar (系统框架)
└── serial-0.1.6_classes.jar (串口库)
```
## 7. 包名依赖
| 包名 | 说明 |
|------|------|
| com.txzing.voiceassistant | TXZ语音助手 |
| com.yaxon.music | 音乐模块 |
| com.yaxon.radio | 收音机模块 |
| com.yaxon.hvac | 空调模块 |
| com.yaxon.bluetooth | 蓝牙模块 |
| com.yaxon.setting | 设置模块 |
| com.yaxon.carcontrol | 车辆控制模块 |
| com.yaxon.avm | 360环视模块 |
| com.yaxon.dvr | 行车记录仪模块 |
| com.android.systemui | 系统UI |
---
*文档整理日期: 2026-05-27*
@@ -0,0 +1,254 @@
# YX_Voice 模块详细文档
## 1. 应用入口 (YxApplication)
**文件路径**: `com/yaxon/voice/YxApplication.java`
**职责**:
- 继承 `AutoMotiveSDKApplication`,提供车载SDK上下文
- 初始化IPC通信框架
- 初始化崩溃处理机制
- 启动语音服务
**初始化流程**:
```
onCreate()
├── ToolInit.init() // 工具类初始化
├── CrashExceptionHandler // 崩溃处理
├── YxIPC.bind() // IPC绑定
│ └── ProtocolManager // 设置消息监听
├── PackagePermissionManager // 权限管理
└── startVoiceService() // 启动服务
```
---
## 2. 语音服务 (VoiceService)
**文件路径**: `com/yaxon/voice/service/VoiceService.java`
**继承关系**: `VoiceService extends BaseVoiceService implements ConnectionListener`
**职责**:
- 与TXZ语音助手建立连接
- 接收并分发语音指令
- 管理各模块监听器生命周期
**初始化流程**:
```
onCreate()
├── initTxz() // 初始化TXZ连接
│ ├── TxzProtocolManager.initialize()
│ ├── TxzProtocolManager.connect()
│ └── addConnectionListener()
├── registerTxzCommandReceiver() // 注册指令接收器
├── BlueToothHelper.register() // 蓝牙监听
├── VoiceManager.registerCallbacks() // 车辆状态监听
└── 注册各种Receiver和Callback
```
**TXZ连接成功同步**:
```java
public void onConnected() {
VoiceManager.getInstance().setVoiceBind(true);
VoiceManager.VOICE_EVENT_HELPER.sendVoiceBindSuccess(null);
AppHelper.getInstance().startScanApp();
initNotifyCarConfig(); // 车辆配置
initNotifyBluetoothStatus(); // 蓝牙状态
initNotifyVolumeStatus(); // 音量状态
initNotifyCarSettingStatus(); // 车辆设置状态
sendBrightnessStatus(); // 亮度状态
}
```
---
## 3. 协议管理器 (ProtocolManager)
**文件路径**: `com/yaxon/voice/core/ProtocolManager.java`
**设计模式**: 单例模式
**职责**:
- 实现IPC消息监听接口 (`MessageListener`, `RemoteListener`)
- 将消息分发给协议处理器
**类图**:
```
┌─────────────────────────────────┐
│ ProtocolManager │
├─────────────────────────────────┤
│ - sInstance: ProtocolManager │
│ - mProtocolHandler: IProtocolHandler │
├─────────────────────────────────┤
│ + getInstance() │
│ + setProtocolHandler() │
│ + onResponse() │ ← MessageListener
│ + onBindRemote() │ ← RemoteListener
│ + release() │
└─────────────────────────────────┘
```
---
## 4. 协议处理器 (YxProtocolHandler)
**文件路径**: `com/yaxon/voice/core/YxProtocolHandler.java`
**职责**:
- 维护包名到协议逻辑类的映射
- 在后台线程处理协议分发
---
## 5. 语音管理器 (VoiceManager)
**文件路径**: `com/yaxon/voice/core/VoiceManager.java`
**设计模式**: 单例模式
**职责**:
- 管理TXZ绑定状态
- 管理AVM/DVR前台状态
- 注册车辆状态回调(空调、设置、智能驾驶)
- 提供AudioTrackPlayer实例
**状态管理**:
```java
private boolean isVoiceBind; // TXZ绑定状态
private AtomicBoolean isAvm; // AVM前台状态
private AtomicBoolean isDvr; // DVR前台状态
```
---
## 6. 语音事件助手 (VoiceEventHelper)
**文件路径**: `com/yaxon/voice/helper/VoiceEventHelper.java`
**职责**:
- 封装所有语音控制操作
- 与各模块进行IPC通信
- 通知TXZ状态变化
**主要功能分类**:
| 类别 | 方法示例 | 说明 |
|------|----------|------|
| 空调控制 | sendAirSwitch, sendAcSwitch, sendAirTempTo | 温度、风速、模式控制 |
| 音量控制 | sendVolumeTo, sendVolumeMute, sendVolumeElevator | 音量调节、静音 |
| 屏幕控制 | setMpuBrightnessLevel, sendScreenOff, sendScreenClean | 亮度、熄屏、清洁 |
| 系统设置 | switchSystemLanguage, switchDistanceUnit | 语言、单位切换 |
| 车窗控制 | sendWindowSwitch, sendWindowPercent | 开关、百分比控制 |
| 语音设置 | sendSwitchVoiceWakeup, customWakeupWord, switchVoiceMan | 唤醒词、发音人设置 |
---
## 7. 接收器与观察者
### 7.1 BroadcastReceiver
| 类名 | 监听Action | 功能 |
|------|------------|------|
| BootReceiver | ACTION_BOOT_COMPLETED | 开机启动服务 |
| PowerReceiver | ACTION_SCREEN_ON/OFF | 屏幕亮灭监听 |
| NetworkChangedReceiver | WIFI_STATE_CHANGED | WiFi状态变化 |
| BluetoothMonitorReceiver | Bluetooth状态广播 | 蓝牙连接状态 |
| HomeStatusReceiver | ACTION_HOME_STATUS_CHANGED | 主页状态变化 |
| VolumeChangedCallback | 音量变化回调 | 音量变化监听 |
| SettingReceiver | STREAM_MUTE_CHANGED_ACTION | 静音状态变化 |
### 7.2 ContentObserver
| 类名 | 监听URI | 功能 |
|------|---------|------|
| ScreenSaversObserver | STATE_URI + STANDBY_DIALOG_STATE | 屏保状态 |
| MediaObserver | MEDIA_SOURCE_URI, MUSIC_INFO_URI | 媒体信息 |
| ReverseObserver | 倒车相关URI | 倒车状态 |
| BrightnessObserver | MPU_BRIGHTNESS_LEVEL | 亮度变化 |
| SettingCommonObserver | VIDEO_LIMIT_URI等 | 通用设置 |
---
## 8. 文件结构
```
YX_Voice/
├── src/main/java/com/yaxon/voice/
│ ├── YxApplication.java # 应用入口
│ ├── api/ # 接口定义
│ │ ├── IProtocolHandler.java
│ │ └── IProtocolLogic.java
│ ├── constant/ # 常量定义
│ │ ├── PackageConst.java
│ │ ├── VoiceNewProtocolConst.java
│ │ └── VoiceProtocolConst.java
│ ├── core/ # 核心类
│ │ ├── PackagePermissionManager.java
│ │ ├── ProtocolManager.java
│ │ ├── VoiceManager.java
│ │ └── YxProtocolHandler.java
│ ├── crash/ # 崩溃处理
│ │ └── CrashExceptionHandler.java
│ ├── helper/ # 辅助类
│ │ ├── AppHelper.java
│ │ ├── AudioFocusHelper.java
│ │ ├── AudioTrackPlayer.java
│ │ ├── BlueToothHelper.java
│ │ ├── BrightnessHelper.java
│ │ ├── ContactHelper.java
│ │ ├── HandlerHelper.java
│ │ ├── JsonHelper.java
│ │ ├── McuHelper.java
│ │ ├── VoiceEventHelper.java
│ │ ├── VolumeHelper.java
│ │ └── WifiHelper.java
│ ├── impl/ # 回调实现
│ │ ├── CarAirCallback.java
│ │ ├── CarIntelligentDrivingCallBack.java
│ │ └── CarSettingCallback.java
│ ├── protocol/ # 协议实现
│ │ ├── AirProtocol.java
│ │ ├── AvmProtocol.java
│ │ ├── BaseProtocol.java
│ │ ├── BluetoothProtocol.java
│ │ ├── CarControlProtocol.java
│ │ ├── DvrProtocol.java
│ │ ├── FactoryProtocol.java
│ │ ├── MediaProtocol.java
│ │ ├── SettingProtocol.java
│ │ ├── SystemUIProtocol.java
│ │ ├── VoiceProtocol.java
│ │ └── YxProtocol.java
│ ├── provider/ # ContentObserver
│ │ ├── BrightnessObserver.java
│ │ ├── MediaObserver.java
│ │ ├── ReverseObserver.java
│ │ ├── ScreenSaversObserver.java
│ │ └── SettingCommonObserver.java
│ ├── receiver/ # 广播接收器
│ │ ├── BluetoothMonitorReceiver.java
│ │ ├── BootReceiver.java
│ │ ├── HomeStatusReceiver.java
│ │ ├── NetworkChangedReceiver.java
│ │ ├── PowerReceiver.java
│ │ ├── SettingReceiver.java
│ │ ├── TestReceiver.java
│ │ └── VolumeChangedCallback.java
│ ├── service/ # 服务
│ │ ├── BaseVoiceService.java
│ │ └── VoiceService.java
│ └── utils/ # 工具类
│ ├── AppUtils.java
│ ├── BroadcastUtils.java
│ └── DataConvert.java
├── libs/ # 依赖库
│ ├── library-release-0127.aar
│ └── tools.jar
├── assets/tts/ # TTS音频资源
└── build.gradle
```
---
*文档整理日期: 2026-05-27*
@@ -0,0 +1,120 @@
# YX_Voice 配置文档
## 1. Gradle配置
```groovy
android {
defaultConfig {
applicationId "com.yaxon.voice"
}
aaptOptions {
noCompress "pcm" // 不压缩PCM音频文件
}
buildTypes {
release {
outputFileName = "YX_Voice.apk"
}
}
}
```
## 2. Manifest配置
```xml
<application
android:name=".YxApplication"
android:directBootAware="true"
android:persistent="true"
android:sharedUserId="android.uid.system">
```
## 3. 权限声明
```xml
<!-- 系统权限 -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
<uses-permission android:name="android.permission.FORCE_STOP_PACKAGES"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
<uses-permission android:name="android.permission.DEVICE_POWER"/>
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- 存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 其他权限 -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
```
## 4. IPC配置
```java
YxIpcConfig yxIpcConfig = new YxIpcConfig();
yxIpcConfig.addRemoteStartService(PackageConst.MUSIC_PKG);
yxIpcConfig.addRemoteStartService(PackageConst.SETTING_PKG);
yxIpcConfig.setRemoteListener(ProtocolManager.getInstance());
YxIPC.getInstance().bind(this, yxIpcConfig);
```
## 5. TXZ连接配置
```java
TxzProtocolManager.initialize(this);
TxzProtocolManager.connect(PackageConst.TXZ_PKG_NEW);
TxzProtocolManager.getConnection(PackageConst.TXZ_PKG_NEW).addConnectionListener(this);
```
## 6. 模块依赖
```
YX_Voice
├── YX_IPC_SDK (IPC通信)
├── YX_Lib_CommonBase (公共基础库)
├── YX_AutoMotiveSDK (车载SDK)
├── library-release-0127.aar (TXZ协议库)
├── tools.jar (工具库)
├── framework_classes.jar (系统框架)
└── serial-0.1.6_classes.jar (串口库)
```
## 7. 包名依赖
| 包名 | 说明 |
|------|------|
| com.txzing.voiceassistant | TXZ语音助手 |
| com.yaxon.music | 音乐模块 |
| com.yaxon.radio | 收音机模块 |
| com.yaxon.hvac | 空调模块 |
| com.yaxon.bluetooth | 蓝牙模块 |
| com.yaxon.setting | 设置模块 |
| com.yaxon.carcontrol | 车辆控制模块 |
| com.yaxon.avm | 360环视模块 |
| com.yaxon.dvr | 行车记录仪模块 |
| com.android.systemui | 系统UI |
## 8. 版本历史
| 版本 | 日期 | 作者 | 说明 |
|------|------|------|------|
| 1.0 | 2024-01-05 | 张博威 | 初始版本 |
| 1.1 | 2024-02-19 | mango | TXZ协议集成 |
| 1.2 | 2024-07-02 | ZhangBoWei | 协议抽象优化 |
| 1.3 | 2025-03-13 | - | 唤醒词修改功能 |
---
*文档整理日期: 2026-05-27*
@@ -0,0 +1,467 @@
# 同行者可见即可说自动注册框架对接方案 - 快速阅读版
> 来源文档:`F:/AI/Obsidian/项目文档/徐工/语音/同行者可见即可说自动注册框架对接方案.docx`
> 整理日期:2026-06-13
> 目标:把原 Word 文档整理成工程接入时能快速查阅的 Markdown 速查文档。
## 1. 一句话结论
“可见即可说”主要依赖 Android 无障碍服务扫描当前界面控件,通过控件的 `android:id`、`android:text`、`android:contentDescription`、`clickable`、`scrollable` 等信息自动注册语音热词。用户说出界面上可见或已通过隐藏标签声明的词后,语音助手命中热词,再回调触发目标控件事件。
如果界面元素无法被无障碍扫描,例如小程序、网页、自绘界面,则需要应用主动通过 AIDL 或广播向语音发送插词列表,语音命中后再把命中的插词回传给应用,由应用自行执行动作。
## 2. 整体链路
### 2.1 普通 Android 控件链路
1. 应用界面展示控件。
2. Android 无障碍服务扫描界面控件。
3. 语音助手读取控件文本或隐藏标签。
4. 语音助手把文本注册为可见即可说热词。
5. 用户说出对应词,例如“每日推荐”或“打开每日推荐”。
6. 热词模块命中控件节点。
7. 无障碍服务触发控件点击或滑动等事件。
8. 应用执行原控件逻辑。
### 2.2 图标控件链路
图标本身不显示文字时,需要通过 `contentDescription` 提供隐藏标签。例如图标按钮没有文字,但设置了 `android:contentDescription="向下滑"`,语音仍可识别“向下滑”并触发该图标。
### 2.3 非常规界面链路
适用于小程序、网页、自绘页面等无障碍拿不到元素的场景。
1. 应用整理当前界面可执行插词列表。
2. 应用通过 AIDL 或广播发送给语音。
3. 语音收到后注册插词。
4. 用户说出插词。
5. 语音识别命中。
6. 语音通过 AIDL 或广播把命中插词回传给应用。
7. 应用根据插词匹配具体控件或业务动作。
## 3. 接入总原则
| 原则 | 说明 |
|---|---|
| 必须有唯一 ID | 所有需要语音响应的控件都应设置唯一 `android:id`。 |
| 文本优先 | 文本控件优先使用 `android:text` 暴露插词。 |
| 图标补标签 | 图标类控件使用 `android:contentDescription` 暴露隐藏插词。 |
| 多说法用 `#` | 多个插词使用 `#` 分隔,例如 `打开#启动`。 |
| 状态要同步 | 播放/暂停、收藏/取消收藏等状态型图标必须动态更新 `contentDescription`。 |
| 点击看 `clickable` | 控件是否响应与 `clickable` 状态有关,`true` 表示可点击。 |
| 滚动看 `scrollable` | 滑动控件本身需要是可滑动节点,即 `Scrollable=true`。 |
| 弹窗用标准 Dialog | 建议使用标准 `Dialog` 或依附 Activity 的自定义 Dialog。 |
## 4. 常见控件接入方式
## 4.1 文本类控件
适用对象:`TextView`、`Button`、带文本的布局项等。
推荐做法:
```xml
<TextView
android:id="@+id/tv_daily_recommend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:text="每日推荐" />
```
支持泛化说法时,可使用 `contentDescription`:
```xml
<TextView
android:id="@+id/tv_daily_recommend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:text="每日推荐"
android:contentDescription="每日推荐#打开每日推荐#我想打开每日推荐" />
```
注意:
- `contentDescription` 中的插词文本必须包含 `text` 中的文本。
- 控件要能响应点击,需确保 `clickable=true` 或实际可点击。
## 4.2 图标类控件
适用对象:只有图片、没有文本的按钮或图标。
```xml
<ImageView
android:id="@+id/iv_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:contentDescription="搜索#查找" />
```
注意:
- 图标没有显示文本时,必须用 `contentDescription` 提供语音插词。
- 多个插词用 `#` 分隔。
- 控件是否响应仍与 `clickable` 状态有关。
## 4.3 同一图标多状态控件
典型场景:播放/暂停、收藏/取消收藏。
错误做法:
```xml
android:contentDescription="播放#暂停"
```
这种做法可能导致当前处于暂停状态时,用户说“暂停”反而触发点击,结果开始播放。
正确做法:根据状态动态更新。
```java
if (isPlaying) {
playButton.setContentDescription("暂停");
} else {
playButton.setContentDescription("播放");
}
```
原则:
- 当前正在播放,插词应是“暂停”。
- 当前已经暂停,插词应是“播放”。
- 当前已收藏,插词应是“取消收藏”。
- 当前未收藏,插词应是“收藏”。
## 4.4 常规滑动控件
适用对象:`ScrollView`、`RecyclerView`、`ListView` 等。
基础要求:
- 控件本身应可滑动,即无障碍节点 `Scrollable=true`。
- 如果每个 item 需要响应语音,item 需要有唯一 ID。
- item 可使用 `contentDescription` 声明插词。
- 如需响应 item 内部 child 控件,child 按文本类或图标类控件处理。
示例:
```xml
<RecyclerView
android:id="@+id/rv_station_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
```
item 示例:
```xml
<TextView
android:id="@+id/tv_station_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:text="88.6MHz"
android:contentDescription="88.6MHz#播放88.6" />
```
## 4.5 翻页控制
文档中特别强调:如果要让可见即可说支持“上一页 / 下一页”这类翻页逻辑,应用需要预留两个大小为 0 的控件。
要求:
- 控件大小为 0。
- 不能设置为 `GONE`。
- 分别添加“上一页”“下一页”等 `contentDescription`。
- 应用自己实现点击事件,点击后控制对应滚动控件翻页。
- 如果页面中有多个同方向滚动控件,只建议配置用户最可能使用的那个。
- 多组 0 大小翻页控件可能导致目标滚动控件不符合预期。
示例:
```xml
<View
android:id="@+id/voice_page_prev"
android:layout_width="0dp"
android:layout_height="0dp"
android:clickable="true"
android:contentDescription="上一页" />
<View
android:id="@+id/voice_page_next"
android:layout_width="0dp"
android:layout_height="0dp"
android:clickable="true"
android:contentDescription="下一页" />
```
```java
voicePagePrev.setOnClickListener(v -> recyclerView.smoothScrollBy(0, -recyclerView.getHeight()));
voicePageNext.setOnClickListener(v -> recyclerView.smoothScrollBy(0, recyclerView.getHeight()));
```
如果不预留翻页控制控件,默认不支持翻页逻辑。
## 4.6 SeekBar 等特殊滑动控件
适用对象:亮度、音量、进度条等 `SeekBar` 控件。
接入目标:支持类似“调高屏幕亮度”“降低音量”“屏幕亮度调大一点”等语音指令。
推荐做法:
```xml
<SeekBar
android:id="@+id/sb_screen_brightness"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="0dp"
android:paddingEnd="0dp"
android:contentDescription="屏幕亮度" />
```
父布局建议:
```xml
android:clipChildren="false"
android:clipToPadding="false"
```
原因:
- 框架根据控件宽高计算百分比拖动距离。
- 无法直接读取控件内部业务值。
- `SeekBar` 默认内边距会造成拖动精度误差。
- 将 `SeekBar` 内边距设为 0,并允许子控件超出父布局边界,可减少误差。
## 4.7 Switch 开关类控件
原生 `Switch`:通常无需额外适配。
自定义开关 View:需要重写 `onInitializeAccessibilityNodeInfo`。
```java
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
info.setCheckable(true);
info.setChecked(isChecked());
}
```
要求:
- 设置 `checkable=true`。
- 根据真实开关状态同步 `checked`。
- 状态变化时及时刷新无障碍信息。
## 5. 非常规界面 AIDL / 广播对接字段
文档流程图给出的字段含义如下。
## 5.1 应用发送给语音:注册或取消注册
| 字段 | 含义 | 示例 |
| ------------- | --------------------- | ------------------------------------------------------ |
| `key_type` | 要处理的类型,`2600` 表示可见即可说 | `2600` |
| `action` | 动作 | `shadow.register` / `shadow.unregister` |
| `packageName` | 应用包名,可不填 | `com.yaxon.carcontrol` |
| `className` | 页面类名,可不填 | `com.yaxon.carcontrol.activity.CarControlMainActivity` |
| `pageName` | 页面名称,可不填 | `voiceSetting` |
| `keywordList` | 插词列表,泛化词用 `#` 分隔 | `打开#启动` |
示例:
```json
{
"key_type": 2600,
"action": "shadow.register",
"packageName": "com.yaxon.carcontrol",
"className": "com.yaxon.carcontrol.activity.CarControlMainActivity",
"pageName": "voiceSetting",
"keywordList": "打开#启动"
}
```
取消注册:
```json
{
"key_type": 2600,
"action": "shadow.unregister",
"packageName": "com.yaxon.carcontrol",
"pageName": "voiceSetting",
"keywordList": "打开#启动"
}
```
## 5.2 语音回传给应用:命中执行
| 字段 | 含义 | 示例 |
| ---------- | --------------------- | ---------------- |
| `key_type` | 回传类型,`1600` 表示可见即可说执行 | `1600` |
| `action` | 动作 | `shadow.execute` |
| `cmd` | 命中的插词原文 | `打开` |
示例:
```json
{
"key_type": 1600,
"action": "shadow.execute",
"cmd": "打开"
}
```
应用收到后,应根据 `cmd` 做业务分发。
## 6. 已知问题与规避方式
## 6.1 `com.android.systemui` 扫描冲突
问题:
- 底部 touch 栏中可能存在很多图标插词。
- 这些插词可能和桌面或其他应用重复,例如“车主手册”。
- `com.android.systemui` 所在 window 可能一直可见,即使视觉上隐藏,也可能被无障碍服务扫描到。
- 用户说“车主手册”时,可能命中 SystemUI 上隐藏的控件,导致结果不符合预期。
建议:
- SystemUI 隐藏时,临时清空相关控件的 `android:text` 和 `android:contentDescription`。
- SystemUI 重新显示时,再恢复文本和隐藏标签。
示例:
```java
if (isSystemUiHidden) {
view.setContentDescription(null);
textView.setText("");
} else {
view.setContentDescription("车主手册");
textView.setText("车主手册");
}
```
## 6.2 同一图片不同插词
问题:
- 同一图片可能对应不同状态,例如播放/暂停。
- 如果同时注册“播放#暂停”,当前状态下可能执行反向动作。
建议:
- 状态变化时动态更新 `contentDescription`。
- 当前能执行什么动作,就只暴露什么插词。
## 6.3 部分弹框扫描不到
问题:
- 一些通过 Window 直接创建的弹框,可能与 Activity 界面不匹配或层级冲突。
- 无障碍服务可能扫描不到弹框里的控件。
建议:
- 优先使用标准 `Dialog`。
- 自定义 Dialog 应依附于 Activity。
- 避免直接用独立 Window 承载需要语音响应的弹框。
## 7. 工程接入检查清单
## 7.1 页面级检查
- [ ] 页面中需要语音响应的控件是否都有唯一 `android:id`。
- [ ] 文本控件是否有明确 `android:text`。
- [ ] 图标控件是否设置了 `android:contentDescription`。
- [ ] 多说法是否使用 `#` 分隔。
- [ ] 控件是否实际可点击或可滑动。
- [ ] 动态状态控件是否会更新 `contentDescription`。
- [ ] 列表 item 是否有唯一 ID 或稳定的可识别标签。
- [ ] 滚动页面是否需要预留上一页/下一页控制控件。
- [ ] 弹框是否使用标准 Dialog 或依附 Activity。
## 7.2 状态型控件检查
- [ ] 播放状态:播放中只暴露“暂停”。
- [ ] 暂停状态:暂停中只暴露“播放”。
- [ ] 已收藏:只暴露“取消收藏”。
- [ ] 未收藏:只暴露“收藏”。
- [ ] 开关类自定义 View 是否正确设置 `checkable` 和 `checked`。
## 7.3 SystemUI 检查
- [ ] 隐藏状态是否仍可能被无障碍扫描。
- [ ] 隐藏时是否清空 `text/contentDescription`。
- [ ] 显示时是否恢复 `text/contentDescription`。
- [ ] 是否存在与 Launcher 或 App 页面重复的插词。
## 7.4 非常规界面检查
- [ ] 是否确认无障碍无法获取页面元素。
- [ ] 是否整理了当前页面插词列表。
- [ ] 是否支持注册和取消注册。
- [ ] 是否能接收语音命中的 `cmd`。
- [ ] `cmd` 到业务动作是否有明确映射表。
- [ ] 页面销毁或切换时是否取消旧插词。
## 8. 当前徐工项目建议落地顺序
1. 先验证 `YX_Voice` 与同行者之间的 `shadow.register` / `shadow.execute` 链路。
2. 在 `YX_CarControl` 做一个测试插词,不绑定真实业务动作,只看日志是否能完整闭环。
3. 链路确认后,再选一个真实页面做最小闭环,例如语音设置页或声音设置页。
4. 对原生 Android 控件,优先走无障碍自动扫描,不额外走 AIDL/广播。
5. 对自绘、网页、小程序、复杂浮窗,走 AIDL/广播注册插词。
6. 对 SystemUI、Launcher、Music、Radio 这类插词多且易冲突的模块,先做页面级插词清单,再逐个治理冲突。
## 9. 日志验证建议
注册阶段重点看:
```text
registerVisibleSpeakKeywords
shadow.register
keywordList
```
命中阶段重点看:
```text
shadow.execute
cmd
handleVisibleSpeakExecute
```
异常排查重点看:
```text
keywordList is empty
TXZ not connected
protocolLogic is null
packageName or cmd is empty
```
## 10. 快速判断表
| 现象 | 优先排查 |
| ------------ | --------------------------------------------- |
| 说了没反应 | 控件是否有 ID、是否 clickable、是否注册成功 |
| 图标不能说 | 是否设置 `contentDescription` |
| 状态动作反了 | 是否动态更新 `contentDescription` |
| 列表不能翻页 | 是否预留 0 大小上一页/下一页控件 |
| SeekBar 拖动不准 | 是否去掉 SeekBar 内边距,父布局是否设置 `clipChildren=false` |
| 弹框扫不到 | 是否用标准 Dialog 或依附 Activity |
| 命中到错误应用 | 是否存在 SystemUI / Launcher / 当前页面插词冲突 |
| 非常规页面无元素 | 改走 AIDL 或广播插词注册 |
## 11. 后续需求明确后需要补充的内容
- 每个模块的可见即可说页面清单。
- 每个页面的控件 ID、显示文本、隐藏标签、状态规则。
- 插词冲突表,尤其是 SystemUI、Launcher、CarControl、Music、Radio。
- 非常规界面的 AIDL / 广播接口定义。
- 命中 `cmd` 到实际业务动作的映射表。
- 现场日志验证结果。
@@ -0,0 +1,84 @@
| 项目 | 值 | 类型 |
| --- | --- | --- |
| 请求名称 | `syncAppList` | String |
| 参数名称 | `appList` | String |
| 参数内容 | 应用列表 JSON 字符串 | String |
```java
BundleMutablePayload params = new BundleMutablePayload();
params.putString("appList", appListJson);
Request request = new Request("syncAppList", params);
sendCommandTxz(request);
```
注意:`appList` 参数本身是一个字符串,字符串内容才是应用列表 JSON,不是直接向参数中放入 JSON 对象。
##应用列表 JSON 格式
```json
{
"list": [
{
"app.name": "空调",
"app.package": "com.yaxon.hvac"
},
{
"app.name": "音乐",
"app.package": "com.yaxon.music"
}
]
}
```
### 3.3 字段定义
| 字段 | 类型 | 是否必填 | 说明 |
| --- | --- | --- | --- |
| `list` | JSON Array | 是 | 应用信息数组 |
| `app.name` | String | 是 | 提供给语音识别使用的应用名称 |
| `app.package` | String | 是 | 应用包名或约定的应用功能标识 |
每个应用元素当前只包含 `app.name` 和 `app.package` 两个字段。
```java
private void scanNativeApp() {
mThreadPoolTool.execute(() -> {
JsonHelper jsonHelper = new JsonHelper();
PackageManager packageManager =
YxApplication.getApplication().getPackageManager();
List<PackageInfo> packageInfoList =
packageManager.getInstalledPackages(0);
JSONArray jsonArray = jsonHelper.createJsonArray();
for (PackageInfo packageInfo : packageInfoList) {
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
String appName = applicationInfo.loadLabel(packageManager).toString();
String packageName = packageInfo.packageName;
if (mAppInfoMap.containsKey(packageName)) {
appName = mAppInfoMap.get(packageName);
}
JSONObject item = jsonHelper.createJsonObject(
new Pair<>("app.name", appName),
new Pair<>("app.package", packageName));
if (item != null) {
jsonArray.put(item);
}
}
for (Map.Entry<String, String> app : mCustomAppMap.entrySet()) {
JSONObject item = jsonHelper.createJsonObject(
new Pair<>("app.name", app.getValue()),
new Pair<>("app.package", app.getKey()));
if (item != null) {
jsonArray.put(item);
}
}
jsonHelper.putJsonArray("list", jsonArray);
VoiceManager.VOICE_EVENT_HELPER
.sendAppListScanComplete(jsonHelper.toJson());
});
}
```
@@ -0,0 +1,114 @@
# 车机系统 CPU / 内存监控问题报告
**日期**: 2026-09-05
**设备**: T9999916 · Android 10 · 6核(600%) · RAM 6.1G + swap 2.4G
**监控窗口**: 10:10 – 11:07(约 57 分钟,10 秒采样,持续报文压测中)
**监控方式**: `top` 10s 轮询 + `dumpsys meminfo` + 线程级 `top -H` + `/proc` 快照
---
## 结论总览
| 等级 | 进程 | 问题 | 证据强度 |
|---|---|---|---|
| 🔴 P0 | `com.txzing.voiceassistant` (PID 4733) | **Native 层内存泄漏 + 持续高 CPU** | 确凿 |
| 🟡 P1 | `com.yaxon.fota` (PID 5037) | 常驻高 CPU(升级服务压测下异常) | 高 |
| 🟡 P2 | `com.yaxon.dms` (PID 5056) | CPU 波动大(可能为正常视觉推理负载) | 中 |
| ⚪ 观察 | system_server / systemui / surfaceflinger / logd / logcollector | 二级负载,随报文流量波动 | 信息 |
---
## 🔴 P0: com.txzing.voiceassistant — Native 内存泄漏(核心发现)
### 内存增长曲线(RSS)
- 10:10 起始 ~562MB → 10:45 ~730MB → 11:00 ~770MB → **11:07 停止监控时 754MB(峰值 793MB)**
- **约 57 分钟增长 ~200MB(+35%)**,锯齿形单调上行,无平台期,无回落,未观察到 GC 释放
- 全局 MemAvailable 同步从 ~1.98G 降至 ~1.70–1.77G,与该进程泄漏吻合
### 泄漏定位在 Native 层(非 Java)
停止监控前最终 `dumpsys meminfo` 快照:
```
Pss Private Heap
Total Dirty Alloc
Native Heap 378885 378784 378358 ← 泄漏主体
Dalvik Heap 113174 113112 100795
TOTAL PSS: 704888 (Views: 9837, ViewRootImpl: 29)
```
- **Native Heap: 378MB**(Heap Alloc 378.4MB / Size 386MB,已接近上限)
- Java Heap: 仅 113MB — 若是 Java 泄漏 GC/lmkd 可回收,Native 才解释 RSS 单调涨
- Views: **9837 个 / ViewRootImpl: 29**(UI 对象也在累积,但量级远小于 native heap)
### CPU 与热点线程(共 104 线程)
```
TID 4942 MDS_SERVER R 94.5% 累计 57:31.58 ← 热点
TID 5018 RenderThread S 2.7% 累计 3:36.31
TID 4962 engine_proc S 2.7% 累计 1:21.77
```
- 进程 CPU 常驻 90–140%,峰值 193%
- **热点线程 TID 4942 = `MDS_SERVER`:单线程 94.5%,57 分钟内累计 57 分 31 秒 CPU 时间** —— 几乎吃满一个核,处于报文/流处理路径;内存与 CPU 异常同源
### 判断
MDS_SERVER(native 报文/数据服务线程)存在分配未释放(缓冲区/会话对象累积),同时该路径处理消耗持续 CPU。按当前 ~3.5MB/min 速率,连续运行数小时将逼近 Native Heap 上限并触发 OOM/崩溃。
### 建议动作
1. `debuggerd -j 4733` 抓 MDS_SERVER 调用栈,确认其在忙什么系统调用/库
2. 对比"停止发报文"前后 RSS 斜率,闭环验证报文触发
3. native 内存定位:`adb shell setprop wrap.com.txzing.voiceassistant "LIBC_MALLOC_DEBUG=1"` 后复测
4. 交 txzing 语音助手团队:重点审 MDS_SERVER 线程的报文缓冲生命周期
---
## 🟡 P1: com.yaxon.fota — 常驻高 CPU
- 全程 40–90% CPU(峰值 100%),RSS 稳定 ~141MB(无内存问题)
- 升级服务在报文压测下持续高 CPU 不正常——怀疑频繁解析/重试报文或轮询过密。建议查其日志与轮询策略
## 🟡 P2: com.yaxon.dms
- CPU 7–91% 波动,RSS 稳定 ~158MB。若 DMS 在做视觉推理属正常负载,峰值偏高,建议确认
---
## ⚪ 二级负载(未见泄漏,供参考)
| 进程 | CPU 范围 | 备注 |
|---|---|---|
| system_server | 20–91% 突发 | 随报文/窗口活动波动 |
| com.android.systemui | 17–64% | 正常偏高 |
| surfaceflinger | 20–47% | 渲染负载 |
| logd | 峰值 77% | 日志量随报文增大 |
| logcollector | 突发 116% | 周期性日志收集 |
| com.yaxon.datacenter_service | 14–57% | 疑似报文接收方,CPU 合理 |
---
## 附录:关键证据命令(可复现)
```bash
adb shell dumpsys meminfo com.txzing.voiceassistant # Native 378MB vs Java 113MB
adb shell top -H -b -n 1 -q -p 4733 # MDS_SERVER 94.5% / 57m31s
adb shell cat /proc/4733/task/4942/comm # → MDS_SERVER
adb shell ls /proc/4733/task | wc -l # → 104 线程
adb shell dumpsys meminfo -c # 全进程 PSS 排名
```
### 全进程 PSS Top(11:07 快照)
| 进程 | PID | PSS (KB) |
|---|---|---|
| com.txzing.voiceassistant | 4733 | 693,609 |
| system | 3288 | 120,731 |
| com.yaxon.dms | 5056 | 90,670 |
| com.yaxon.music | 4806 | 53,318 |
| com.yaxon.factory | 5074 | 38,285 |
| com.yaxon.voice | 4677 | 36,104 |
| com.yaxon.logcat | 5031 | 32,314 |
| com.yaxon.bluetooth | 4721 | 33,078 |
@@ -0,0 +1,174 @@
# 语音唤醒异常问题分析
## 概述
本文档记录从日志文件推断ADB命令的分析流程,用于语音唤醒异常问题的排查。
---
## 分析案例
### 输入:log 录音分析.md
**文件特征:**
- 文件大小:578KB
- 行数:9001行
- 无直接adb命令记录
### 分析步骤
#### 1. 查看文件开头内容
```text
pid: 3211
pid: 3294
...
Output thread 0xf2164c184c40, name AudioOut_D, tid 3103, type 0 (MIXER):
I/O handle: 13
Sample rate: 48000 Hz
HAL format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
Channel count: 8
...
```
#### 2. 识别日志类型
根据关键内容特征判断:
| 特征 | 内容示例 | 判断 |
|------|----------|------|
| 线程信息 | Output thread, Input thread | AudioFlinger dump |
| 音频参数 | Sample rate, HAL format, Channel count | 音频系统状态 |
| 设备信息 | AUDIO_DEVICE_OUT_BUS | 音频设备配置 |
| 时间戳统计 | Timestamp stats, jitterMs | 性能数据 |
**结论:** 这是一份 AudioFlinger 服务 dump 日志
#### 3. 推断ADB命令
根据日志类型推断:
| 日志类型 | 推断命令 |
| ----------------- | --------------------------------------- |
| AudioFlinger dump | `adb shell dumpsys media.audio_flinger` |
| AudioPolicy dump | `adb shell dumpsys media.audio_policy` |
| 完整音频状态 | `adb shell dumpsys audio` |
---
## 常见音频Dump命令汇总
| 命令 | 说明 | 应用场景 |
| --------------------------------------- | ---------------- | --------- |
| `adb shell dumpsys media.audio_flinger` | AudioFlinger服务状态 | 录音/播放问题排查 |
| `adb shell dumpsys media.audio_policy` | 音频策略服务状态 | 设备切换问题排查 |
| `adb shell dumpsys audio` | 完整音频系统状态 | 全面音频问题排查 |
| `adb shell dumpsys media.camera` | 摄像头服务状态 | 摄像头问题排查 |
---
## AudioFlinger Dump关键字解读
### Output Thread(输出线程)
| 字段 | 说明 |
|------|------|
| Sample rate | 采样率(如48000Hz) |
| HAL format | 硬件音频格式(PCM_16_BIT) |
| Channel count | 通道数 |
| Channel mask | 通道掩码 |
| Output device | 输出设备(如AUDIO_DEVICE_OUT_BUS) |
| Frames written | 已写入帧数 |
### Input Thread(输入线程)
| 字段 | 说明 |
|------|------|
| Audio source | 音频源(如AUDIO_SOURCE_MIC) |
| Input device | 输入设备 |
| Frames read | 已读取帧数 |
### Timestamp Stats(时间戳统计)
| 字段 | 说明 |
|------|------|
| jitterMs | 抖动(毫秒) |
| localSR | 本地采样率 |
| err | 错误计数 |
---
## 问题排查流程
### 语音唤醒异常排查步骤
1. **开启录音保存**
```bash
setprop persist.pcm.dump 9
```
2. **触发语音唤醒**
- 讲话唤醒同行者
- 可多次尝试
3. **获取音频系统状态**
```bash
adb shell dumpsys media.audio_flinger > audio_flinger.log
adb shell dumpsys audio > audio.log
```
4. **关闭录音保存**
```bash
setprop persist.pcm.dump 0
```
5. **提取录音文件**
```bash
adb pull /data/misc/audioserver/dump_audio_9.pcm
```
### 分析要点
| 问题类型 | 检查内容 |
|----------|----------|
| 无法唤醒 | Input thread状态、Frames read计数 |
| 声音异常 | jitterMs抖动、err错误计数 |
| 设备问题 | Input/Output device配置 |
| 格式问题 | HAL format、Sample rate |
---
## 从日志推断命令的方法总结
### 判断流程
```
1. 查看文件开头内容
↓
2. 识别关键特征字段
↓
3. 对照常见dump格式
↓
4. 推断具体adb命令
```
### 特征对照表
| 日志特征 | 对应命令 |
|----------|----------|
| Output/Input thread | dumpsys media.audio_flinger |
| AudioPolicy相关 | dumpsys media.audio_policy |
| ActivityManager | dumpsys activity |
| PackageManager | dumpsys package |
| SurfaceFlinger | dumpsys SurfaceFlinger |
---
## 参考文件
- [[项目文档/徐工/log 录音分析.md]] - 原始日志文件
- [[项目文档/徐工/语音.md]] - 语音模块开发文档
---
*文档整理日期: 2026-05-27*
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

@@ -0,0 +1,27 @@
# Repository Guidelines
## Project Structure & Module Organization
This repository is a multi-module Android project defined in `settings.gradle`. App modules such as `YX_Voice`, `YX_Radio`, `YX_Factory`, and `YX_SystemUI` follow the standard `src/main`, `src/test`, and `src/androidTest` layout. Shared code lives in SDK and base modules like `YX_BluetoothSDK`, `YX_AutoMotiveSDK`, `YX_Lib_CommonBase`, and `YX_Lib_UIStyleBase`. Prebuilt platform jars are stored in `sdklibs/`, generated APKs are copied into `third_apps/<Module>/`, and root scripts such as `build_apk.sh` handle CI-oriented packaging.
## Build, Test, and Development Commands
Use the Gradle wrapper from the repository root.
- `.\gradlew clean` clears root and module build outputs.
- `.\gradlew :YX_Voice:assembleDebug` builds one app module for local iteration.
- `.\gradlew :YX_BluetoothSDK:testDebugUnitTest` runs one module's JVM tests.
- `.\gradlew :YX_Factory:connectedDebugAndroidTest` runs instrumentation tests on a connected device.
- `bash build_apk.sh debug` or `bash build_apk.sh release` performs the multi-APK packaging flow used for `third_apps/`.
Prefer module-scoped commands over full-repo builds because the workspace is large.
## Coding Style & Naming Conventions
Follow existing Android Java and Gradle conventions: 4-space indentation, braces on the same line, `UpperCamelCase` for classes, `lowerCamelCase` for methods and fields, and `SCREAMING_SNAKE_CASE` for constants. Keep package names under `com.yaxon...` or the existing module namespace. Name resources by type and feature, such as `activity_back_car.xml`, `ic_launcher.png`, or `radio_widget_info.xml`.
## Testing Guidelines
Unit tests live under `src/test/java`; device tests live under `src/androidTest/java`. Existing test classes use the `ExampleUnitTest` and `ExampleInstrumentedTest` pattern, but new tests should use feature-specific names such as `BluetoothPbapClientTest`. Add or update tests for module behavior changes when practical, especially in shared SDK modules.
## Commit & Pull Request Guidelines
Recent history uses scoped subjects like `feat(YX_SystemUI): ...`, `fix(YX_Radio): ...`, and `modify(YX_Voice): ...`. Keep that format: `<type>(<module>): <summary>`. Pull requests should state the affected modules, user-visible behavior changes, test coverage or device validation performed, and screenshots when UI layouts or drawables change.
## Security & Configuration Tips
Do not commit real secrets, device-specific `local.properties`, or modified signing material. `config.gradle` currently references shared signing and SDK paths, so treat local environment changes as workstation-only unless the team explicitly standardizes them.
@@ -0,0 +1,159 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
车载 Android 应用系统,多模块架构。主要模块包括语音助手、车控、媒体、系统UI等。当前分支 `develop_xcmg` 为徐工(xcmg)项目开发分支。
## Build Commands
```bash
# 编译单个模块 (Debug)
./gradlew :YX_Voice:assembleDebug
./gradlew :YX_CarControl:assembleDebug
# 编译单个模块 (Release)
./gradlew :YX_Voice:assembleRelease
# 清理构建
./gradlew clean
# 全量编译所有 APK (使用脚本)
./build_apk.sh # Debug 版本
./build_apk.sh release # Release 版本
# 编译 YX_Support jar 包
./gradlew makejar # 输出到 YX_Support/build/libs/
```
## Architecture Overview
### 模块分层结构
```
┌─────────────────────────────────────────────────────────────────────┐
│ Application Layer (应用层) │
│ YX_Voice | YX_CarControl | YX_Music | YX_Radio | YX_Video | ... │
└─────────────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ SDK Layer (SDK层) │
│ YX_AutoMotiveSDK | YX_IPC_SDK | YX_MediaSDK | YX_MusicSDK | ... │
└─────────────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Base Library (基础库层) │
│ YX_Lib_CommonBase | YX_Lib_UIStyleBase | YX_Support │
└─────────────────────────────────────────────────────────────────────┘
```
### 跨进程通信 (YxIPC)
核心跨进程通信机制,位于 `YX_IPC_SDK` 模块。
**使用方式:**
```java
// 初始化 (Application.onCreate)
YxIPC.getInstance().bind(application, ipcConfig);
// 发送消息 (无回调)
YxIPC.getInstance().sendRemoteNoCall(remotePackage, bundle);
// 发送消息 (有回调)
YxIPC.getInstance().sendRemoteByCall(remotePackage, bundle, sessionId, callback);
// 注册消息监听
YxIPC.getInstance().addMessageListener(messageListener);
```
**关键类:** `YxIPC.java`, `YxMessenger.java`, `MessageListener.java`
### 语音模块架构 (YX_Voice)
语音助手模块,与同行者(TXZ)语音服务通信。
**核心类:**
| 类 | 路径 | 职责 |
|---|------|------|
| VoiceManager | core/VoiceManager.java | 单例管理器,包含 VoiceEventHelper 实例 |
| VoiceEventHelper | helper/VoiceEventHelper.java | 事件处理核心,所有语音操作入口 |
| ProtocolManager | core/ProtocolManager.java | IPC 协议分发器 |
| YxProtocolHandler | core/YxProtocolHandler.java | 根据包名分发协议到对应 Protocol |
| VoiceProtocol | protocol/VoiceProtocol.java | TXZ 协议解析 |
| CarControlProtocol | protocol/CarControlProtocol.java | 车控状态同步协议 |
**协议常量 (两套):**
- `VoiceNewProtocolConst.java` (YX_Voice) - TXZ 协议常量,与语音服务通信
- `VoiceNewConstant.java` (YX_Lib_CommonBase) - IPC 协议常量,模块间通信
### 车控模块 (YX_CarControl)
车辆硬件控制模块。
**关键类:**
- `VoiceSettingManager.java` - 语音控制入口,接收 IPC 消息调用硬件
- `CarControlsManager` (YX_AutoMotiveSDK) - 硬件控制 API
## Module Dependencies
模块间依赖关系 (典型):
```
YX_Voice → YX_IPC_SDK → YX_Lib_CommonBase → YX_Support
YX_Voice → YX_AutoMotiveSDK → (framework_classes.jar)
YX_CarControl → YX_IPC_SDK → YX_Lib_CommonBase
YX_CarControl → YX_SettingSDK → YX_AutoMotiveSDK
```
**平台 SDK:**
- `sdklibs/framework_classes.jar` - Android framework 类 (需要 hidden API)
- `sdklibs/yx_support_classes.jar` - YX_Support 编译输出
- `sdklibs/android.car.jar` - Android Car API
## Protocol Flow
语音控制指令流向:
```
TXZ语音 → VoiceProtocol → VoiceEventHelper → YxIPC → VoiceSettingManager → CarControlsManager → 硬件
```
状态同步流向:
```
硬件 → CarControlsControllerCallBack → VoiceSettingManager → YxIPC → CarControlProtocol → VoiceEventHelper → TXZ
```
## Package Names
主要模块包名:
| 模块 | 包名 |
|------|------|
| YX_Voice | com.yaxon.voice |
| YX_CarControl | com.yaxon.carcontrol |
| TXZ语音 | com.txzing.voiceassistant |
| YX_Music | com.yaxon.music |
| YX_Radio | com.yaxon.radio |
| YX_Hvac | com.yaxon.hvac |
| YX_Launcher | com.yaxon.launcher |
| YX_SystemUI | com.android.systemui |
## Signing
使用平台签名 (platform.jks):
- 密钥别名: yx
- 密钥密码: yx123456
- 所有模块共用同一签名配置 (config.gradle)
## Branch Information
- `develop_xcmg` - 徐工项目 (当前分支)
- `x9hp_dw` - 三一项目
- `master` - 主分支
## Special Notes
1. **编译环境:** BuildTools 30.0.3, Gradle Plugin 4.1.3, compileSdk 29
2. **平台 API:** 需要 `sdklibs/framework_classes.jar` 访问 hidden API
3. **混淆:** Release 版本开启混淆 (minifyEnabled true)
4. **输出路径:** Release APK 输出到 `third_apps/<ModuleName>/`
5. **语音唤醒广播:** `YXUtils.ACTION_START_TXZVOICE` / `android.intent.action.start_txzvoice`
@@ -0,0 +1,58 @@
百度Deepway开发版本;
**编译yx_support指令**
1.应用源码根目录下执行指令:gradlew makejar(可直接在AS上的Terminal中输入该指令)
2.生成路径:YX_Support/build/libs
基于三一项目的开发分支建立的分支进行开发;
U盘升级程序编译步骤:
1、进入项目根目录:/sanyi/tangzhigang/android10_x9hp/(以caiqinghui服务器为例)执行:
./build.sh all
./build.sh pac
2、进入semdrive 的编译目录:/sanyi/tangzhigang/android10_x9hp/vendor/semidrive/rtos 执行:
source ./setenv.sh
make ota
编译完成后输出升级程序目录:
\sanyi\tangzhigang\android10_x9hp\vendor\semidrive\rtos\ota_X9HP_Ref_Isolation_Android_Linux_Serdes_8G_3200
该目录会有两个文件:x9hp_ref-ota.zip、x9hp_ref-target_files.zip 将x9hp_ref-ota.zip文件拷到U盘根目录升级即可;
外部按键/方向盘唤醒或关闭语音,通过广播方式:
1.唤醒语音广播:YXUtils.ACTION_START_TXZVOICE 或者 “android.intent.action.start_txzvoice”
2.关闭语音广播:YXUtils.ACTION_CLOSE_TXZVOICE 或者 “android.intent.action.close_txzvoic”
三一编译版本拉取代码步骤:
1.拉取主仓库代码:
1)git reset HEAD --hard
2)git pull origin x9hp_dw
2.进入yx_app目录后拉取应用层代码
1)git reset HEAD --hard
2)git pull
请按照上诉步骤拉取代码,防止代码修改未编译进去!!!
样机选择操作:
1、进入Z:\znzc\014_sanyi\android10_x9hp\vendor\semidrive\rtos\rtos\lk_boot\exdev\lcm\max96xxx\src\文件夹;
2、编辑max96789.c文件 修改如下对应数据:
#define BOARD_B1_EN 1 // B1
#define BOARD_B1_EN 0 // A1
关于字体修改:
1.当前全局默认是 思源黑体 CN Normal
常规:setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
加粗:setTypeface(Typeface.DEFAULT, Typeface.BOLD);
2.DINPro 关于字体修改
常规:setTypeface(Typeface.DINPro, Typeface.NORMAL);
加粗:setTypeface(Typeface.DINPro, Typeface.BOLD);
工程模式密码:
高权限密码:xgroot+系统年月日(例xgroot20260606)
低权限密码:xg+系统年月日(例xg20260606)
注:不同密码看到的工程模式功能列表不同
+825
View File
@@ -0,0 +1,825 @@
# 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.
@@ -0,0 +1,239 @@
# Codex Xugong
## Workspace
- Root: `D:\zdCode\xugong`
- Project type: multi-module Android project
- Main module registry: `settings.gradle`
## Fast Start
下次进入这个项目,优先按下面顺序建立上下文,不需要全仓扫描。
1. 先确认当前任务目标
2. 再定位相关模块
3. 只沿调用链读取必要代码
4. 需要修改时,再扩展到相邻模块
推荐开场信息:
```text
继续处理 xugong 项目
当前问题:<一句话描述>
相关模块:<模块名列表>
已知结论:<如果有,写 1-3 句>
```
示例:
```text
继续处理 xugong 项目的蓝牙通讯录重复同步问题
相关模块:YX_Voice / YX_BluetoothSDK / YX_BlueTooth
已知结论:syncBtPhoneContactStatus 在连接成功后和 PBAP 通讯录下载完成后各发送了一次
```
## Module Heuristics
当前仓库很大,但常见问题可先按职责收敛:
- `YX_Voice`: 语音协议、事件上报、蓝牙状态同步到语音侧
- `YX_BluetoothSDK`: 蓝牙协议、PBAP、联系人/通话记录同步
- `YX_BlueTooth`: 蓝牙业务 UI、ViewModel、页面触发入口
- `YX_Lib_CommonBase`: 常量、公共协议字段
如果问题和蓝牙联系人同步有关,默认先看:
- `YX_Voice/src/main/java/com/yaxon/voice/helper/VoiceEventHelper.java`
- `YX_Voice/src/main/java/com/yaxon/voice/protocol/YxProtocol.java`
- `YX_Voice/src/main/java/com/yaxon/voice/receiver/BluetoothMonitorReceiver.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtContactsManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/platform/BluetoothPbapClient.java`
## Current Topic
### Issue
日志文件:
- `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0513_1747_33_545.log`
问题:
- 打开蓝牙并连接手机后,`syncBtPhoneContactStatus` 发送了 2 次
### Current Conclusion
`syncBtPhoneContactStatus` 的两次发送不是单纯由“蓝牙打开”触发,而是两段不同链路:
1. 蓝牙连接成功后立即上报一次当前联系人
2. 首次连接触发 PBAP 通讯录下载,下载完成后再次上报一次
关键日志时间点:
- `2026-05-13 17:48:07.393` 第一次发送 `syncBtPhoneContactStatus`
- `2026-05-13 17:48:13.662` `BtContactsManager` 触发 `phoneBookPullRequest`
- `2026-05-13 17:48:14.270` 第二次发送 `syncBtPhoneContactStatus`
### Relevant Code Paths
当前已确认的主要入口:
- `YX_Voice/.../YxProtocol.java`
- `BLUETOOTH_CONNECT_STATUS` 中调用 `VoiceManager.VOICE_EVENT_HELPER.notifyContact()`
- `YX_Voice/.../BluetoothMonitorReceiver.java`
- `ACTION_CONTACTS_DOWNLOAD_COMPLETE` 中调用 `VoiceManager.VOICE_EVENT_HELPER.notifyContact()`
- `YX_Voice/.../VoiceEventHelper.java`
- `initNotifyBluetoothStatus()` 中会补发一次 `notifyContact()`
联系人下载链路:
- `BtContactsManager.onPbabStatusChanged(...)`
- `BluetoothPbapClient.phoneBookPullRequest(...)`
- `ContactUtils.getAllContacts(...)`
- 完成后回到语音侧再触发联系人同步
## Suggested Fix
优先按“数据就绪事件”而不是“连接事件”驱动联系人同步。
建议顺序:
1. 去掉连接成功后立即 `notifyContact()` 的路径
2. 保留 PBAP 下载完成后的联系人同步
3. 保留显式查询联系人时的主动同步
4. 给 `notifyContact()` 增加幂等保护
推荐幂等键:
- `activeDeviceMac`
- `contactJson` 的 hash
推荐规则:
- 同一 `mac`
- 同一份联系人内容
则跳过重复发送 `syncBtPhoneContactStatus`
### Minimal Safe Change
如果只做最小改动,先改这一条:
- 删除 `YxProtocol.BLUETOOTH_CONNECT_STATUS` 里的 `notifyContact()`
保留:
- PBAP 下载完成广播触发的 `notifyContact()`
- 手动查询联系人时触发的 `notifyContact()`
同时把初始化补发改成带条件:
- 仅当当前已连接
- 且本地已有该 `mac` 的联系人缓存
才补发联系人
## Re-entry Notes
如果下次关闭终端后重新进入,直接:
```powershell
cd D:\zdCode\xugong
```
然后对 Codex 说:
```text
按 codex_xugong.md 继续
```
或者补一句:
```text
按 codex_xugong.md 继续,开始改蓝牙联系人重复同步
```
## Session Log
### 2026-05-13
当前分析聚焦在“蓝牙联系人同步到语音侧”的链路。
#### Logs Conclusion
日志文件:
- `C:\Users\Administrator\Desktop\徐工日志\yx_log\2026_0513_1747_33_545.log`
结论:
- `syncBtPhoneContactStatus` 确实发送了 2 次
- 不是单纯“蓝牙打开”导致 2 次
- 实际是:
1. 蓝牙连接成功后先上报一次联系人
2. PBAP 通讯录下载完成后又上报一次联系人
#### Uncommitted Code Review
已分析当前工作区未提交改动,重点文件:
- `YX_Voice/src/main/java/com/yaxon/voice/helper/ContactHelper.java`
- `YX_Voice/src/main/java/com/yaxon/voice/helper/VoiceEventHelper.java`
- `YX_Voice/src/main/java/com/yaxon/voice/protocol/BluetoothProtocol.java`
- `YX_Voice/src/main/java/com/yaxon/voice/helper/BlueToothHelper.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtSettingManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtContactsManager.java`
- `YX_BluetoothSDK/src/main/java/com/yaxon/bluetoothsdk/modules/BtVoiceManager.java`
当前未提交改动的主要目的:
- 修复“切换不同手机后,把多个手机的通讯录一起同步给语音侧”的问题
当前结论:
1. 这次改动的主方向是对的
2. 关键修复已经落在“按当前活动设备 `macAddress` 过滤联系人”
3. `notifyContact()` 不再直接读全量蓝牙联系人,而是:
- 先取 `activeDeviceMac`
- 再调用 `getContactsNew(macAddress)`
4. `ContactHelper.getContactsNew(macAddress)` 已经对 `content://com.yaxon.bluetooth.contact/table_contacts` 加了:
- `selection = "macAddress = ?"`
- `selectionArgs = new String[]{macAddress}`
5. 数据表列名 `macAddress` 已核对,和 `YX_BluetoothSDK` 里的数据库定义一致
这意味着:
- “多台手机联系人混发”的问题,当前未提交代码已经基本修住
#### Remaining Risks
这批改动还没有解决的点:
1. 仍可能对同一台手机重复同步 2 次联系人
- 连接成功一次
- PBAP 下载完成一次
2. `notifyContact()` 现在强依赖 `activeDeviceMac`
- 如果某个时序里先触发 `notifyContact()`
- 但语音侧还没收到 `cmd.syncBtActiveDeviceMac`
- 这次同步会直接跳过,日志会打印 `notifyContact: no active device`
#### Next Step
明天继续时,建议在当前基础上做这两件事:
1. 给 `notifyContact()` 增加幂等保护
- 去重键建议:`macAddress + contactJson/hash`
2. 收敛联系人同步触发时机
- 区分“连接事件”
- 区分“PBAP 下载完成事件”
- 避免同一份联系人重复发给语音侧
#### Resume Prompt
明天可直接输入:
```text
按 codex_xugong.md 继续,基于当前未提交代码继续处理蓝牙联系人同步去重
```
@@ -0,0 +1,145 @@
# 联系人 JSON 数据组装说明
## JSON 结构
最终组装为一个根对象,根对象下只有 `contact` 数组:
```json
{
"contact": [
{
"name": "张三",
"number": "13800138000"
},
{
"name": "李四",
"number": "+8613900139000"
}
]
}
```
空联系人时返回:
```json
{
"contact": []
}
```
## 组装规则
1. 每个联系人 item 只包含两个字段:
```json
{
"name": "联系人姓名",
"number": "联系人号码"
}
```
2. 优先使用 `number_detail` 字段。
`number_detail` 是 JSON 数组字符串,用于表示同一个联系人下的多个号码,例如:
```json
["13800138000", "13900139000"]
```
3. 如果 `number_detail` 不为空,则解析其中每一个号码,并分别展开成独立联系人 item。
例如:
```json
{
"name": "张三",
"number_detail": ["13800138000", "13900139000"]
}
```
会组装为:
```json
{
"contact": [
{
"name": "张三",
"number": "13800138000"
},
{
"name": "张三",
"number": "13900139000"
}
]
}
```
4. 如果 `number_detail` 为空,或者解析失败,则使用 `number` 字段兜底。
5. 号码为空时不加入数组。
6. 号码会做格式清洗,只去除以下字符:
```text
- 空格 ( )
```
保留数字和 `+`。
例如:
```text
+86 138-0013-8000 -> +8613800138000
138 0013 8000 -> 13800138000
```
7. 会按 `name + "|" + number` 去重,同一姓名同一号码只保留一条。
## 核心组装代码
```java
JSONArray contactArray = new JSONArray();
Set<String> uniqueKeys = new HashSet<>();
while (cursor.moveToNext()) {
String name = cursor.getString(idxName);
String number = cursor.getString(idxNumber);
String numberDetail = cursor.getString(idxNumberDetail);
if (!TextUtils.isEmpty(numberDetail)) {
try {
JSONArray numbers = new JSONArray(numberDetail);
for (int i = 0; i < numbers.length(); i++) {
addContactItem(contactArray, uniqueKeys, name, numbers.optString(i, ""));
}
} catch (Exception e) {
addContactItem(contactArray, uniqueKeys, name, number);
}
} else {
addContactItem(contactArray, uniqueKeys, name, number);
}
}
JSONObject root = new JSONObject();
root.put("contact", contactArray);
return root.toString();
```
```java
private void addContactItem(JSONArray contactArray, Set<String> uniqueKeys,
String name, String number) {
if (TextUtils.isEmpty(number)) return;
String formattedNumber = number.replaceAll("[-\\s()]", "");
if (TextUtils.isEmpty(formattedNumber)) return;
String key = (name == null ? "" : name) + "|" + formattedNumber;
if (uniqueKeys.contains(key)) return;
uniqueKeys.add(key);
JSONObject item = new JSONObject();
item.put("name", name);
item.put("number", formattedNumber);
contactArray.put(item);
}
```
@@ -0,0 +1,72 @@
# xugong_init
## 用途
这个文件记录 `xugong` 仓库的本地初始化结果,以及下次进入仓库时的最小启动步骤。
## 当前结论
当前仓库已经完成基础初始化校验,不需要每次重新初始化。
已确认可用:
- 仓库根目录:`D:\zdCode\xugong`
- 当前分支:`develop_xcmg`
- Android SDK:由 `local.properties` 指向本机 SDK
- JDK:`Java 1.8`
- Gradle Wrapper:`gradlew.bat` 可运行
- Gradle:首次 Wrapper 解压和项目识别已完成
## 下次进入项目
直接进入仓库:
```powershell
cd D:\zdCode\xugong
```
如果只是开始开发,通常不需要重新初始化,直接执行需要的任务即可。
例如:
```powershell
.\gradlew.bat projects
.\gradlew.bat :YX_Voice:assembleDebug
.\gradlew.bat :YX_Launcher:assembleDebug
```
## 什么时候需要重新检查初始化
只有以下情况才建议重新检查:
- 换了电脑
- 重装了 JDK / Android Studio / Android SDK
- 删除了 `~/.gradle` 缓存
- `local.properties` 被改动或丢失
- Gradle 报环境错误
- Android SDK 路径失效
## 快速排查命令
检查 Java:
```powershell
java -version
```
检查 Gradle Wrapper:
```powershell
.\gradlew.bat -version
```
检查模块识别:
```powershell
.\gradlew.bat projects
```
## 备注
- 仓库当前存在未提交改动,操作时不要随意执行重置或清理命令。
- 该工程是多模块 Android 项目,模块清单以 `settings.gradle` 为准。