手套箱 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 | :73 | Tab0 开箱 |
TAB_INDEX_GLOVE_BOX_PASSWORD_SET = 1 | :76 | Tab1 密码设置 |
setGloveBoxPwdSettingEntryVisibility() | :86-96 | 按登录态显隐 Tab1 |
getPropertyIdSet() | :99-102 | 订阅 GLOVE_BOX + CAR_MODE_STATUS |
initLocalVehicleControls() | :111-138 | 创建两个 Tab 并挂点击监听 |
onCreateInternal() | :140-148 | 2 列布局、标题、注册账号广播 |
onStartInternal() / onDestroyInternal() | :150-160 | 刷新可见性 / 注销广播 |
onHandlePropertyChange() | :166-188 | 属性变化 → 更新 Tab 状态(见 §3) |
showPasswordCheckDialog() | :196-203 | 拉密码框,成功 → openGloveBox(WITH_PASSWORD) |
showAccountVerifyPage() | :210-219 | 拉账号验证,成功 → 清零 + 开箱 + 忘记密码埋点 |
openGloveBox(openType) | :222-229 | setVehicleProperty(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-223 | Switch 态 ← getLocalPwd() 是否为空 |
isConflictDialog() / conflictExceptSource() | :231-243 | 冲突弹窗;放行 GloveBoxPasswordDialog |
2.3 GloveBoxPasswordDialog.kt(密码框宿主,~85 行)
- singleInstance Activity(
AndroidManifest.xml:32-40),布局只有一个 FragmentContainerView。 showFragment()(:72-78):先GloveBoxAccountVerifyUtils.cancelVerify()清残留,再 replaceGloveBoxPasswordFragment。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 isAvailable | VehiclePropertyUtil.isPropertyAvailable(gloveBoxStatus) | :176 |
Tab0 mIsSelected(开/关显示) | getIntPropertyValue(gloveBoxStatus) == CommonParams.OpenClose.OPEN | :177-178 |
Tab1 isAvailable | !isValetMode && carMode == NORMAL | :181-186 |
Tab1 isVisible | AccountUtils.isUserLogin()(走 setGloveBoxPwdSettingEntryVisibility) | :86-96 |
| Tab0 不可用 Toast | Peripheral.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-258 | SHA-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.xml | res | 4 指示位 + 10 数字键 + 删除 + 忘记密码 |
initView() | :166-206 | View 绑定 |
initData() | :209-251 | 按 Intent extra 决定输入模式/标题/忘记密码可见性 |
initClickListener() | :254-286 | 数字/删除/忘记密码点击 |
| 密码回调绑定 | :289-362 | verifyCallback/setPwdCallback/onPwdInputChangeCallback |
| 忘记密码点击 | :262-276 | openAccountVerifyPage(false) → 切 SET 模式 |
| 锁定自动拉账号验证 | :316-326 | 第 5 次错自动触发 |
onPwdInputChange() | :388-410 | 指示器:末位明文其余圆点 |
noticeVerifyResult() | :422-433 | LocalBroadcast + 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 | 使用中 |