手套箱 07 · 物理按键扩展设计(Meego 5458412)
本篇回答:手套箱物理按键怎么做、为什么这么做。这是需求设计篇,与本库 02/03 篇(软按键业务流与源码)互补。
⚠️ 状态声明(必读):技术方案处于修订版待用户确认状态(2026-10-18 定稿,经红队审查修订),确认前不改业务代码;埋点
source协议是遗留 TODO,待产品/数据方确认。本篇是方案的知识库化整理,不代表代码已落地。基线声明:
- 主锚点(W)=
/home/zbc/car/fuben4_micarsettings/MiCarSettings-5458412,origin/dev@930be9145e06a0a390d5982b6d713dc381004f7c(2026-10-17),分支feature/meego-5458412-glovebox-button。本篇行号以此为准。- 出货对照=
/home/zbc/car/pack_a17_cn_20260923(快照,本篇未引用其行号)。- 平台源码=
/home/zbc/pangu/xcd_xring_a17_fermi/vendor/micar。- 一手资料=
/home/zbc/下载/test/车机Feature/Meego 5458412/(下称 M 目录):docs/技术方案-手套箱物理按键.md、scan/手套箱物理按键-代码扫描.md、research/方案红队审查.md、test/自测用例-手套箱物理按键.md。
1. 需求边界
一句话:在 Modena-L / MANN(A17)车型上,新增手套箱物理按键 GlvBoxBtnSts 触发入口,仅在 ACC/Run 下复用现有 HMI 软按键的完整开箱流程,并补物理来源埋点。交付仅限 MiCarSettings,不改 SC12/SC49、平台 AIDL 或车辆属性 SDK。
边界逐条:
| 边界 | 内容 | 依据 |
|---|---|---|
| 触发信号 | Body.GLOVE_BOX_BUTTON_STATUS = 0x61401781(十进制 1631590273),INT32/GLOBAL/BODY | 平台 Body.java:1841-1848;AIDL MiVehicleProperty.aidl:1614-1619 |
| 触发枚举 | ERROR=0 / NOT_PRESSED=1 / PRESSED=2 / NO_INFO=3,需求语义为 0x2: Psd | 平台 Body.java:6948-6953;SDK vehicle.support:0.11.70-SNAPSHOT 已含,无需升级 |
| 电源前置 | 严格 ACC=2 或 RUN=3;OFF=0/AWAKE=1/START=4 均不允许——禁止 >=ACC,因为 START=4 > ACC | 平台 Driving.java:10876-10885(PowerStatus) |
| 行为等价 | 不可用按原因 Toast、已开启 Toast「请手动关闭手套箱」、无密码直接开箱、有密码拉密码页、错误达上限走车主账号验证——与 HMI 软按键同一份逻辑 | 软按键主流程 GloveBoxController.kt:196-302 |
| 车型范围 | Modena L 27C / E4U3(fermi)、Mann 28A / E4(feynman),均 A17;不写死 device,用 POWER 配置字 + 真实信号门控 | 配置字读取 CarConfigManager.kt:139-146;入口显隐先例 VehicleControlSettingsFragment.java:168-172 |
| 责任分界 | SC12 负责软件滤波、20 秒卡滞、休眠唤醒真实状态;HMI 只消费按下沿,不做 20 秒计时 | M 目录 kb/FDFR-Modena-L-MANN手套箱实体按键及电释放锁.md |
| 埋点 | 新增物理触发来源 source,不改变现有 type=1/2 的密码语义,禁止造 type=3 | VehicleControlReporter.kt:104-108;TrackConstants.java:532-558 |
2. 总体架构:为什么不能只在 Controller 里加一个属性 ID
GloveBoxController 随设置页创建/销毁,其 getPropertyIdSet() 只监听 GLOVE_BOX 与 CAR_MODE_STATUS(GloveBoxController.kt:99-102),且现有单测断言恰好两个属性(GloveBoxControllerTest.kt:107-123)。而物理按键要求页面未打开时也能响应——这决定了必须是「进程级常驻监听 + 共享业务流程」两段式:
flowchart TD subgraph CAR["车端(SC12 责任域)"] BTN["实体按键<br/>GlvBoxBtnSts 0x2:Psd"] SC12["SC12<br/>软件滤波/20s 卡滞/休眠唤醒"] VHAL["VHAL 属性<br/>GLOVE_BOX_BUTTON_STATUS<br/>0x61401781"] BTN --> SC12 --> VHAL end subgraph BASE["base/settingsVehicleLib(只注册分发,不写业务)"] VPL["VehiclePermanentListener<br/>getPropertyIdSet 新增按键属性<br/>prepared 时重注册"] end subgraph FEAT["settingsPage/VehicleBodyControl(feature 业务)"] PBH["GloveBoxPhysicalButtonHandler(新增)<br/>三态边沿/主线程串行<br/>每次沿重读门控/写前复查注入"] ORH["GloveBoxOpenRequestHandler(新增,共享)<br/>不可用Toast/已开启Toast<br/>无密码开箱/密码页/车主验证<br/>统一写入+埋点(最多一次)"] CTRL["GloveBoxController(改)<br/>点击分支委托共享处理器"] end WRITE["写 GLOVE_BOX=OPEN<br/>0x6140b407"] TRACK["埋点 TIP_OPEN_GLOVE_BOX<br/>type(密码维度)+source(触发来源)"] VHAL -->|"onPropertyChanged"| VPL VPL -->|"回调先过滤 propertyId<br/>再统一 post 主线程"| PBH PBH -->|"NOT_PRESSED→PRESSED 沿<br/>且门控全过"| ORH CTRL -->|"点击,source=HMI"| ORH ORH -->|"物理:beforeOpen 复查通过后<br/>GlobalCarPropertyManager.setCarProperty"| WRITE ORH -->|"软:setVehicleProperty(原通路)"| WRITE ORH -->|"reportOpenGloveBox(type, source)"| TRACK
关键结构选择(全部有代码实证):
- 常驻宿主选
VehiclePermanentListener而非GlobalCarPropertyManager或GloveBoxModulePublishService:前者在属性服务 prepared 时先重注册属性、再通知业务回调(VehiclePermanentListener.java:103-115),覆盖休眠唤醒/服务重连;GlobalCarPropertyManager.addPropertyCallback()只做一次性注册和转发,没有 prepared 重注册补偿(GlobalCarPropertyManager.kt:40-59);GloveBoxModulePublishService的keep_alive=false(settingsPage/VehicleBodyControl/src/main/AndroidManifest.xml:24-27),不能保证需要时存活。 - base 不写业务:
VehiclePermanentListener在settingsVehicleLib,无法反向依赖 VehicleBodyControl 的密码 UI;它只新增一个属性 ID 并复用既有分发(VehiclePermanentListener.java:393遍历 CopyOnWriteArrayList 回调,列表声明在:69)。 - 注册时机:
MainThreadStartTask.execute()已在主线程完成GlobalCarPropertyManager.init(:62)与VehiclePermanentListener.getInstance().init()(:66);物理 Handler 放进异步段initInAsync()(:80-117)请求初始化,不新增主线程同步属性读取。该异步段已有 app → VehicleBodyControl 的依赖先例(ChildSeatSignalSync.INSTANCE.sync(),:111)。开机覆盖由既有BootCompleteReceiver保证(app/src/main/AndroidManifest.xml:1615-1619),无需新增组件。
3. 架构决策记录 D1~D8
| # | 决策 | 理由与代码依据 |
|---|---|---|
| D1 | 选 VehiclePermanentListener 常驻注册按键属性 | prepared 重注册 + CopyOnWrite 回调列表(VehiclePermanentListener.java:69,103-115);addCarPropertyManagerCallback() 是公开入口,已连接时在调用线程立即回调 prepared(:305-313),业务留在 feature 模块不下沉 |
| D2 | 提炼共享 GloveBoxOpenRequestHandler | 需求要求「同 HMI 软按键逻辑」——提炼同一处理器而非复制近似逻辑,保证长期一致;通过构造函数注入 openProperty/beforeOpen/beforeVerifyUi 保留两条写入通路与门控差异 |
| D3 | 严格三态边沿 | 只有 NOT_PRESSED→PRESSED 触发;初始/ERROR/NO_INFO/unavailable/重连一律先 UNKNOWN。依据:注册可能同步回放缓存值(CarPropertyCacheManager.java:197-223,:222 直接 callback.onChangeEvent(value)),「仍按住」与「新按下」无法区分 |
| D4 | 全部按键状态主线程串行 | 不只 UI,边沿基线、prepared/released、注册幂等都 post 到同一主线程 Handler;addCarPropertyManagerCallback 可能在任意调用线程立即回调(VehiclePermanentListener.java:310-312),不串行就有基线竞态 |
| D5 | 最终写前复查(beforeOpen) | 密码/账号验证是异步回调(GloveBoxController.kt:196-219),验证期间电源/手套箱状态可能变化;物理路径在写 OPEN 前再次检查 POWER、ACC/RUN、可用且非 OPEN;软按键 beforeOpen 恒 true 保持原行为 |
| D6 | 独立 source,协议单独确认 | type=1/2 只表达密码维度(VehicleControlReporter.kt:104-108,reportOpenGloveBox 现态 :495-505);KEY_SOURCE="source" 常量已存在(TrackConstants.java:534)且有带 source 上报先例(reportEnterMirrorAdjust,:143-153),但其他事件的 SOURCE_SETTINGS=1/SOURCE_OTHER=2(:38-42)不能借用——手套箱专属枚举值待产品/数据方确认 |
| D7 | 仅物理入口关系统面板 | 物理按键可能在任意前台页面触发,密码页需防控制中心遮挡——先例是 MIS 入口拉起验证 UI 前发 Intent.ACTION_CLOSE_SYSTEM_DIALOGS(GloveBoxNavigationHandler.kt:104-107,调用点 :76,:82);软按键不发,行为零回归 |
| D8 | 不加长期 inFlight 锁/新文案/车型硬编码 | 密码 Activity 启动异常或无结果回调时,长期锁会永久卡死后续请求;未知 reason 需求未给文案,保持软按键默认分支(GloveBoxController.kt:288-302);车型用 POWER 配置门控而非 device 判断 |
4. 三态边沿状态机
内部状态只有 UNKNOWN / NOT_PRESSED / PRESSED 三个,唯一触发沿 = NOT_PRESSED→PRESSED:
stateDiagram-v2 [*] --> UNKNOWN : 进程启动/注册无有效快照 UNKNOWN --> NOT_PRESSED : 有效值 NOT_PRESSED(布防) UNKNOWN --> PRESSED : 首个有效值 PRESSED(只建基线,不触发) NOT_PRESSED --> PRESSED : ★按下沿,唯一触发 PRESSED --> NOT_PRESSED : 松开(重新布防,可再触发) NOT_PRESSED --> NOT_PRESSED : 重复值,不触发 PRESSED --> PRESSED : 重复值,不触发 UNKNOWN --> UNKNOWN : ERROR/NO_INFO/未知值/unavailable NOT_PRESSED --> UNKNOWN : ERROR/NO_INFO/未知值/unavailable/released PRESSED --> UNKNOWN : ERROR/NO_INFO/未知值/unavailable/released
全转换表(含异常与生命周期行):
| 当前状态 | 输入事件 | 新状态 | 允许触发 |
|---|---|---|---|
| 无有效初始快照 | init/prepared 读不到可用值 | UNKNOWN | 否 |
| UNKNOWN | 值=NOT_PRESSED 且事件可用 | NOT_PRESSED | 否(布防) |
| UNKNOWN | 值=PRESSED 且事件可用 | PRESSED | 否(只建基线) |
| UNKNOWN | ERROR / NO_INFO / 未知值 / unavailable | UNKNOWN | 否 |
| NOT_PRESSED | 值=PRESSED 且事件可用 | PRESSED | 是(唯一) |
| NOT_PRESSED | NOT_PRESSED 重复 | NOT_PRESSED | 否 |
| NOT_PRESSED | ERROR / NO_INFO / 未知值 / unavailable | UNKNOWN | 否 |
| PRESSED | 值=NOT_PRESSED 且事件可用 | NOT_PRESSED | 否(重新布防) |
| PRESSED | PRESSED 重复 | PRESSED | 否 |
| PRESSED | ERROR / NO_INFO / 未知值 / unavailable | UNKNOWN | 否 |
| 任意 | onPropertyManagerReleased()(服务断开) | UNKNOWN | 否 |
| 任意 | 重连后首个回调为 PRESSED | PRESSED | 否(快照只布防) |
| 任意 | 重复 init | 不变 | 否(幂等,不重置基线) |
要点:onPropertyManagerPrepared() 在主线程读当前快照直接建立状态,不把快照当按下沿;初始已是 PRESSED 时必须经历 PRESSED→NOT_PRESSED→PRESSED 才触发;ERROR/unavailable 恢复后的第一个有效 PRESSED 同样只建基线。这套规则是红队审查「必须修正 1」的落点(M 目录 research/方案红队审查.md)。
5. 门控顺序与最终写前复查
每次有效按下沿,主线程依次执行(顺序即语义):
- 重读手套箱锁型配置:
CarConfigManager.getGloveBoxLockType(),仅POWER继续。配置未就绪时默认值是 MANUAL(CarConfigManager.kt:139-146),因此禁止启动时缓存默认值后永久关门——每次沿重新读,配置后变为 POWER 仍可工作。 - 电源严格判等:
== ACC || == RUN,其余(OFF/AWAKE/START)退出。 - 读完整
GLOVE_BOX属性:先看MiCarPropertyValue.status是否 AVAILABLE(工具方法VehiclePropertyUtil.java:173-182),再看 value;null/unavailable只能进现有不可用原因映射,禁止把空值当 CLOSE——getIntPropertyValue(null)会返回 0(VehiclePropertyUtil.java:55-70),而 0 恰好是 CLOSE,不拦就会误判。 - 可用且已 OPEN:只 Toast「请手动关闭手套箱」(
glove_box_close_notice,软按键同款,GloveBoxController.kt:261-263),不写属性。 - 其余进入共享流程:无密码直接开箱 / 密码页 / 错误达上限走车主账号验证(保留 P 挡约束,
GloveBoxAccountVerifyUtils.kt:45-57,非 P 挡 Toast 不拉页)。
写前复查(beforeOpen):密码/账号验证是异步的,验证期间用户可能切电源、箱子可能已被其他途径打开。物理路径向共享处理器注入 beforeOpen:最终写 OPEN 前再次检查「POWER 配置 + 严格 ACC/RUN + GLOVE_BOX 可用且非 OPEN」,失败则不写、不上报开箱事件,只记录拦截原因。软按键 beforeOpen 恒 true,不新增二次门控。
写入通路分离:软按键仍走原 setVehicleProperty(Controller 既有通路,GloveBoxController.kt:222-229);物理入口用 GlobalCarPropertyManager.setCarProperty()(MIS 入口先例,GloveBoxNavigationHandler.kt:125-131),不用 VehiclePermanentListener 的离线排队(mWaitingVehicleDataList,VehiclePermanentListener.java:106-114),避免服务恢复后执行过期开箱意图。
sequenceDiagram participant VHAL as "VHAL(SC12)" participant VPL as "VehiclePermanentListener" participant PBH as "GloveBoxPhysicalButtonHandler" participant ORH as "GloveBoxOpenRequestHandler(共享)" participant UI as "密码/账号验证页" participant PROP as "GlobalCarPropertyManager" VHAL->>VPL: GLOVE_BOX_BUTTON_STATUS 变化 VPL->>PBH: 分发(回调列表遍历) PBH->>PBH: 过滤 propertyId → post 主线程 → 三态边沿 Note over PBH: 仅 NOT_PRESSED→PRESSED 继续 PBH->>PBH: ①重读 POWER 配置 ②严格 ACC/RUN ③完整 status/value PBH->>ORH: onAvailableRequest(isOpened, source=PHYSICAL) alt 已开启 ORH->>UI: Toast「请手动关闭手套箱」 else 无密码 ORH->>ORH: beforeOpen() 二次复查 ORH->>PROP: setCarProperty(GLOVE_BOX, OPEN) ORH->>ORH: reportOpenGloveBox(type=1, source=物理) 仅一次 else 有密码/达上限 ORH->>UI: beforeVerifyUi() 关闭系统面板 → 拉起验证页 Note over ORH,UI: source 作为不可变参数捕获进回调闭包 UI-->>ORH: 异步回调成功 ORH->>ORH: beforeOpen() 二次复查 alt 复查通过 ORH->>PROP: setCarProperty(GLOVE_BOX, OPEN) ORH->>ORH: reportOpenGloveBox(type=2, source=物理) 仅一次 else 复查失败(切 OFF/AWAKE/START、已 OPEN、unavailable) ORH->>ORH: 不写、不报,只记录拦截原因 end end
6. 并发语义:后请求覆盖前请求,source 不可变穿透
- 单回调槽:通用密码框架
PinPasswordCheckUtils只有一个mCallback槽(PinPasswordCheckUtils.kt:58),每次goToCheckPassword直接覆盖(:147)再起 Activity(:150-159);结果广播回来调当前槽(:64-70)。 - singleInstance 密码页:
GloveBoxPasswordDialog为singleInstance+ 独立 taskAffinity(VehicleBodyControl/AndroidManifest.xml:30-38),onNewIntent重建 Fragment(GloveBoxPasswordDialog.kt:59-62)。 - 结论性语义:软/物理并发发起密码请求时,后请求替换当前页面与结果回调,前请求不再开箱;最终最多一次有效开箱和一次开箱埋点。
- source 不共享:每次请求的 source 作为不可变参数被回调闭包捕获,穿透异步验证;禁止使用共享可变
currentSource字段——否则后请求会污染先发起请求的上报来源。 - 不加长期 inFlight 锁:Activity 启动异常或用户未返回结果时回调不一定到达,长期锁会把后续所有请求永久卡死(红队「必须修正 4」与建议项)。
7. 文件级改动清单(方案 §4)
| # | 文件 | 改动 |
|---|---|---|
| 1 | base/settingsVehicleLib/.../VehiclePermanentListener.java | getPropertyIdSet()(:274-303)增加 Body.GLOVE_BOX_BUTTON_STATUS;只注册分发,不写业务;复用 prepared 重注册 |
| 2 | 新增 settingsPage/VehicleBodyControl/.../glovebox/GloveBoxPhysicalButtonHandler.kt | 实现 CarPropertyManagerCallback;单例 + init 幂等;onPropertyChanged 第一行过滤 propertyId 再 post 主线程;三态边沿;每次沿重读 POWER/ACC-RUN/完整状态;注入 beforeOpen 复查 |
| 3 | 新增 .../glovebox/GloveBoxOpenRequestHandler.kt | 从 GloveBoxController 原样提炼:已开启 Toast、不可用原因映射、无密码开箱、密码页、车主验证、统一写入 + 一次埋点;构造函数注入 openProperty/beforeOpen/beforeVerifyUi |
| 4 | 改 .../glovebox/GloveBoxController.kt | 点击分支委托共享处理器(:115-123 改为 onAvailableRequest(tab.mIsSelected, SOURCE_HMI) / showUnavailableNotify(...));删除已迁移私有方法;getPropertyIdSet() 仍只留两个属性(:99-102);软按键不发 CLOSE_SYSTEM_DIALOGS、不加二次门控 |
| 5 | app/.../MainThreadStartTask.java | initInAsync()(:80-117)前段请求一次 GloveBoxPhysicalButtonHandler.init(mApplication);异步段而非主线程,不扩大冷启动耗时 |
| 6 | base/settingsBaseLib/.../VehicleControlReporter.kt | reportOpenGloveBox(type)(:495-505)增加 source 参数,写既有 KEY_SOURCE;新增手套箱专属来源枚举(建议 HMI=1 / PHYSICAL_BUTTON=2,协议值待确认);type 语义不变,不加 type=3 |
| + | src/test/.../glovebox/ 单测 | 三态边沿全覆盖 + 共享流程必要用例(见 §9);现有 GloveBoxControllerTest 继续跑 |
不改清单及依据:
| 不改 | 依据 |
|---|---|
| SC12 / SC49 / 平台 AIDL | 信号与枚举平台已具备(Body.java:1841-1848,6948-6953);用户确认仅 HMI 交付 |
vehicle.support 版本 | 0.11.70-SNAPSHOT 已含目标属性 |
GloveBoxPasswordTools / 密码存储 | 复用现有密码与错误上限;本需求不重做密码系统 |
GloveBoxModulePublishService | keep_alive=false(模块 Manifest :24-27),不能当常驻宿主 |
VehiclePermanentListener 内业务逻辑 | base 无法依赖 feature 的密码 UI,只加属性 ID |
| 车型/device 硬编码 | POWER 配置字 + 信号能力门控已覆盖目标车型 |
| 20 秒卡滞计时/软件滤波 | FD&FR 明确属 SC12 |
| 未知 reason 新文案 | 保持现有默认分支,不擅自创造产品文案 |
TrackConstants.java | KEY_SOURCE 已存在(:534) |
另注:手套箱业务源码集中在 src/main/java,无 dcddif/xcddif/f3dif 同名副本(已 find 实证),sourceSets(build.gradle:57-89)无需多副本齐改。
8. 行为对照矩阵(物理 vs 软按键)
| 场景 | 物理按键预期 | 软按键预期 |
|---|---|---|
| 初始 UNKNOWN→PRESSED | 只建立 PRESSED 基线,不触发 | 不适用 |
| 初始已为 PRESSED | 必须松开再重新按下才触发 | 不适用 |
| 连续 PRESSED | 不重复触发 | 不适用 |
| ERROR/NO_INFO/unavailable→PRESSED | 不触发;需重新 NOT_PRESSED→PRESSED | 不适用 |
| NOT_PRESSED→PRESSED | 进入一次门控流程 | 每次点击进入流程 |
| OFF / AWAKE / START | 不执行 | 本需求不改变 |
| ACC / RUN | 允许执行 | 本需求不改变 |
| 非 POWER 手套箱 | 忽略并记录原因;配置变 POWER 后可工作 | 入口按现有配置隐藏(VehicleControlSettingsFragment.java:168-172) |
| GLOVE_BOX null/unavailable | 只进原因映射,禁止空值当 CLOSE | 与现有不可用分支一致 |
| 不可用 + 未解防/防玩/代客 | 复用对应 Toast(GloveBoxController.kt:288-302) | 与现状一致 |
| 不可用 + 未知 reason | 保持默认分支,不新造文案 | 与现状一致 |
| 已开启 | Toast「请手动关闭手套箱」,不写属性 | 与现状一致 |
| 无密码 + 关闭 | 写 OPEN 一次;type=1, source=2(枚举待确认) | 写 OPEN;type=1, source=1 |
| 有密码 + 未达上限 | 关系统面板后弹密码页;成功 type=2, source=2 | 直接弹密码页;成功 type=2, source=1 |
| 密码失败/取消 | 不写 OPEN、不上报开箱 | 与现状一致 |
| 验证期间切 OFF/AWAKE/START 或已 OPEN/unavailable | 成功回调也不写、不报(beforeOpen 拦截) | 不新增二次门控 |
| 错误达上限 + P 挡 | 车主验证成功清零并开箱,type=2, source=2 | 同流程,source=1 |
| 错误达上限 + 非 P 挡 | 复用现有提示,不拉验证、不开箱 | 与现状一致 |
| 软/物理密码请求并发 | 后请求覆盖前请求,最多一次有效开箱与一次事件 | 同左 |
| 服务断开/重连 | 立即 UNKNOWN,重连快照只布防;不排队补开过期意图 | 本需求不改变 |
9. 风险表与测试矩阵摘要
主要风险与缓解(全表见 M 目录技术方案 §8):
| 风险 | 缓解 |
|---|---|
| 初始/无效恢复/重连误开箱 | 三态仅 NOT_PRESSED→PRESSED;released 即 UNKNOWN;快照只布防 |
| 多线程基线/注册竞态 | 边沿、prepared/released、注册全部同一主线程 Handler 串行;init 幂等 |
| 验证期间前置条件变化仍开箱 | 物理路径写前二次复查 POWER/ACC-RUN/可用非 OPEN |
| 空属性当关闭可用 | 先查完整 status 再读 value(VehiclePropertyUtil.java:55-70,173-182) |
| 启动默认 MANUAL 永久关门 | 每次沿重读配置,不缓存启动值 |
| 软按键回归 | 只迁移原逻辑;写入走原通路;不加二次门控/面板行为;跑现有单测 + 双变体编译 |
| 服务恢复执行过期意图 | 物理写入不走永久监听器离线队列(VehiclePermanentListener.java:106-114) |
| type 语义污染 | 独立 source,禁 type=3 |
| source 协议未确认 | 手套箱专属常量名;产品/数据方确认后才允许合入 |
| 异步来源被覆盖 | source 不可变捕获进闭包,禁共享 currentSource |
| 并发重复开箱 | 沿用 singleInstance + 单回调槽后请求优先;不加易卡死锁 |
| 非设置页 tabInstanceId 误导 | 验收时记录实际值,不解释为本次物理会话(reportOpenGloveBox 现带 KEY_TAB_INSTANCE_ID,:500) |
| 未知 reason 无文案 | 保持现状并显式列入测试 |
测试矩阵(全表在 M 目录 test/自测用例-手套箱物理按键.md,执行走 car-signal-mock-test 流程,逐例留 uiautomator/截图/logcat 三证并复位信号):
- E01~E09 信号边沿与生命周期(9 例):初始按住不触发、连续/重复 PRESSED、松开重按、ERROR/NO_INFO/unavailable 恢复、服务断连重连、force-stop 重启不执行过期意图。
- G01~G07 前置门控(7 例):OFF/AWAKE/START 全拒(G03 专证不是
>=ACC)、ACC/RUN 放行、MANUAL 忽略且配置变 POWER 后可恢复、属性 null/unavailable 进原因映射。 - F01~F17 业务流程(17 例):三类不可用 Toast、未知 reason 现状记录、已开启、无密码/有密码/失败/取消、达上限 ±P 挡、软物理并发后请求为准、验证期间切电源或状态变化被写前复查拦截。
- R01~R07 兼容与回归(7 例):现有单测全过、
xcd_cn_a17Debug/Release 编译、另一非目标变体编译防共享代码破坏、软按键全链路无回归、语音/MIS 冒烟(R07,本需求未改其通路——MIS 入口见 04 篇)。
10. 当前状态与开放问题
| 项 | 状态 |
|---|---|
| 技术方案 | 修订版待用户确认(2026-10-18,含红队 4 项修正);确认前不改业务代码 |
| source 协议 | 遗留 TODO:手套箱 source 的 key、枚举值、HMI 旧事件是否补 source=1,均无数据协议原文佐证;用户 2026-10-19 指示暂不拍板,Gerrit 合入前需产品/数据方确认 |
| Q1 初始/重连/恢复时已 PRESSED 是否立即响应 | 方案 A(当前建议,安全优先)=只建基线,须松开重按;方案 B=首个有效 PRESSED 直接响应。用户已于 2026-10-19 发起与产品讨论,尚无结论;未决前三态边沿仅为推荐方案 |
| 前序 PRD / 测试用例表 | 当前账号无读权限(M 目录 README 开放问题节有占位与补扒方法) |
| 台架兼容性 | 待确认;当前 mach 台架已实证手套箱入口 + 密码页可调出(2026-10-19/20,入口显隐靠 LOCK_TYPE_CONFIG=POWER 注入 + force-stop;密码页 exported=false 需 root 直拉;注意 wrong_times≥5 残留会变扫码页)——配方见 /home/zbc/下载/test/车机JIRA/.claude/skills/car-signal-mock-test/references/场景-手套箱入口与密码页.md |
| 节点 | 开发&提测 2026-10-20~10-22;代码合入 DDL 2026-10-23 |
11. 证据清单
| 文件 | 行号 | 说明 |
|---|---|---|
/home/zbc/下载/test/车机Feature/Meego 5458412/docs/技术方案-手套箱物理按键.md | 全文 | 方案主体(需求结论/§4 文件 diff/§5 对照矩阵/§8 风险/§10 D1~D8/§11 Q1) |
.../Meego 5458412/scan/手套箱物理按键-代码扫描.md | 全文 | 代码证据(信号实证/软按键流程/常驻入口选择/埋点现状/改与不改清单) |
.../Meego 5458412/research/方案红队审查.md | 全文 | 4 项必须修正(三态边沿/主线程串行/写前复查/source 与并发) |
.../Meego 5458412/test/自测用例-手套箱物理按键.md | 全文 | E/G/F/R 测试矩阵全表 |
.../Meego 5458412/README.md | 24-92 | 需求速查、开放问题、节点排期 |
平台 .../support-lib/src/mi/car/config/Body.java | 1841-1848, 6948-6953 | GLOVE_BOX_BUTTON_STATUS=0x61401781;枚举 ERROR/NOT_PRESSED/PRESSED/NO_INFO |
平台 .../aidlproperty/MiVehicleProperty.aidl | 1614-1619 | AIDL 映射 SYSTEM_GLOVE_BOX_BUTTON_STATUS=1631590273 |
平台 .../support-lib/src/mi/car/config/Driving.java | 10876-10885 | PowerStatus:OFF=0/AWAKE=1/ACC=2/RUN=3/START=4,证 >=ACC 错误 |
平台 .../support-lib/src/mi/car/config/Peripheral.java | 572-610 | GLOVE_BOX(0x407)/LOCK_TYPE_CONFIG(0x409)/UNAVL_REASON(0x40a) |
W:app/.../MainThreadStartTask.java | 62, 66, 76, 80-117 | 主线程初始化 GlobalCarPropertyManager/VehiclePermanentListener;异步段先例 ChildSeatSignalSync(:111) |
W:app/src/main/AndroidManifest.xml | 1615-1619 | BootCompleteReceiver 覆盖开机 |
W:base/settingsVehicleLib/.../VehiclePermanentListener.java | 69, 95-120, 274-317, 362-394 | CopyOnWrite 回调表;prepared 重注册(:103-115)/released(:118-120);注册集合(:274-303);add 回调立即 prepared(:305-313);分发(:393);离线队列(:106-114) |
W:base/settingsVehicleLib/.../GlobalCarPropertyManager.kt | 40-59 | add/removePropertyCallback 无 prepared 重注册 |
W:base/settingsVehicleLib/.../CarConfigManager.kt | 139-146 | getGloveBoxLockType,默认 MANUAL |
W:base/settingsVehicleLib/.../vehicle/CarPropertyCacheManager.java | 197-223 | 注册同步回放缓存(:222 onChangeEvent) |
W:base/settingsVehicleLib/.../vehicle/VehiclePropertyUtil.java | 55-70, 173-182 | null→0 陷阱;status==STATUS_AVAILABLE 检查 |
W:settingsPage/VehicleBodyControl/.../glovebox/GloveBoxController.kt | 99-102, 115-123, 166-188, 196-302 | 软按键监听集/点击分支/可用性驱动/密码与账号异步回调/统一写入+埋点/原因映射 |
W:.../glovebox/GloveBoxNavigationHandler.kt | 62-86, 94-107, 114-131 | MIS 先例:POWER 检查、拉验证页前 CLOSE_SYSTEM_DIALOGS(:104-107)、GlobalCarPropertyManager 写开箱(:125-131) |
W:.../glovebox/GloveBoxAccountVerifyUtils.kt | 45-57 | 车主验证 P 挡约束(:50-55) |
W:settingsPage/VehicleBodyControl/src/main/AndroidManifest.xml | 24-27, 30-38 | PublishService keep_alive=false;密码页 singleInstance |
W:.../glovebox/GloveBoxPasswordDialog.kt | 59-62 | onNewIntent 重建 Fragment |
W:base/settingsBaseUi/.../password/PinPasswordCheckUtils.kt | 57-70, 146-159 | 单回调槽 mCallback(:58)被后请求覆盖(:147);结果广播回调(:64-70) |
W:base/settingsBaseLib/.../track/TrackConstants.java | 532-558 | VehicleControlKey.KEY_SOURCE(:534)/KEY_GLOVE_BOX_OPEN_TYPE(:558) 已存在 |
W:base/settingsBaseLib/.../track/VehicleControlReporter.kt | 38-42, 104-108, 143-153, 489-505 | SOURCE_SETTINGS/OTHER 不可借用;type=1/2 密码维度;source 上报先例;reportOpenGloveBox 现态 |
W:settingsPage/VehicleBodyControl/.../VehicleControlSettingsFragment.java | 168-172 | 非 POWER 隐藏手套箱入口 |
W:.../vehicle/VehicleBodyPropertyMap.kt | 42-49 | 软按键 allowClickWhenDisabled(不可用也可点以弹原因) |
W:src/test/.../glovebox/GloveBoxControllerTest.kt | 107-123 | 现有断言恰两个监听属性,物理信号不得塞进 Controller |
W:settingsPage/VehicleBodyControl/build.gradle | 57-89 | sourceSets;glovebox 仅 src/main 一份(find 实证) |
/home/zbc/下载/test/车机JIRA/.claude/skills/car-signal-mock-test/references/场景-手套箱入口与密码页.md | 全文 | mach 台架入口/密码页调出配方与坑(wrong_times 残留等) |
篇间导航
- 软按键开箱主流程与密码体系(本需求复用的全部逻辑):03-开箱主流程源码精讲、04-密码与账号验证体系
- glovebox 包 10 个类逐段拆解:03-开箱主流程源码精讲
- 0x407~0x40a 属性语义与读写权限:01-需求与信号契约
- MIS 语音/远程开箱(第三条入口,本需求未改):04-密码与账号验证体系
- 埋点事件与既有台架 mock 配方:05-埋点体系、06-属性通道与台架mock
- propId/常量/adb 命令一页纸:08-速查卡
- 返回总索引:00-手套箱-README