手套箱 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:

配置值
exportedtrue
foregroundServiceTypeconnectedDevice
权限com.xiaomi.permission.ACCESS_MIS_SPEC_SERVICE
intent-filter actioncom.xiaomi.mis.action.STATIC_DDF_CONFIG_ACTION
meta-datastatic_ddf_configuration=@raw/compartment_ddf(raw 来自 misspec AAR,本仓无源)
keep_alivefalse(有 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 响应码

码常量含义触发条件(判定顺序)
1001CODE_OPEN_SUCCESS开箱成功电动 + 无密码 → 直接开
1002CODE_OPEN_FAIL_PWD需要密码验证电动 + 有密码未锁定 → 拉起密码框
1003CODE_OPEN_FAIL_NOT_SUPPORT手套箱类型不支持配置字非 POWER
1004CODE_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

两个细节:

  1. closeControlCenter()(:105-107):弹密码/验证 UI 前先发 Intent.ACTION_CLOSE_SYSTEM_DIALOGS 收起控制中心——否则密码框被控制中心遮挡。
  2. 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:105const val GLOVE_BOX_PWD = "glove_box_password"(与 GloveBoxPasswordTools 同一 key)
LocalSettingsGlobal.kt:226-227getGloveBoxPwd(): String 读哈希(空=未设)
builder/VehicleControlDataBuilder.kt:127-131KEY_VEHICLE_STATUS14 = "status14" 手套箱密码开关,0=关闭 1=开启
VehicleControlDataBuilder.kt:198map[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 时注意区分。