手套箱 03 · 源码精讲

本篇逐类拆解手套箱全部源码。读之前建议先看 02 篇建立流程概念。 基线:fuben31_micarsettings/MiCarSettings @ dev

1. 类关系全景

classDiagram
    class GloveBoxController {
        +TAB_INDEX_GLOVE_BOX_SWITCH=0
        +TAB_INDEX_GLOVE_BOX_PASSWORD_SET=1
        +getPropertyIdSet()
        +onHandlePropertyChange()
        -showGloveBoxSwitchClick()
        -openGloveBox(openType)
        -mLoginReceiver
    }
    class BaseVehicleButtonIconToggleGroupPropController
    GloveBoxController --|> BaseVehicleButtonIconToggleGroupPropController

    class GloveBoxSettingsDialogActivity {
        -mPwdSwitch: PreCheckSwitch
        -mPwdChange: View
        -handleSwitchChange()
        -goToChangePassword()
        -updateUIState()
    }
    class BaseConflictDialogActivity
    GloveBoxSettingsDialogActivity --|> BaseConflictDialogActivity

    class GloveBoxPasswordDialog {
        -showFragment()
    }
    GloveBoxPasswordDialog --|> BaseConflictDialogActivity

    class GloveBoxPasswordFragment {
        +ACTION_PASSWORD_VERIFY_RESULT
        +getPasswordConfig()
        +openAccountVerifyPage()
    }
    class BasePinPasswordFragment
    GloveBoxPasswordFragment --|> BasePinPasswordFragment

    class GloveBoxPasswordTools {
        -config: PinPasswordConfig
        +impl: PinPasswordTools
    }
    GloveBoxPasswordTools ..> PinPasswordTools : 委托
    class GloveBoxPasswordCheckUtils {
        +impl: PinPasswordCheckUtils
    }
    GloveBoxPasswordCheckUtils ..> PinPasswordCheckUtils : 委托
    class GloveBoxAccountVerifyUtils {
        +openVerifyPage(isNeedCheckGear, callback)
    }
    GloveBoxAccountVerifyUtils ..> PinAccountVerifyUtils : 委托
    PinAccountVerifyUtils ..> AccountVerifyHelper : 包装

    class GloveBoxModulePublishService {
        +getServiceHandlerList()
    }
    class GloveBoxNavigationHandler {
        +onAction(Open)
        -openGloveBox()
        -showPasswordCheckDialog()
    }
    GloveBoxModulePublishService ..> GloveBoxNavigationHandler : 发布
    GloveBoxNavigationHandler ..> GloveBoxController : 复用开箱逻辑

2. glovebox 包 10 类逐一

目录:settingsPage/VehicleBodyControl/src/main/java/com/android/car/settings/miauto/vehicle/glovebox/

2.1 GloveBoxController.kt(主控制器,~340 行)

继承 BaseVehicleButtonIconToggleGroupPropController(:54-64),双 Tab 宿主。

成员行号职责
TAB_INDEX_GLOVE_BOX_SWITCH = 0:73Tab0 开箱
TAB_INDEX_GLOVE_BOX_PASSWORD_SET = 1:76Tab1 密码设置
setGloveBoxPwdSettingEntryVisibility():86-96按登录态显隐 Tab1
getPropertyIdSet():99-102订阅 GLOVE_BOX + CAR_MODE_STATUS
initLocalVehicleControls():111-138创建两个 Tab 并挂点击监听
onCreateInternal():140-1482 列布局、标题、注册账号广播
onStartInternal() / onDestroyInternal():150-160刷新可见性 / 注销广播
onHandlePropertyChange():166-188属性变化 → 更新 Tab 状态(见 §3)
showPasswordCheckDialog():196-203拉密码框,成功 → openGloveBox(WITH_PASSWORD)
showAccountVerifyPage():210-219拉账号验证,成功 → 清零 + 开箱 + 忘记密码埋点
openGloveBox(openType):222-229setVehicleProperty(GLOVE_BOX, OPEN) + 埋点
reportGloveBoxOpenTrack() / reportGloveBoxForgotPasswordTrack():235-258埋点(07 篇)
showGloveBoxSwitchClick():261-278开箱主分发(02 篇 §1)
showGloveBoxSwitchUnavailableNotify():288-302不可用 Toast 三分支
registerReceiver() / unregisterReceiver():305-340账号广播(API33+ EXPORTED)

