手套箱 02 · 页面结构与门控
回答一个问题:手套箱入口挂在哪个页面、由什么控制显示/可用/可见、两个 Tab 的三态语义。
基线声明
- 主锚点(W):
/home/zbc/car/fuben4_micarsettings/MiCarSettings-5458412,origin/dev @930be9145。行号默认以 W 为准。- 出货对照:
/home/zbc/car/pack_a17_cn_20260923(pack_a17_cn_20260923 快照)。- 平台源码:
/home/zbc/pangu/xcd_xring_a17_fermi/vendor/micar。前置阅读:00-手套箱-README(功能全景);属性值语义表见 01-需求与信号契约。
0. TL;DR
- 入口挂在 设置 → 车辆控制(
VehicleControlSettingsFragment),XML 声明为一个MiCarPreferenceCategory分组,内嵌一个ButtonIconTogglePreference,Controller 是GloveBoxController。 - 三道门控,自上而下:①配置字门控——
GLOVE_BOX_LOCK_TYPE_CONFIG != POWER(2)时整组从页面移除;②信号状态门控——GLOVE_BOX属性的status决定「打开手套箱」Tab 是否可用;③账号/代客门控——账号未登录时「密码设置」Tab 直接 GONE,代客模式或非 NORMAL 车模式下置灰。 - 两个 Tab 的三态(
isVisible/isAvailable/mIsSelected)分别映射到 View 的VISIBLE/GONE、isEnabled + alpha、isSelected,渲染收口在ButtonIconAdapter。 - 页面不开,Controller 不建、属性不订阅、广播不注册——手套箱的全部逻辑随页而生灭,这是物理按键需求必须另起结构的根因(见第 5 节)。
1. 页面挂载链:XML key → Fragment → Controller → Tab
1.1 挂载链总览
flowchart TD A["XML: miauto_vehicle_body_ctrl_settings_fragment.xml<br/>Category key=pk_vehicle_control_glove_box"] --> B["Fragment: VehicleControlSettingsFragment<br/>getPreferenceScreenResId 加载 XML"] B --> C{"onCreatePreferences<br/>removePreferencesIfNeed()"} C -->|"配置字!=POWER, key在移除列表"| D["整组 Category 被 removePreference<br/>Controller 不会实例化"] C -->|"配置字==POWER, 保留"| E["遍历 mPreferenceControllers<br/>按 key 绑定 Preference"] E --> F["GloveBoxController 实例化<br/>(XML settings:controller 声明)"] F --> G["initLocalVehicleControls()<br/>注册两个 Tab"] G --> H["Tab0: 打开手套箱<br/>createGloveBoxSwitchCtrl"] G --> I["Tab1: 手套箱密码设置<br/>createGloveBoxPasswordSetCtrl"]
1.2 XML 声明
settingsPage/VehicleBodyControl/src/main/res/xml/miauto_vehicle_body_ctrl_settings_fragment.xml:219-229:
<!-- 密码手套箱 -->
<com.android.car.settings.miauto.preferences.MiCarPreferenceCategory
android:key="@string/pk_vehicle_control_glove_box"
android:title="@string/micar_vehicle_control_glove_box">
<com.android.car.settings.miauto.preferences.ButtonIconTogglePreference
android:key="@string/pk_vehicle_control_glove_box_toggle"
android:layout="@layout/micar_ui_pref_button_layout"
settings:controller="com.android.car.settings.miauto.vehicle.glovebox.GloveBoxController" />
<com.android.car.settings.miauto.preferences.MiCarSpaceDividerPreference
android:key="@string/pk_vehicle_control_glove_box_divider"/>
</com.android.car.settings.miauto.preferences.MiCarPreferenceCategory>要点:
- 门控作用于外层 Category(
pk_vehicle_control_glove_box),不是内层 toggle。移除 Category 等于把标题、按钮、分割线一锅端。 - Controller 通过
settings:controller属性静态声明在 XML 里,键值字符串定义于settingsPage/VehicleBodyControl/src/main/res/values/micar_preference_keys.xml:80-82(pk_vehicle_control_glove_box/_divider/_toggle)。
1.3 Fragment 与 key 过滤机制
宿主 Fragment 是 VehicleControlSettingsFragment(W:settingsPage/VehicleBodyControl/src/main/java/com/android/car/settings/miauto/vehicle/VehicleControlSettingsFragment.java):
:70@RouterProvider(path = VEHICLE_CONTROL_URI_PATH)—— 路由「车辆控制」页;:83-87getPreferenceScreenResId()返回R.xml.miauto_vehicle_body_ctrl_settings_fragment;:98getPreferenceKeyResIdsToRemove()返回本页要隐藏的 preference key 资源 id 列表。
过滤机制的执行点在基类 BaseXmlParserSettingsFragment(W:base/settingsBaseUi/src/main/java/com/android/car/settings/common/BaseXmlParserSettingsFragment.java):
:647-667onCreatePreferences():addPreferencesFromResource建树 → 遍历mPreferenceControllers按 key 绑定 Preference(:656-660)→:662removeHiddenList()→:663removePreferencesIfNeed()。:1201-1209removePreferencesIfNeed():拿到getPreferenceKeyResIdsToRemove()列表,逐个removePreference(preferenceKeyResId)。:1211-1245removePreference():非哨兵 id 走preferenceScreen.findPreference(getContext().getString(preferenceKeyResId))再doRemovePreference;哨兵PREF_KEY_RES_ID_CHECK_EMPTY_CATEGORY(:120)用于「组内删空后连 Category 一起删」的收尾。
注意 :1184-1188 的注释约束:getPreferenceKeyResIdsToRemove() 也会被语音搜索 APT 反射调用,此时 Context 不可用——所以手套箱门控①里读的 CarConfigManager.INSTANCE.getGloveBoxLockType() 走的是带缓存的静态读(见 2.1),不依赖 Fragment Context。
1.4 Controller 创建与两个 Tab 注册
GloveBoxController(W:settingsPage/VehicleBodyControl/src/main/java/com/android/car/settings/miauto/vehicle/glovebox/GloveBoxController.kt):
:54-64继承BaseVehicleButtonIconToggleGroupPropController;:111-138initLocalVehicleControls()先mSupportedCtrls.clear(),再依次 add 两个控件,各自带withOnClickListener:
mSupportedCtrls.add(VehicleBodyPropertyMap.createGloveBoxSwitchCtrl()
.withOnClickListener { tab: Tab<*> ->
MLog.d("%s #onTabClicked() on tab: %s", implClassName, tab)
if (tab.isAvailable) {
showGloveBoxSwitchClick(tab)
} else {
showGloveBoxSwitchUnavailableNotify()
}
}
)
mSupportedCtrls.add(VehicleBodyPropertyMap.createGloveBoxPasswordSetCtrl()
.withOnClickListener { tab: Tab<*> ->
MLog.d("%s #onTabClicked() on tab: %s", implClassName, tab)
if (tab.isAvailable) {
context.startActivity(
Intent(context, GloveBoxSettingsDialogActivity::class.java)
)
} else {
MiCarToastUtils.showToast(R.string.glove_box_fail_valet_mode)
}
}
)两个 Tab 的工厂在 VehicleBodyPropertyMap.kt(W:同模块 vehicle/VehicleBodyPropertyMap.kt):
:42-50createGloveBoxSwitchCtrl():属性MiCarPropertyIds.DrivingCtrl.GLOVE_BOX,图标ic_glove_box_switch,文案R.string.glove_box_switch(中文「打开手套箱」,values-zh-rCN/strings.xml:251),默认值CommonParams.OpenClose.OPEN,带withAllowClickWhenDisabled(true)与语音 flagTAB_FLAG_GLOVE_BOX_SWITCH(TabKeyMapHelper.java:24)。:55-63createGloveBoxPasswordSetCtrl():同一属性 ID,图标ic_glove_box_settings,文案R.string.glove_box_pwd_set(「手套箱密码设置」,values-zh-rCN/strings.xml:252),默认值false,flagTAB_FLAG_GLOVE_BOX_PASSWORD_SET(TabKeyMapHelper.java:25)。
withAllowClickWhenDisabled(true) 是关键:禁用态下点击事件依然会上抛,所以上面两个 listener 的 else 分支才有机会弹 Toast 解释「为什么不能用」。字段定义见 CarUiTabLayout.java:760,渲染侧透传见 ButtonIconAdapter.kt:168。
2. 三道门控逐一拆解
2.1 门控① 配置字 LOCK_TYPE_CONFIG == POWER,否则整组移除
代码证据(W:VehicleControlSettingsFragment.java:168-172):
// 非电动手套箱 屏蔽功能
if (Peripheral.GloveBoxLockTypeConfig.POWER
!= CarConfigManager.INSTANCE.getGloveBoxLockType()) {
ids.add(R.string.pk_vehicle_control_glove_box);
}配置字读取在 CarConfigManager.kt:139-146(W:base/settingsVehicleLib/src/main/java/com/android/car/settings/miauto/CarConfigManager.kt):
fun getGloveBoxLockType(): Int {
val res = getIntProperty(
MiCarPropertyIds.DrivingCtrl.GLOVE_BOX_TYPE,
Peripheral.GloveBoxLockTypeConfig.MANUAL
)
MLog.tag(TAG).d("getGloveBoxLockType: $res")
return res
}常量定义(平台源码 vendor/micar/packages/services/MiVehicleService/support-lib/src/mi/car/config/Peripheral.java):
:596GLOVE_BOX_LOCK_TYPE_CONFIG = 0x409(属性低位 ID;完整 ID 形如0x6140b40x段,同前缀的GLOVE_BOX=0x6140b407见 00-README 第 24 行);:1421-1424GloveBoxLockTypeConfig { MANUAL = 0x1; POWER = 0x2 }。
映射关系:MiCarPropertyIds.java:1487 DrivingCtrl.GLOVE_BOX_TYPE = Peripheral.GLOVE_BOX_LOCK_TYPE_CONFIG。
语义与默认值:默认 MANUAL(1)=机械手套箱=不显示入口;只有读出 POWER(2)(电动手套箱)才保留。读不到(服务不可用)时 getIntProperty 返回默认值(CarConfigManager.kt:100-106 的 INVALID_CONFIG_VAL 探测逻辑),即「读不到就藏」——安全方向是隐藏。
台架验证方法(2026-10-19 mach 实证:mock 2 后入口出现):
- 台架注入配置字
0x409 = 2(POWER); adb shell am force-stop com.android.car.settings(清缓存重读);- 重进 设置 → 车辆控制,「手套箱」分组出现;
- 反向 mock
0x409 = 1(MANUAL)重复上述步骤,分组消失。 判定以 App 侧实际渲染为准(uiautomator dump 查pk_vehicle_control_glove_box或截图);vehicled 不持久,重启回滚是预期行为。
2.2 门控② GLOVE_BOX.status 驱动开关 Tab 的 isAvailable
代码证据(W:GloveBoxController.kt:166-188,onHandlePropertyChange):
// 更新手套箱开关状态
val gloveBoxStatus = getProperty(MiCarPropertyIds.DrivingCtrl.GLOVE_BOX)
val gloveBoxSwitchTab: Tab<*>? = preference.buttons[TAB_INDEX_GLOVE_BOX_SWITCH]
MLog.tag(TAG).d("onPropertyChanged: gloveBoxStatus=$gloveBoxStatus")
gloveBoxSwitchTab?.isAvailable = VehiclePropertyUtil.isPropertyAvailable(gloveBoxStatus)
gloveBoxSwitchTab?.mIsSelected = VehiclePropertyUtil
.getIntPropertyValue(gloveBoxStatus) == CommonParams.OpenClose.OPENisPropertyAvailable 的判定(W:base/settingsVehicleLib/src/main/java/com/android/car/settings/miauto/vehicle/VehiclePropertyUtil.java:173-182):属性对象非空、未被 mock 置不可用、且 getStatus() == MiCarPropertyValue.STATUS_AVAILABLE。
属性订阅集合在 :99-102:GLOVE_BOX + Driving.CAR_MODE_STATUS 两个。属性 ID 映射:MiCarPropertyIds.java:1485 DrivingCtrl.GLOVE_BOX = Peripheral.GLOVE_BOX,平台侧 :576 GLOVE_BOX = 0x407。
不可用时的点击分支(:288-302 showGloveBoxSwitchUnavailableNotify)按 Peripheral.GLOVE_BOX_UNAVL_REASON 选文案:
NOT_DIS_ARMED(1)→glove_box_fail_need_unlock(需先解锁车辆);ANTY_PLAY(2)→glove_box_fail_anty_play(防玩模式);- 其他 → 代客模式给
glove_box_fail_valet_mode「请在用户模式下使用该功能」(values-zh-rCN/strings.xml:245),否则不弹。
枚举值定义见平台 Peripheral.java:1426-1430(IDLE=0 / NOT_DIS_ARMED=1 / ANTY_PLAY=2)。
台架验证方法:注入 0x407 属性 status 为不可用 → 开关 Tab 置灰(alpha 降为 DISABLE 档,渲染见 3.2);点击应弹对应 reason 的 Toast;恢复 status 可用 → 置灰解除。reason 属性分别注入 1/2 验证两条文案。
2.3 门控③ 账号登录驱动密码 Tab isVisible;代客+CAR_MODE 驱动其 isAvailable
密码入口同时受两套条件管,可见性与可用性分离:
可见性(W:GloveBoxController.kt:86-96):
private fun setGloveBoxPwdSettingEntryVisibility() {
val gloveBoxSetTab: Tab<*>? = preference.buttons[TAB_INDEX_GLOVE_BOX_PASSWORD_SET]
if (AccountUtils.isUserLogin(BaseApplication.getGlobalApp().applicationContext)) {
MLog.i(TAG, "Account login, showing glove box password setting entry")
gloveBoxSetTab?.isVisible = true
} else {
MLog.i(TAG, "Account logout, hiding glove box password setting entry")
gloveBoxSetTab?.isVisible = false
}
refreshUi()
}调用时机有两个:onStartInternal()(:150-153,页面每次回到前台刷一次)和账号广播接收器(:305-332 registerReceiver,监听 USER_DATA_CHANGED="com.mi.car.account.USER_DATA_CHANGED" 与 LOGIN_ACCOUNTS_POST_CHANGED="android.accounts.LOGIN_ACCOUNTS_POST_CHANGED",常量见 CarSettingsJump.kt:370-371,权限 mi.car.account.permission.ACCESS_BROADCAST 见 :375;API 33+ 走带 RECEIVER_EXPORTED 标志的注册重载,:320-325)。
可用性(:181-186,在 onHandlePropertyChange 内):
if (TAB_INDEX_GLOVE_BOX_PASSWORD_SET in preference.buttons.indices) {
val carMode = getIntProperty(Driving.CAR_MODE_STATUS)
val isValetMode = AccountUtils.isValetMode(context)
val gloveBoxSetTab: Tab<*>? = preference.buttons[TAB_INDEX_GLOVE_BOX_PASSWORD_SET]
gloveBoxSetTab?.isAvailable = !isValetMode && carMode == Driving.CarModeStatus.NORMAL
}AccountUtils(W:base/settingsBaseLib/src/main/java/com/android/micar/settings/common/AccountUtils.java)::217-221 isValetMode 读 Settings.Secure 的访客标记;:224-226 isUserLogin = getXiaomiAccount(context) != null。
不可用时的点击反馈固定为 glove_box_fail_valet_mode Toast(:134)。
台架验证方法:
- 可见性:退出小米账号 → 密码 Tab 从布局消失(GONE);重新登录 → 重现。广播路径可用「退出/登录账号」真实操作触发,无需 mock。
- 可用性:进入代客模式 → 密码 Tab 置灰、点击弹「请在用户模式下使用该功能」;退出代客且
CAR_MODE_STATUS == NORMAL→ 恢复可点。
3. Tab 三态语义表
三态字段定义在 CarUiTabLayout.Tab(W:base/settingsBaseUi/src/main/java/com/android/car/settings/miauto/common/CarUiTabLayout.java):
:708protected Boolean isAvailable = true;:753public boolean isVisible = true;:754public boolean mIsSelected;
| 态 | 定义处 | 手套箱 Tab0(开关)驱动源 | 手套箱 Tab1(密码)驱动源 | UI 表现(渲染处) |
|---|---|---|---|---|
isVisible | CarUiTabLayout.java:753 | 恒 true(代码未改它) | 账号登录=true / 登出=false(GloveBoxController.kt:86-96) | ButtonIconAdapter.kt:68-72:true→View.VISIBLE,false→View.GONE,占位整块移除 |
isAvailable | CarUiTabLayout.java:708 | GLOVE_BOX 属性 status==AVAILABLE(GloveBoxController.kt:176;VehiclePropertyUtil.java:173-182) | !isValetMode && carMode==NORMAL(GloveBoxController.kt:185) | ButtonIconAdapter.kt:165 buttonIconView.isEnabled = tab.isAvailable;:169-173 不可用→alpha 降为 DISABLE 档(置灰) |
mIsSelected | CarUiTabLayout.java:754 | 属性值==OpenClose.OPEN 时 true(GloveBoxController.kt:177-178) | 不用(默认 false,VehicleBodyPropertyMap.kt:60) | ButtonIconAdapter.kt:166-167 同步到 View 的 isSelected;:121-127 选中且有 checkedText 时换文案 |
三态正交,可任意组合。两个实战组合:
- 已开箱:Tab0
isAvailable=true + mIsSelected=true→ 高亮可选中态,点击走showGloveBoxSwitchClick第一分支弹「请手动关闭手套箱」(:262-263,文案values-zh-rCN/strings.xml:249)。手套箱只有「开」没有「关」的下发,关箱是物理动作。 - 未登录:Tab1
isVisible=false→ 直接 GONE,此时它的isAvailable是多少都无所谓,用户根本看不到。
4. 布局与标题资源
- 2 列布局:
GloveBoxController.kt:70private val TAB_COLUMN_SIZE: Int = 2;onCreateInternal(:140-148)里经preference.config.putInt(PreferenceArgs.PREFERENCE_TAB_ITEM_COLUMN_SIZE, TAB_COLUMN_SIZE)下发。注意:142-144与:146对同一个 key 重复 putInt 了两次,无害但属冗余。Tab 索引常量::73TAB_INDEX_GLOVE_BOX_SWITCH = 0、:76TAB_INDEX_GLOVE_BOX_PASSWORD_SET = 1。 - 单 Tab 布局模板:基类
BaseVehicleButtonIconToggleGroupPropController.kt:29-40在configPreference()里把PREFERENCE_TAB_ITEM_LAYOUT_RES定为R.layout.micar_ui_button_icon_toggle_layout、权重ITEM_WEIGHT_WRAP_CONTENT。 - 分组标题:
:145preference.assistViewTitle = getString(R.string.micar_vehicle_control_glove_box)(语音「可见即可说」用标题);XML 侧 Category title 同为该资源(miauto_vehicle_body_ctrl_settings_fragment.xml:222)。多语言:values/strings.xml:171英文「Glovebox」;values-zh-rCN/strings.xml:170中文「手套箱」。
5. 「页面不开则逻辑不活」——生命周期随页
Controller 的一生完全绑在 Preference 树上:
sequenceDiagram participant F as "VehicleControlSettingsFragment" participant Base as "BaseXmlParserSettingsFragment" participant C as "GloveBoxController" participant V as "车辆属性服务" F->>Base: onCreatePreferences() Base->>Base: addPreferencesFromResource(XML) alt 门控①命中(非电动手套箱) Base->>Base: removePreference(pk_vehicle_control_glove_box) Note over C: 不会实例化, 全程不存在 else 门控①放行 Base->>C: 实例化 + setPreference() C->>C: onCreateInternal(): initButtons/config/列数/registerReceiver Note over C: View 绑定后才 registerPropertyCallback<br/>(BaseVehicleButtonGroupPropController.java:55-58) V-->>C: 属性上报(GLOVE_BOX / CAR_MODE_STATUS) C->>C: onStartInternal(): 刷密码入口可见性 F->>C: 页面销毁 onDestroyInternal(): unregisterReceiver end
关键事实:
- 属性回调注册发生在 View bind 完成之后(
BaseVehicleButtonGroupPropController.java:55-58的setViewBindedListener),不是 Controller 一建就订阅; - 账号广播在
onCreateInternal注册、onDestroyInternal注销(GloveBoxController.kt:147、:155-158),页面销毁即停; - 门控①命中时 Category 被移除,Controller 连实例化都不会发生。
结构性推论:在这套范式里,「设置页不开,手套箱逻辑就是死的」。任何要求页面关闭时也要响应的能力(典型如物理按键开箱、语音在任意界面唤醒后的状态查询)都不能寄生在这个 Controller 里,必须另起一个独立于页面生命周期的受理端。这正是物理按键扩展的设计动机,展开见 07-物理按键扩展设计。语音/远程已走的独立通道(ModulePublishService)参见 04-密码与账号验证体系。
6. 门控判定全流程图
从「入口是否出现」到「点击走哪条分支」的完整判定:
flowchart TD S["用户进入 设置→车辆控制"] --> G1{"门控① LOCK_TYPE_CONFIG==POWER(2)?<br/>VehicleControlSettingsFragment.java:168-172"} G1 -->|"否(MANUAL/读不到)"| X1["整组移除<br/>页面无手套箱入口"] G1 -->|"是"| R["渲染两个 Tab"] R --> C1{"点击哪个 Tab?"} C1 -->|"Tab0 打开手套箱"| G2{"门控② tab.isAvailable?<br/>(GLOVE_BOX.status)"} G2 -->|"否"| T1["Toast: 按 UNAVL_REASON 选文案<br/>未解锁/防玩/代客"] G2 -->|"是"| S1{"mIsSelected?<br/>(已开?)"} S1 -->|"已开"| T2["Toast: 请手动关闭手套箱"] S1 -->|"未开"| P1{"本地有密码?"} P1 -->|"无"| O1["直接下发 OPEN 开箱"] P1 -->|"有"| P2{"错误次数达上限?"} P2 -->|"是"| O2["车主账号验证页(扫码)<br/>通过后清零+开箱"] P2 -->|"否"| O3["密码输入弹窗<br/>验证通过开箱"] C1 -->|"Tab1 密码设置"| G3{"门控③a isVisible?<br/>(账号已登录)"} G3 -->|"否(未登录)"| X2["Tab 已 GONE<br/>不存在点击"] G3 -->|"是"| G4{"门控③b isAvailable?<br/>(非代客 && CAR_MODE==NORMAL)"} G4 -->|"否"| T3["Toast: 请在用户模式下使用该功能"] G4 -->|"是"| O4["跳 GloveBoxSettingsDialogActivity"]
开箱后的密码生命周期(设置/修改/关闭/忘记密码)不在本篇范围,见 04-密码与账号验证体系;点击埋点(54662 等)见 05-埋点体系。
7. 出货对照
GloveBoxController.kt 在 W 与出货快照 pack_a17_cn_20260923 之间 diff 为空、逐字节一致(含 :318-331 的 API 33 RECEIVER_EXPORTED 注册段)。本篇全部行号对出货线同样适用。
证据清单
| 文件(相对 W 根或注明绝对路径) | 行号区间 | 说明 |
|---|---|---|
| settingsPage/VehicleBodyControl/…/vehicle/VehicleControlSettingsFragment.java | 70, 83-87, 98-99, 168-172 | 路由、XML 加载、key 移除列表、手套箱门控① |
| settingsPage/VehicleBodyControl/src/main/res/xml/miauto_vehicle_body_ctrl_settings_fragment.xml | 219-229 | 手套箱 Category + ButtonIconTogglePreference + controller 声明 |
| settingsPage/VehicleBodyControl/src/main/res/values/micar_preference_keys.xml | 80-82 | 三个 preference key 字符串 |
| base/settingsBaseUi/…/common/BaseXmlParserSettingsFragment.java | 120, 647-667, 1190-1245 | key 过滤执行链(removePreferencesIfNeed/removePreference) |
| base/settingsVehicleLib/…/miauto/CarConfigManager.kt | 100-106, 139-146 | getGloveBoxLockType 读配置字,读不到回默认 MANUAL |
| (平台)vendor/micar/…/mi/car/config/Peripheral.java | 576, 596, 1421-1424, 1426-1430 | GLOVE_BOX=0x407、LOCK_TYPE_CONFIG=0x409、MANUAL/POWER、UNAVL_REASON 枚举 |
| base/settingsBaseLib/…/miauto/vehicle/MiCarPropertyIds.java | 1485-1487 | DrivingCtrl.GLOVE_BOX / GLOVE_BOX_TYPE 映射 |
| settingsPage/VehicleBodyControl/…/vehicle/glovebox/GloveBoxController.kt | 54-64, 70-76, 86-102, 111-158, 166-188, 261-302, 305-332 | 本篇主角:双 Tab 注册、三道门控、生命周期、广播注册 |
| settingsPage/VehicleBodyControl/…/vehicle/VehicleBodyPropertyMap.kt | 42-50, 55-63 | 两个 Tab 的工厂(图标/文案/默认值/tabFlag) |
| base/settingsBaseUi/…/miauto/voiceassist/TabKeyMapHelper.java | 24-25 | TAB_FLAG_GLOVE_BOX_SWITCH / PASSWORD_SET |
| base/settingsVehicleLib/…/vehicle/BaseVehicleButtonGroupPropController.java | 49-59, 162-176 | onCreateInternal 序列、View bind 后才注册属性回调 |
| base/settingsVehicleLib/…/vehicle/BaseVehicleButtonIconToggleGroupPropController.kt | 14-41 | 单 Tab 布局模板与权重 |
| base/settingsBaseUi/…/miauto/common/CarUiTabLayout.java | 708, 753-754, 760, 1066-1079 | Tab 三态字段定义与 setter |
| base/settingsBaseUi/…/miauto/common/adapter/ButtonIconAdapter.kt | 68-72, 121-127, 164-173 | 三态到 View 的渲染收口(VISIBLE/GONE、enable+alpha、selected) |
| base/settingsVehicleLib/…/vehicle/VehiclePropertyUtil.java | 173-182 | isPropertyAvailable 判定(STATUS_AVAILABLE) |
| base/settingsBaseLib/…/micar/settings/common/AccountUtils.java | 217-226 | isValetMode / isUserLogin |
| base/settingsBaseUi/…/common/pageroute/CarSettingsJump.kt | 370-375 | 账号广播 action 与权限常量 |
| settingsPage/VehicleBodyControl/src/main/res/values/strings.xml / values-zh-rCN/strings.xml | 171 / 170, 245, 249, 251-252 | 标题与 Toast 多语言文案 |
| (出货对照)pack_a17_cn_20260923 同路径 GloveBoxController.kt | 全文件 | 与 W diff 为空,逐字节一致 |