06 · 台架 mock 配方与测试矩阵
在 fermi 台架上把「能量回收自定义 slider」从无到有置为可测状态的完整配方,以及 N801PROB-132401 修复的验收矩阵。全部命令来自实机实操(transcript 2026-10-12/13), 设备
adb -s 0304507188488540(fermi, A17)。案例背景见 05 篇。
1. 环境与通道确认
adb -s 0304507188488540 root
adb -s 0304507188488540 shell "dumpsys -l 2>/dev/null | grep -i vehicle"
# fermi 是 AIDL VHAL,服务名:android.hardware.automotive.vehicle.IVehicle/micar后文统一简写:
S="adb -s 0304507188488540 shell"
M="dumpsys android.hardware.automotive.vehicle.IVehicle/micar --mock_from_car"
G="dumpsys android.hardware.automotive.vehicle.IVehicle/micar --get"2. 信号三件套(propId 权威值)
| 信号 | propId(hex) | propId(dec) | 关键值 | 作用 |
|---|---|---|---|---|
| BAN_REASON | 0x6140757b | 1631614331 | 0=NO_INHIBIT(可用) | 禁用原因;非 0 则整组禁用 |
| BRAKING_MODE | 0x61408120 | 1631617312 | 3=ADVANCED(自定义);台架真实值 2 | slider 可见性门控:BrakingStrengthPreferenceController.java setVisible(getIntProperty(BRAKING_MODE)==Energy.RegenerationMode.ADVANCED)(dev :203-205 / rls :140-141) |
| ENERGY_REGENERATION_LEVEL | 0x6140757d | 1631614333 | 0~100 | slider 值本体 |
- propId 权威获取法:对编译产物
javap -constants读MiCarPropertyIds$DrivingCtrl.class常量(mi.car.config aar 无本地源码);或运行时日志实证(SET/onChangeEvent 行里的0x6140757d)。 - 枚举值 ADVANCED=3 的本地证据:
Energy.RegenerationMode.ADVANCED被 controller 引用, 运行时 App 点「自定义」SET 的就是 3(见 §4 的onDataVerifyFailed propVal: 3)。
3. mock 语法与读值
# 读值(status 与值都要看)
$S "$G 0x61408120"
# => VehiclePropValue{timestamp:..., areaId:0, prop:1631617312, status: AVAILABLE, value: RawPropValues{int32Values: [2], ...}}
# mock 注入(从"车端"方向注入,App 看到的是属性变化)
$S "$M <propId> -a <areaId> --stat <status> -i <int值>"
# --stat 0 = AVAILABLE;台架真实 status=4 是全页禁用真因,必须翻 0status=4 实证:未 mock 时 $G 0x6140757d 返回 status: 4, int32Values: [0]——
LEVEL 属性在台架车上原生不可用,这就是页面默认全灰的直接原因(BRAKING_MODE 同理,真实 status=4, [2])。
4. 置「能量回收可用」完整配方
4.1 最终版(修复验证时实测通过)
# STEP 1 BAN_REASON 变化序列(值不变 App 缓存不刷新,必须翻转一次)
$S "$M 0x6140757b -a 0 --stat 0 -i 1"; sleep 1
$S "$M 0x6140757b -a 0 --stat 0 -i 0"
# STEP 2 BRAKING_MODE:先恢复真实值 2 翻 AVAILABLE,再注入 ADVANCED=3
$S "$M 0x61408120 -a 0 --stat 0 -i 2"; sleep 1
$S "$M 0x61408120 -a 0 --stat 0 -i 3"
# STEP 3 LEVEL 翻 AVAILABLE(初值任意,给 0)
$S "$M 0x6140757d -a 0 --stat 0 -i 0"
# STEP 4 冷启动页面(App 有静态缓存,必须 force-stop 后拉起)
$S "am force-stop com.android.car.settings"
$S "monkey -p com.android.car.settings -c android.intent.category.LAUNCHER 1"完成后:设置→驾驶偏好→能量回收强度出现「自定义」可选,选中后底部出现 0~100 slider。
4.2 为什么必须「值变化序列」
App 侧 CarPropertyCacheManager 对相同值去重,只翻 status 不改值,UI 不刷新。
实操对比:先 stat 0 -i 2(值与台架真实值相同)→ tab 仍暗;补 2→1→2 变化序列后
App 日志出现 isPropAvailable: true ×4,tab 全亮。BAN_REASON 同理(0→1→0)。
4.3 「ADVANCED=3 被拒」疑点澄清(重要,别再误读)
- 被拒的是 App 侧 SET 写值:点「自定义」tab 时 App 主动
setVehicleProperty(BRAKING_MODE, 3), 台架 vehicled 不持久写入,回读校验失败 → logcatonDataVerifyFailed propId: 0x61408120, areaId: 0, propVal: 3→ UI 回滚。这是台架 vehicled 的写入语义问题,不是枚举值错误。 - mock 注入 3 是成功的:
--mock_from_car 0x61408120 --stat 0 -i 3后「自定义」选中、slider 出现。 - 结论:台架上验证 ADVANCED 相关 UI 只能走 mock 注入,不能让 App 自己 SET。 「ADVANCED 写值语义(为何台架拒写持久化)」待与平台确认,不阻塞 mock 验证。
5. 页面导航与输入注入
# launcher 类名(dev 包实证):com.android.car.settings.Settings_Launcher_Homepage
# ⚠️ am start -n com.android.car.settings/.homepage.HomepageActivity 会报 Activity class does not exist
# 权威查法:
$S "cmd package resolve-activity -a android.intent.action.MAIN -c android.intent.category.LAUNCHER com.android.car.settings | grep name="
# 拉起统一用 monkey(免去类名漂移):
$S "monkey -p com.android.car.settings -c android.intent.category.LAUNCHER 1"
# IVI 屏截图:必须用物理 display id(先查)
$S "dumpsys SurfaceFlinger | grep -iE 'displayName|DisplayDeviceInfo'"
adb -s 0304507188488540 exec-out screencap -d 4639175380446095616 -p > /tmp/screen.png
# slider 控件 bounds(uiautomator + 正则提取)
$S "uiautomator dump /sdcard/ui.xml >/dev/null 2>&1; cat /sdcard/ui.xml" | python3 -c "
import sys, re
x = sys.stdin.read()
for m in re.finditer(r'<node[^>]*class=\"([^\"]*(?:Slider|SeekBar|IconSlider)[^\"]*)\"[^>]*bounds=\"\[(\d+),(\d+)\]\[(\d+),(\d+)\]\"', x):
cls, x1, y1, x2, y2 = m.group(1), *map(int, m.groups()[1:])
print(f'{cls.split(\".\")[-1]}: bounds=[{x1},{y1}][{x2},{y2}] center=({(x1+x2)//2},{(y1+y2)//2})')"
# 导航实测坐标(fermi 2880x1800,仅参考,换分辨率必须重新 dump)
$S "input tap 269 883" # 驾驶偏好入口
$S "input swipe 1440 1000 1440 1500 400" # 列表滚动
# slider 轨道 y≈1598;点按=input swipe 同起止点 150ms(如摸 thumb: input swipe 1280 1598 1280 1598 150)⚠️ GT 主题 slider 视觉/触摸错位 ~96px(独立待查项,见 05 篇 §7): 视觉 thumb 位置 ≠ 触摸判定位置。实操实录:想摸 thumb 点 x=1376,实际判定落到了轨道值 79。 注入坐标必须以 uiautomator bounds + 实测日志反推为准,别按视觉位置算。
6. 台架坑清单(逐条实踩)
| # | 坑 | 实证 | 对策 |
|---|---|---|---|
| 1 | vehicled 不持久写入:App SET 后回读 0,触发 onDataVerifyFailed 回滚 UI | logcat onDataVerifyFailed propId: 0x61408120, propVal: 3;--get 回读 status: 4, [0] | 验证 UI 行为以 App 侧日志为准(>>> Done set propVal/updateThumbText),不以 VHAL 回读为准;信号态用 mock 注入维持 |
| 2 | v7.0(A14)包不能在 fermi(A17)跑 | 安装后拉起即 FATAL EXCEPTION: java.lang.NoSuchFieldError: No field ADVANCE_MEDIA_T_UUID ... in class Landroid/bluetooth/BluetoothUuid,at LocalBluetoothProfileManager.updateProfiles | A17 验证必须用 dev 分支 xcd_cn_a17 变体包(实测 versionName=1.0.0.252-dev-xcd_cn_a17) |
| 3 | dev 包 launcher 类名漂移 | am start -n .../.homepage.HomepageActivity → Activity class does not exist;resolve-activity 实证为 com.android.car.settings.Settings_Launcher_Homepage | 一律 monkey -p com.android.car.settings -c android.intent.category.LAUNCHER 1 拉起 |
| 4 | fuben 副本仓 build/ 污染 | 副本仓 build/ 目录含源仓绝对路径,增量编译失败 | 编不过先 ./gradlew clean |
| 5 | IVI 屏截图/坐标 | screencap 默认 display 可能不是 IVI 屏 | 物理 id 截图(-d 4639175380446095616);坐标用 uiautomator dump 拿 bounds |
| 6 | App 静态缓存不刷新 | mock 后页面仍旧值 | am force-stop + monkey 冷启动;或注入「值变化序列」(§4.2) |
| 7 | 台架 vehicled 不持久 → 计数/状态时序类回归不可复现:SET 后回读失败走 onDataVerifyFailed 回滚,syncValueInternal 的 callChangeListener 返回 false 走回滚分支 slider.setValue(mSliderValue)(SliderPreference.java:1009,e8b5a7ba5),mSliderValue 不被覆盖 → V1「计数在下发之后」这类依赖 mSliderValue 覆盖的回归,台架上计数恒正常 | V1 回归(连续拖动松手被取整)台架自测四条全绿,但正式提测包 30 例复现(05 篇 §9.2) | 台架物理上无法复现此类回归;计数/时序类改动必须上正式环境包验证,或在代码里临时打计数日志;「台架绿」不等于无回归。(2026-10-13 更新,mach):vehicle_mock_data=1 后 SET 成功链在台架完整执行,本盲区已解除,见 §9.1 实证 |
7. 修复验收测试矩阵(N801PROB-132401)
前置:§4 配方注入完成,slider 可见;logcat 观察窗:
adb -s 0304507188488540 logcat -c
# 操作…
adb -s 0304507188488540 logcat -d | grep -iE "onStartTrackingTouch|onStopTrackingTouch|onSliderStopTrackingTouch|Done set propVal|updateThumbText"| 用例 | 操作(实测坐标,fermi) | 预期(修复后) | 判定日志签名 |
|---|---|---|---|
| T1 拖动保持 | input swipe 1345 1598 1315 1598 700 拖到 67 松手 | 值 67,显示 67,SET 67 | onStopTrackingTouch mSliderValue=67 + Done set propVal: 67;无 SET:70 |
| T2 摸 thumb 不改写 | 拖定 67 后 input swipe 1280 1598 1280 1598 150(0 位移摸 thumb) | 值/显示/下发全不变 | onSliderStopTrackingTouch sliderValue=67.0 + Done set propVal: 67;无取整无 SET:70 |
| T3 点轨道取整保留 | 点 x≈1376(~79) | 吸附 80,标签立即 80,SET 80 | Done set propVal: 79 → updateThumbText() progress=80, thumbText=80 → Done set propVal: 80(⚠️ 该签名是 V1 顺序实录;V2 起先 setValue 再回调,79 泄漏帧消除,预期变为直接 SET:80 + updateThumbText 80) |
| T4 边界 ≥5 进位 | mock LEVEL=45 后点 thumb 同位置 | 45→50 | onStopTrackingTouch mSliderValue=45 → updateThumbText progress=50 → SET:50 |
| T5 边界对照 | 同上法点 55 / 35 | 55→60 / 35→40 | 同上,逐个对 SET 值 |
| T6 拖动跟手 | input swipe 654 1619 1315 1619 800(58→68) | 逐值 SET + 标签实时刷新 | updateThumbText progress=58/62/65/68 逐值 + SET 62/65/68… |
| T7 跨车型同款 | Lux/Mann M1 自定义页重复 T1~T4 | 同行为(三 controller 配置相同) | 待实测(该页在 mach 被移除,需 Lux/Mann 台架) |
| T8 GT 连点不跳变(V3 验收) | mach GT 主题,同位置连点 10 次(测试第三轮动作:拖动+连点 8~15 次) | 值/显示全程不跳变,零吸附 SET(如 34 不再被吸到 30) | 连点段全程无 Done set propVal 新值;对照三轮 bugreport 的 6 次吸附(05 篇 §9.3)。待 mach 实机实测(V3 GT 路径未经实机验证;mach 台架 mock 走 HIDL lshal 通道,见 §9) |
| T9 Normal 点击取整回归(V3 验收) | fermi Normal 主题重复 T3/T4(点 79→80、45→50) | 86420 点击跳整十完整保留(isIgnoreClick=false 不受 V3 影响) | 同 T3/T4 签名;PS4 后必跑,防 GT 门控误伤 Normal |
实操实录(transcript 2026-10-12):
- T2 通过实锤(22:11:34):
onStart/onStopTrackingTouch mSliderValue=67→onSliderStopTrackingTouch sliderValue=67.000000→Done set propVal: 67—— 修复前同样操作会出 SET:70。 - T3 通过实锤(22:12:14):SET:79 →
updateThumbText progress=80, thumbText=80(同一毫秒,标签立即跟随)→ SET:80。 - T6 通过实锤(21:59:10):
updateThumbText progress=58/62/65/68逐值 + SET 62/65/68,滑动中跟手下发无损。
8. 收尾复位
# mock 注入断电/重启 vehicled 后失效;如需立即复位,把信号注回台架真实值
$S "$M 0x6140757b -a 0 --stat 0 -i 0"
$S "$M 0x61408120 -a 0 --stat 4 -i 2" # 台架真实:status=4, 值=2
$S "$M 0x6140757d -a 0 --stat 4 -i 0" # 台架真实:status=4, 值=0注意:vehicled 偶发
FAILED_TRANSACTION(dump 失败),多为 vehicled 重启中,ps -A | grep vehicle确认进程存活后重试即可。
9. mach(YU7/YU7 GT)通道差异:HIDL VHAL + lshal debug(2026-10-13 实证)
mach 台架(adb serial f8d80e5c,Xiaomi_YU7_GT)没有 AIDL IVehicle/micar,
VHAL 是 HIDL:android.hardware.automotive.vehicle@2.0::IVehicle/default
(lshal 可见,服务进程 vendor.micar.hardware.vehicle@1.0-service)。
dumpsys -l 里也没有它(HIDL 不走 servicemanager),dumpsys <HIDL名> 报 Can’t find service。
mock 通道 = lshal debug,参数语法与 AIDL dump 完全相同:
S="adb -s f8d80e5c shell"
V="android.hardware.automotive.vehicle@2.0::IVehicle/default"
$S "lshal debug $V --get 0x61408120" # 读
$S "lshal debug $V --mock_from_car 0x61408120 -a 0 --stat 0 -i 3" # 注入- 三件套 propId 与 fermi 完全一致(0x6140757b / 0x61408120 / 0x6140757d),§4.1 配方原样适用。
- mach 台架原生三信号均为
status=0x4:BAN_REASON 真值 1、BRAKING_MODE 真值 2、LEVEL 真值 0。 - 注入后回读
.status = AVAILABLE即成功;UI 效果:能量回收强度「自定义」selected、0~100 slider 出现。 - ⚠️ mach 页面结构差异(01 篇):能量回收只在驾驶偏好主页,M1/M2 页与主自定义页被车型门控移除。
- 同法可推广到所有 HIDL VHAL 车型的其他信号 mock。
9.1 vehicle_mock_data 开关:App 写值方向的「防弹回」总闸(2026-10-13 用户口径+实证)
$S "settings put --user 0 GLOBAL vehicle_mock_data 1" # 开:App SET 被接受,UI 不回滚
$S "settings put --user 0 GLOBAL vehicle_mock_data 0" # 关:恢复台架默认(SET 校验失败回滚)用户口径:「让底层信号默认不会弹,也就是顶层接受了这个信号」。逐项对照实验实证(全部在 mach 台架):
| 维度 | =0 | =1 |
|---|---|---|
vehicled 接受 --mock_from_car 注入 | ✓ 接受 | ✓ 接受 |
| App 接受 mock 信号(上行,页面点亮) | ✓ | ✓ |
| 信号自动 AVAILABLE(免注入) | ✗ 不会 | ✗ 不会,仍必须走 §4.1 注入 |
| App SET 写值(下行) | onDataVerifyFailed → UI 回滚(§4.3 经典行为) | 接受生效,无回滚 |
实证签名:
=0点「标准」:onDataVerifyFailed propId: 0x61408120, propVal: 2→ tab 回滚到「自定义」。=1点「标准」:App cachev=2+notifyUIByPropertyChange()→ 「标准」selected 生效;无 onDataVerifyFailed。=1点「自定义」:cachev=3生效,slider 出现——§4.3 的「台架必回滚」被此开关解开,App 可以自己 SET ADVANCED 了。- 细节:=1 时
lshal --get回读仍显示 mock 注入值(如 3),但 App cache 已是 SET 值(2)——接受的是 App 写值回执/cache,不是 vehicled 存储真值。
对既有结论的修订:
- §4.3「台架上只能 mock 注入、不能让 App 自己 SET」→ 在 mach 上
vehicle_mock_data=1后 App 自己 SET 生效(fermi 是否有此开关未验证,设备当时不在线)。 - §6 坑 #7「台架无法复现计数/时序类回归(vehicled 不持久→走回滚分支)」→ 已实证解除(2026-10-13,mach + 252-dev-xcd_cn_a14 包):
=1时拖 slider,SET 成功链完整执行——Done set propVal: 51/52/53逐值下发 → App cache 逐值接受 →updateThumbText实时跟随 → 松手onSliderStopTrackingTouch sliderValue=53.0(== mSliderValue,无取整改写)→ 终值 SET:53,全程零 onDataVerifyFailed。即「盲区成因(SET 必走回滚分支)」已消除,台架与正式环境走同一条链:带 bug 的包应可在台架复现回归,带修复的包(252)走出修复后行为。=1之后「台架绿」才开始有正式环境等价含义;=0/未开时 §6#7 警示照旧。注意语义边界:vehicled 接受但不落库——App SET:53 后lshal --get回读仍是 mock 值 0,mock 模式下 vehicled 对 SET 回成功应答但不改存储。 - ⚠️ 本实验起始时该开关已是 1(bench 镜像预设或前人设置)——若 mach 台架行为与 KB 描述不符,先
settings get确认此开关。