点击注册(:115-137):

mSupportedCtrls.add(VehicleBodyPropertyMap.createGloveBoxSwitchCtrl()
    .withOnClickListener { tab ->
        if (tab.isAvailable) showGloveBoxSwitchClick(tab)
        else showGloveBoxSwitchUnavailableNotify()
    })
mSupportedCtrls.add(VehicleBodyPropertyMap.createGloveBoxPasswordSetCtrl()
    .withOnClickListener { tab ->
        if (tab.isAvailable)
            context.startActivity(Intent(context, GloveBoxSettingsDialogActivity::class.java))
        else
            MiCarToastUtils.showToast(R.string.glove_box_fail_valet_mode)
    })

2.2 GloveBoxSettingsDialogActivity.kt(密码管理弹窗,~245 行)

成员行号职责
initView():64-109绑定 Switch/修改密码行;Switch 拦截器(isPrevent 恒 true)+ 双击防抖
handleSwitchChange():145-186三态分支:锁定→账号验证;未设→SET_PWD;已设→CHECK_PWD+isClosePwd→clearPwd
goToChangePassword(skipVerify):118-135修改密码入口;skipVerify=true 时用 CHANGE_PWD_VERIFIED 跳过旧密码
showAccountVerify():193-205锁定时的账号验证,成功后按 hasPwd 决定走 SET 还是 CHANGE
updateUIState():213-223Switch 态 ← getLocalPwd() 是否为空
isConflictDialog() / conflictExceptSource():231-243冲突弹窗;放行 GloveBoxPasswordDialog

2.3 GloveBoxPasswordDialog.kt(密码框宿主,~85 行)

  • singleInstance Activity(AndroidManifest.xml:32-40),布局只有一个 FragmentContainerView。
  • showFragment()(:72-78):先 GloveBoxAccountVerifyUtils.cancelVerify() 清残留,再 replace GloveBoxPasswordFragment。
  • isConflictDialog() = true(:81)。

2.4 GloveBoxPasswordFragment.kt(~60 行)

薄壳:只提供三样东西——

override fun getPasswordConfig() = GloveBoxPasswordTools.config          // :39
override fun openAccountVerifyPage(isNeedCheckGear: Boolean, callback) =
    GloveBoxAccountVerifyUtils.openVerifyPage(isNeedCheckGear, callback) // :54-60
companion object { const val ACTION_PASSWORD_VERIFY_RESULT = ... }       // :32

所有键盘 UI/输入逻辑都在基类 BasePinPasswordFragment。

2.5 GloveBoxPasswordTools.kt(~40 行)

单例,委托 PinPasswordTools,只注入手套箱参数(:33-41):三个 Settings.Global key + 广播 Action。

2.6 GloveBoxPasswordCheckUtils.kt(~60 行)

单例,委托 PinPasswordCheckUtils,绑定 GloveBoxPasswordDialog::class.java(:29-32); goToCheckPassword()(:50-58)是拉起密码框的统一入口。

2.7 GloveBoxAccountVerifyUtils.kt(~60 行)

单例,包装 PinAccountVerifyUtils,加 P 挡检查(:45-57,02 篇 §4)。

2.8 GloveBoxNavigationHandler.kt(~130 行)

MIS 指令处理器,继承 AbsGloveCompartmentHandler(MIS SDK)。详见 05 篇。

2.9 GloveBoxModulePublishService.kt(22 行)

