RearProjectionChildLockController 拆解

源码:settingsPage/micarDisplaySettings/src/main/java/com/android/car/settings/miauto/display/RearProjectionChildLockController.kt 父类:base/settingsVehicleLib/.../vehicle/BaseVehicleNewSwitchPrefController.kt

一、定位

后排投影的童锁开关控制器,与模式 Tab 并列挂在「后排投影」分组下。它驱动一个 MiCarNewSwitchPreference(开关),开启后锁定后排乘客对投影幕布的物理操控,防止儿童误操作。

二、继承链(与模式 Tab 对比)

classDiagram
    class PreferenceController~V~ {
        <<AOSP 顶层>>
    }
    class CarPropertyMgrPreferenceController~V~ {
        <<信号管理>>
        +getPropertyIdSet()
        +obtainSetPropertyId()
        +onHandlePropertyChange()
    }
    class BaseVehicleImageTabPreferenceController {
        <<Tab 模板>>
    }
    class BaseVehicleNewSwitchPrefController {
        <<开关模板>>
        +handlePreferenceChanged()
        #isPropertyOpen()
        #getPropertyVal()
        #isValidPropVal()
        #needSecondConfirm()
        #needIntermediateState()
    }
    class RearProjectionModeTabController {
        模式 Tab
    }
    class RearProjectionChildLockController {
        童锁开关
    }
    class RearTvChildLockSwitchController {
        后排电视童锁(对照)
    }

    PreferenceController~V~ <|-- CarPropertyMgrPreferenceController~V~
    CarPropertyMgrPreferenceController~V~ <|-- BaseVehicleImageTabPreferenceController
    CarPropertyMgrPreferenceController~V~ <|-- BaseVehicleNewSwitchPrefController
    BaseVehicleImageTabPreferenceController <|-- RearProjectionModeTabController
    BaseVehicleNewSwitchPrefController <|-- RearProjectionChildLockController
    BaseVehicleNewSwitchPrefController <|-- RearTvChildLockSwitchController

关键:童锁和模式 Tab 是 CarPropertyMgrPreferenceController两条平行分支——一个走 Tab 模板,一个走 Switch 模板,共享底层信号管理基础设施。

三、UI 类型

驱动 MiCarNewSwitchPreference(小米自研开关组件)。用户看到的是一个开关(非 Tab、非进入二级页)。

XML(miauto_display_atmosphere_settings_fragment.xml:258-262):

<com.android.car.settings.miauto.preferences.MiCarNewSwitchPreference
    android:key="@string/pk_settings_display_rear_projection_child_lock"
    android:title="@string/settings_display_rear_projection_child_lock"
    android:summary="@string/settings_display_rear_projection_child_lock_summary"
    settings:controller="...RearProjectionChildLockController" />

四、信号:收发同信号(与模式 Tab 不同)

维度童锁模式 Tab(对照)
上行监听PROJECTION_CHILD_LOCK (0x634)PROJECTION_SCREEN_STATUS (0x62b)
下行写入同 0x634(默认)SCREEN_CONTROL_COMMAND (0x637)
模式收发共用收发分离

童锁未重写 obtainSetPropertyId(),走基类默认实现(返回 getPropertyIdSet() 第一项,即 0x634 自身)。getPreferenceType() 返回 MiCarNewSwitchPreference(BaseVehicleNewSwitchPrefController.kt:37-39)。

五、值映射(注意:命名反向于直觉)

flowchart LR
    L["信号 LOCKED (0x1)"] -->|isPropertyOpen=true| ON["开关 ON 童锁启用"]
    U["信号 UNLOCKED (0x2)"] -->|isPropertyOpen=false| OFF["开关 OFF"]
    I["信号 INVALID (0x0)"] -->|isValidPropVal=false| DIS["置灰,不刷新勾选"]

RearProjectionChildLockController.kt:28-40:

override fun isPropertyOpen(propVal: Int) =
    propVal in arrayOf(Peripheral.ProjectionChildLockStatus.LOCKED)
override fun getPropertyVal(isChecked: Boolean) =
    if (isChecked) Peripheral.ProjectionChildLockStatus.LOCKED
    else Peripheral.ProjectionChildLockStatus.UNLOCKED

