手套箱 05 · MIS 语音/远程与对外暴露
本篇回答:除了设置页,手套箱还从哪里被触达——语音助手、手机 App 远程、路由、快照。 基线:
fuben31_micarsettings/MiCarSettings@ dev
1. 对外触达全景
flowchart TD subgraph 上游调用方 V[语音助手<br/>「打开手套箱」] R[手机 App 远程控制] D[外部 deeplink/路由<br/>glovebox_pw / glovebox_pw_set] VA[可见即可说<br/>TAB_FLAG 定位] end subgraph 本仓出口 S[GloveBoxModulePublishService<br/>MIS 静态发布] H[GloveBoxNavigationHandler<br/>onAction 受理] PA[PageAlias 别名] TF[TabKeyMapHelper flag] end V & R -->|MIS 总线 Open action| S S --> H D --> PA VA --> TF H --> VHAL[(GLOVE_BOX=OPEN)]
2. GloveBoxModulePublishService(发布服务,22 行)
glovebox/GloveBoxModulePublishService.kt:
override fun getServiceHandlerList(): CompartmentServiceHandlerList {
val list = CompartmentServiceHandlerList()
list.gloveCompartment = GloveBoxNavigationHandler() // :16-21
return list
}- 继承 MIS SDK 的
AbsModulePublishService(依赖com.xiaomi.mis.misspec:compartment:0.1.0,settingsPage/VehicleBodyControl/build.gradle:102); - 基类
MisSpecPublisherServiceBase:online()时ModulePublishHelper.publish(...),offline()时 unPublish——即把手套箱能力注册给 MIS(Mi Intelligent Service)总线; - 上游:语音助手、手机 App 远程控制等。
启动链(静态注册,无代码调用点)
settingsPage/VehicleBodyControl/src/main/AndroidManifest.xml:9-28:
| 配置 | 值 |
|---|---|
exported | true |
foregroundServiceType | connectedDevice |
| 权限 | com.xiaomi.permission.ACCESS_MIS_SPEC_SERVICE |
| intent-filter action | com.xiaomi.mis.action.STATIC_DDF_CONFIG_ACTION |
| meta-data | static_ddf_configuration=@raw/compartment_ddf(raw 来自 misspec AAR,本仓无源) |
keep_alive | false(有 Property/Event 订阅时才拉起,关保活) |
DDF 描述(compartment_ddf.json,2024-09-27 e08cae248 引入):
iid=32,urn:aiot-spec-v3:com.mi.car:module:compartment——手套箱是 compartment(舱室)模块的一个能力。
grep 全仓无
startService/bindService调用点;同构服务还有 app 级SettingMisModulePublishService.kt与顶舱micarLockSettings/.../mis/RoofSecurityHandler.kt。
3. GloveBoxNavigationHandler(指令受理)
glovebox/GloveBoxNavigationHandler.kt,继承 AbsGloveCompartmentHandler。
类注释(:14-38)明确两个来源:语音助手开箱 / 手机 App 远程开箱。
3.1 响应码
| 码 | 常量 | 含义 | 触发条件(判定顺序) |
|---|---|---|---|
| 1001 | CODE_OPEN_SUCCESS | 开箱成功 | 电动 + 无密码 → 直接开 |
| 1002 | CODE_OPEN_FAIL_PWD | 需要密码验证 | 电动 + 有密码未锁定 → 拉起密码框 |
| 1003 | CODE_OPEN_FAIL_NOT_SUPPORT | 手套箱类型不支持 | 配置字非 POWER |
| 1004 | CODE_OPEN_FAIL_PASSWORD_LIMIT | 密码错误达上限 | 已锁定 → 拉起账号验证 |
3.2 onAction 分支(:62-86)
override fun onAction(actionRequest: Open) {
val type = CarConfigManager.getGloveBoxLockType()
val resCode = if (type != Peripheral.GloveBoxLockTypeConfig.POWER) {
CODE_OPEN_FAIL_NOT_SUPPORT
} else if (GloveBoxPasswordTools.getLocalPwd().isNullOrEmpty()) {
openGloveBox(); CODE_OPEN_SUCCESS
} else if (GloveBoxPasswordTools.isPwdLimit()) {
showAccountVerifyPage(); closeControlCenter(); CODE_OPEN_FAIL_PASSWORD_LIMIT
} else {
showPasswordCheckDialog(); closeControlCenter(); CODE_OPEN_FAIL_PWD
}
sendResponse(actionRequest, resCode)
}sequenceDiagram participant VA as 语音助手/手机App participant MIS as MIS 总线 participant S as GloveBoxModulePublishService participant H as GloveBoxNavigationHandler participant D as GloveBoxPasswordDialog participant V as vehicled VA->>MIS: Open action(打开手套箱) MIS->>S: 拉起(keep_alive=false,按需) S->>H: getServiceHandlerList().gloveCompartment MIS->>H: onAction(Open) H->>H: 查配置字 getGloveBoxLockType() alt 非电动 H-->>MIS: 1003 NOT_SUPPORT else 无密码 H->>V: setVehicleProperty(GLOVE_BOX, OPEN) H-->>MIS: 1001 SUCCESS else 有密码 H->>H: closeControlCenter()<br/>sendBroadcast(ACTION_CLOSE_SYSTEM_DIALOGS) H->>D: showPasswordCheckDialog() H-->>MIS: 1002 FAIL_PWD Note over D: 用户输对密码后<br/>走 GloveBoxController.openGloveBox 开箱 else 已锁定 H->>H: closeControlCenter() H->>D: showAccountVerifyPage()(查 P 挡) H-->>MIS: 1004 PASSWORD_LIMIT end
两个细节:
closeControlCenter()(:105-107):弹密码/验证 UI 前先发Intent.ACTION_CLOSE_SYSTEM_DIALOGS收起控制中心——否则密码框被控制中心遮挡。- MIS 路径没有代客/登录检查(设置页有),只靠配置字 + 密码 + 锁定三层拦截。
4. 路由别名(PageAlias)
base/settingsBaseUi/src/main/java/com/android/car/settings/common/pageroute/PageAlias.kt:58-59:
const val DIALOG_GLOVE_BOX_CONTROL = "glovebox_pw"
const val DIALOG_GLOVE_BOX_SET_CONTROL = "glovebox_pw_set"供跨模块/外部 deeplink 拉起密码弹窗/密码设置弹窗。仓内当前无调用方,属预留。
5. 语音「可见即可说」
base/settingsBaseUi/.../voiceassist/TabKeyMapHelper.java:24-25:
public static final int TAB_FLAG_GLOVE_BOX_SWITCH = ...;
public static final int TAB_FLAG_GLOVE_BOX_PASSWORD_SET = ...;VehicleBodyPropertyMap.kt:49,62 创建两个 Tab 时通过 .withTabFlag(...) 注入,
语音助手据此把「手套箱」「手套箱密码」的说法映射到具体控件。
历史 bugfix:
3dcb687fd(2025-03-21)「输入密码未屏蔽可见即可说」—— 密码弹窗里语音可见即可说会泄露/误触,已屏蔽。改密码页时注意不要放出来。
6. Snapshot 快照消费
micarSnapshot 模块把手套箱密码状态打进车控快照,供大数据看密码使用率:
| 位置 | 内容 |
|---|---|
settingsPage/micarSnapshot/.../data/LocalSettingsGlobal.kt:105 | const val GLOVE_BOX_PWD = "glove_box_password"(与 GloveBoxPasswordTools 同一 key) |
LocalSettingsGlobal.kt:226-227 | getGloveBoxPwd(): String 读哈希(空=未设) |
builder/VehicleControlDataBuilder.kt:127-131 | KEY_VEHICLE_STATUS14 = "status14" 手套箱密码开关,0=关闭 1=开启 |
VehicleControlDataBuilder.kt:198 | map[KEY_VEHICLE_STATUS14] = getGloveBoxPwdStatus() 打进 buildTrackData |
VehicleControlDataBuilder.kt:219-226 | 算法:getGloveBoxPwd().isEmpty() → 0/1,只报是否设密码,不报哈希 |
另:DrivingPropertyFetcher.kt:29-35 提供 getGloveBoxStatus()(读 GLOVE_BOX 信号),
但当前 buildTrackData() 未使用——fetcher 层预留。
7. 账号验证场景号
AccountVerifyHelper.kt:70:CONFIRM_SCENE_OWNER_GLOVE_BOX = "glovebox_verify",
:127/:168 打包进账号验证的 Bundle,账号中心以此区分「这次车主验证是手套箱触发的」。
查账号侧日志/数据时按这个 scene 过滤。
8. 名称相近但无关的功能(防混淆)
settingsPage/micarSafetyServiceSettings/.../usb/UsbDiskHelper.java 的 isGloveBoxUsb()
是 DVR 记录仪识别手套箱 USB 口(物理位置命名),被 DvrButtonPreferenceController/
FormatTask 使用,与手套箱电动锁/密码完全无关。grep 时注意区分。