MIS 发布服务。详见 05 篇。

2.10 GloveBoxButton.kt(死代码)

自定义 LinearLayout 按钮(:20),仓内无任何调用方,2025-02 重构遗留。

3. UI 状态驱动详解

3.1 Tab 状态表(onHandlePropertyChange,GloveBoxController.kt:166-188)

UI 状态驱动源行号
Tab0 isAvailableVehiclePropertyUtil.isPropertyAvailable(gloveBoxStatus):176
Tab0 mIsSelected(开/关显示)getIntPropertyValue(gloveBoxStatus) == CommonParams.OpenClose.OPEN:177-178
Tab1 isAvailable!isValetMode && carMode == NORMAL:181-186
Tab1 isVisibleAccountUtils.isUserLogin()(走 setGloveBoxPwdSettingEntryVisibility):86-96
Tab0 不可用 ToastPeripheral.GLOVE_BOX_UNAVL_REASON 三分支:288-302
override fun onHandlePropertyChange(propertyValue: MiCarPropertyValue<*>?, shouldUpdateUI: Boolean) {
    if (preference.buttons.isEmpty()) return
    val gloveBoxStatus = getProperty(MiCarPropertyIds.DrivingCtrl.GLOVE_BOX)
    val gloveBoxSwitchTab = preference.buttons[TAB_INDEX_GLOVE_BOX_SWITCH]
    gloveBoxSwitchTab?.isAvailable = VehiclePropertyUtil.isPropertyAvailable(gloveBoxStatus)
    gloveBoxSwitchTab?.mIsSelected = VehiclePropertyUtil
        .getIntPropertyValue(gloveBoxStatus) == CommonParams.OpenClose.OPEN
    if (TAB_INDEX_GLOVE_BOX_PASSWORD_SET in preference.buttons.indices) {
        val carMode = getIntProperty(Driving.CAR_MODE_STATUS)
        val isValetMode = AccountUtils.isValetMode(context)
        preference.buttons[TAB_INDEX_GLOVE_BOX_PASSWORD_SET]?.isAvailable =
            !isValetMode && carMode == Driving.CarModeStatus.NORMAL
    }
    refreshUi()
}

3.2 弹窗状态刷新时机

GloveBoxSettingsDialogActivity.updateUIState() 的触发点: onCreate(:68)+ 每个密码操作 callback 末尾(成功失败都刷,:132,159,162,177,181,199,202)—— 保证任何操作后 Switch 与「修改密码」行立即与真实存储一致。

3.3 加载态

代码中没有任何 loading/进度 UI。开箱下发与密码验证均无进度指示; 密码输入完成后有 200ms 延迟(PinPasswordTools.DELAY_TIMES)再校验。 排查「点了没反应」类问题时注意:UI 上不会有任何中间态反馈。

4. 通用 PIN 框架精讲(base/settingsBaseUi/…/common/password/)

4.1 PinPasswordTools.kt(密码核心)

成员行号职责
PWD_WRONG_MAX_TIMES=5 / PWD_MAX_LENGTH=4 / DELAY_TIMES=200L / PWD_EMPTY_VALUE=-1:62-71核心常量
mFirstInputPwd / mSecondInputPwd:77-80两个 IntArray(4)
inputNum(num):127-155输入;满 4 位后 200ms 延迟触发 verify 或切第二次输入
checkDoubleInputs():214-228两次输入逐位比对
verifyPwd():238-258SHA-256 比对 + 错误计数 + 5 次锁定回调
savePwd():266-275写哈希+重新生成盐
clearPwd():282-290清哈希+清零错误
getLocalPwd() / isPwdLimit():297-310,336-341读哈希 / 是否达上限
setPwdWrongTimes() / getPwdWrongTimes():313-330错误计数读写

4.2 BasePinPasswordFragment.kt(键盘 Fragment)