⚠️ LOCKED(已锁定)= 开关 ON。即开关打开 = 童锁启用。命名上 LOCKED 对应”开”,容易混淆,但符合业务语义(锁定状态 = 功能生效)。

六、业务语义:车辆物理锁,非 UI 锁

文案证据(strings.xml):

  • 标题:后排投影童锁
  • 副标题:开启后,后排投影控制将被锁定(英文:Lock rear projector controls)

开启后,锁定的是后排乘客(含儿童)对幕布/投影仪的物理操控(如后排物理按键折叠/展开幕布),防止儿童误操作。设置 App 内的开关本身仍可由驾驶员操作——这是车辆侧行为锁,不是 UI 层禁用。

Settings 侧只负责下发 LOCKED/UNLOCKED,具体锁定后排哪些物理部件由车控侧实现,Settings 不感知。

七、与 RearProjectionModeTabController 的关系

结论:完全无联动,相互独立。

证据说明
XML 无 settings:dependKey两者平级挂在 category 下,无依赖关系
信号无交叉ModeTab 不监听 PROJECTION_CHILD_LOCK;童锁不监听模式 Tab 的 5 个依赖信号
代码 grep 无命中ModeTab 及其父类中无 CHILD_LOCK 引用

业务理解:童锁锁的是后排物理操控;模式 Tab 是驾驶员通过中控屏发的 SCREEN_CONTROL_COMMAND,走不同通路,故不受童锁影响。这与典型 UI dependKey 联动不同。

八、可用性 / 异常态

童锁只有一道闸:isValidPropVal()(RearProjectionChildLockController.kt:42-49):

override fun isValidPropVal(vehicleProperty: CarPropertyValue<*>?) =
    vehicleProperty?.let {
        val s = VehiclePropertyUtil.getIntPropertyValue(it)
        s == ProjectionChildLockStatus.LOCKED || s == ProjectionChildLockStatus.UNLOCKED
    } ?: false
  • 信号值 INVALID(0x0) 或 null → isValidPropVal=false → 两条效果(父类 BaseVehicleNewSwitchPrefController.kt:58-59,86-92):
    • shouldUpdateUIByPropVal=false:不刷新勾选状态(保持上一次)
    • isInnerLogicAvailable=false:配合 isPropertyAvailable 共同决定 preference.isEnabled(置灰)

未启用的能力(父类默认 false,本类未重写):

  • needIntermediateState() → 无 loading 中间态
  • needSecondConfirm() → 无二次确认弹窗
  • isPreventSwitchSlide() → 不拦截滑动

九、信号流时序

sequenceDiagram
    autonumber
    participant U as 驾驶员
    participant UI as MiCarNewSwitchPreference
    participant C as ChildLockController
    participant B as BaseSwitchController
    participant M as SettingsCarPropertyManager
    participant V as 车控(0x634)

    V->>M: 初始 LOCKED(0x1)
    M->>C: onPropertyChanged
    C->>UI: setChecked(isPropertyOpen=true)
    U->>UI: 点击关
    UI->>B: handlePreferenceChanged(false)
    B->>M: setVehicleProperty(0x634, UNLOCKED, areaId)
    M->>V: 写 PROJECTION_CHILD_LOCK_STATUS
    V-->>M: 回灌 UNLOCKED(0x2)
    M->>C: onHandlePropertyChange
    C->>UI: setChecked(false)
    Note over V: 异常: 上报 INVALID(0x0)
    V-->>M: INVALID
    C->>UI: isEnabled=false(置灰), 不刷新勾选

十、对照:RearTvChildLockSwitchController

同目录的「后排电视童锁」结构几乎一致,差异仅在:

  • 值映射用 CommonParams.Lock.LOCK/UNLOCK 而非 ProjectionChildLockStatus
  • 重写了 getAreaId() = SEAT_REAR_ROW(后排区域);而投影童锁用默认 GLOBAL

同一套 BaseVehicleNewSwitchPrefController 模板,通过重写几个钩子即可适配不同童锁场景。