成员行号职责
布局 fragment_pin_pwd_dialog.xmlres4 指示位 + 10 数字键 + 删除 + 忘记密码
initView():166-206View 绑定
initData():209-251按 Intent extra 决定输入模式/标题/忘记密码可见性
initClickListener():254-286数字/删除/忘记密码点击
密码回调绑定:289-362verifyCallback/setPwdCallback/onPwdInputChangeCallback
忘记密码点击:262-276openAccountVerifyPage(false) → 切 SET 模式
锁定自动拉账号验证:316-326第 5 次错自动触发
onPwdInputChange():388-410指示器:末位明文其余圆点
noticeVerifyResult():422-433LocalBroadcast + finish

4.3 PinPasswordCheckUtils.kt(拉窗+结果桥)

// :132-163 统一入口
fun goToCheckPassword(context, event, isClosePwd, callback, extras) {
    mCallback = callback
    val intent = Intent(context, dialogActivityClass)     // 由子类绑定 GloveBoxPasswordDialog
    intent.putExtra(PARAM_EVENT_ID_KEY, event)
    intent.putExtra(PARAM_IS_CLOSE_PWD, isClosePwd)
    intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
    context.startActivity(intent)
}
// :64-70 广播接收 → 回调
mBroadcastReceiver: onReceive → mCallback?.invoke(result)

4.4 PinPasswordEncryptor(哈希算法)

  • hashPassword(input, saltKey) = SHA-256(salt + contentToString(input))十六进制(:52-64);
  • 盐 = System.currentTimeMillis(),savePwd 时写入 Settings.Global(:113-132);
  • 换盐即旧哈希失效(:30-32 注释)。

4.5 输入模式常量(PinPasswordTools)

常量用途
INPUT_EVENT_SET_PWD设置新密码(两次输入)
INPUT_EVENT_CHECK_PWD验证密码(一次输入)
INPUT_EVENT_CHANGE_PWD修改(先验旧再两次新)
INPUT_EVENT_CHANGE_PWD_VERIFIED账号验证已过,跳过旧密码直接设新

5. 冲突弹窗机制(BaseConflictDialogActivity)

sequenceDiagram
    participant A as GloveBoxSettingsDialogActivity
    participant M as DialogActivityConflictManager
    participant B as 其它冲突弹窗
    participant P as GloveBoxPasswordDialog

    A->>M: onCreate: closeOtherDialog("GloveBoxSettingsDialogActivity")
    M-->>B: 事件通知
    B->>B: finish()
    A->>P: startActivity(密码框)
    Note over A,P: P 在 A 的 conflictExceptSource() 白名单里<br/>GloveBoxSettingsDialogActivity.kt:241-243
    P-->>A: 叠加显示,不互踢
  • 作用:车机上同时可能有多个 Dialog 风格 Activity(账号验证页、其它设置的弹窗), 同一时刻只留一个;手套箱两弹窗构成「设置弹窗 → 密码弹窗」的合法叠加,其余互斥。

6. 资源文件清单

资源路径状态
入口声明res/xml/miauto_vehicle_body_ctrl_settings_fragment.xml:219-229使用中
设置弹窗布局res/layout/glovebox_pwd_settings_dialog_activity.xml使用中
密码框容器布局res/layout/activity_glove_box_pwd_dialog.xml使用中(仅容器)
键盘布局base/settingsBaseUi/res/layout/fragment_pin_pwd_dialog.xml使用中(通用)
旧键盘布局res/layout/fragment_glove_box_pwd_dialog.xml遗留未用
旧按钮布局res/layout/micar_glove_box_btn.xml遗留未用
图标drawable/ic_glove_box_switch.xml、ic_glove_box_settings.xml、ic_glove_box_pwd_delete.xml前两个使用中
颜色color/glove_box_bg_color.xml、glove_box_text_color.xml、glove_box_subtitle.xml使用中
文案values/strings.xml 与 values-zh-rCN/strings.xml L228-261使用中