后屏投影:异常态与依赖信号

本篇梳理后排投影的异常处理机制——这是该模块最复杂的部分,约占 40% 的真实复杂度。 整合自 ModeTab 业务逻辑 + 信号常量 + 对抗审查补充(HMI 等待期、getTimeoutFlag)。

一、异常态全景

后排投影的异常处理分四类,分布在信号链路的不同层:

flowchart TB
    subgraph 业务层["业务层 (ModeTabController)"]
        A1["依赖信号 → Tab 可用性<br/>isTabDependencyAvailable"]
        A2["不可用 Tab 点击 → toast<br/>showDisableReason"]
        A3["过渡态 → 调整中 toast<br/>isTabProcessing"]
        A4["TTS 信号 → 安全提示<br/>防夹/座椅联动"]
    end
    subgraph 信号层["信号层 (CarPropertyMgr / SCPM)"]
        B1["回弹忽略 + HMI 等待期<br/>shouldIgnorePropertyFeedback"]
        B2["超时监控 + 回退<br/>getTimeoutFlag / restorePropertyToPrevious"]
        B3["信号不可用 → 置灰<br/>isPropertyAvailable / isValidPropVal"]
    end
    A1 --> B3
    A2 --> B3
    B2 --> A3

二、不可用原因信号(决定 Tab 可用性)

三个 Tab 各有独立的「不可用原因」信号,值非 INVALID 即表示有故障,Tab 禁用:

Tab信号hex故障枚举值
收起OFF_MODE_NOT_AVAILABLE_REASON0x62aProjectionCommandExecutionFailedReason
投影模式PROJECTOR_NOT_AVAILABLE_REASON0x62fProjectorNotAvailableReason
屏风模式SCREEN_NOT_AVAILABLE_REASON0x630ProjectorNotAvailableReason

2.1 可用性判定(isTabDependencyAvailable,ModeTabController.kt:139-162)

flowchart LR
    T["Tab.data"] --> Q{命令类型}
    Q -->|PROJECTION| A1["读 PROJECTOR_NOT_AVAILABLE_REASON"]
    Q -->|SCREEN| A2["读 SCREEN_NOT_AVAILABLE_REASON"]
    Q -->|CLOSE| A3["读 OFF_MODE_NOT_AVAILABLE_REASON"]
    A1 & A2 & A3 --> J{"isPropertyAvailable<br/>&amp;&amp; value == INVALID?"}
    J -->|是| OK["Tab 可用"]
    J -->|否| NO["Tab 禁用(灰)"]

INVALID 表示”无不可用原因”,即正常可用。投影与屏风共用 ProjectorNotAvailableReason 枚举,收起用 ProjectionCommandExecutionFailedReason

2.2 点击禁用 Tab → 弹原因(showDisableReason,:211-247)

因 Tab 设了 allowClickWhenDisabled=true,禁用时仍可点,点击根据原因信号值弹具体 toast:

枚举值Toast(真实文案)
PROJECTION_SYSTEM_FAILURE投影系统故障,请联系线上服务中心
BED_BOARD_FOLD二层床板已升起,请放下床板后再使用
SPACE_IS_NOT_ENOUGH空间不足,暂时无法使用

⚠️ 收起模式的 ProjectionCommandExecutionFailedReason 还有 ANTI_COLLISION_OR_ANTI_PINCHSTOP_COMMAND 两个值,但 showDisableReason 只处理了 PROJECTION_SYSTEM_FAILURE,其余落入 else 只打日志(见 03-信号体系 §七)。

三、TTS 安全提示信号

车端在幕布运动过程中遇到异常时主动上报,Settings 弹 toast:

信号hex触发值Toast
TTS_ANTI_COLLISION_OR_ANTI_PINCH0x63aCommonParams.Request.REQUEST幕布运动受阻,请检查是否有障碍物后再试
TTS_SEAT_LINKAGE_FAILED0x63bCommonParams.Request.REQUEST座椅调整失败,请稍后再试

逻辑在 onHandlePropertyChange(:193-205):收到这两个信号且值为 REQUEST 时弹 toast。这是安全相关事件的主动告知。

四、回弹忽略与 HMI 等待期 ⭐(对抗审查补充)

这是信号层一个容易被忽略但很重要的机制。

4.1 shouldIgnorePropertyFeedback

下发命令后,车端会回弹状态信号。但在某些情况下,Controller 需要忽略本次回弹不刷新 UI(等超时后再 restore):

  • CarPropertyMgrPreferenceController.isInHMITriggerWaitingDuration(:674-690):判断是否处于 HMI 触发等待期
  • shouldIgnorePropertyFeedback:在等待期内,信号上报只更新 cache、不刷新 UI,超时后走 restore 逻辑。

场景:用户快速连续点击,或下发后短时间内收到中间态回弹,忽略可避免 UI 抖动。注释在 :679-687

4.2 与超时回退配合

flowchart LR
    S[下发命令] --> W[启动超时监控]
    R[收到回弹] --> G{getTimeoutFlag}
    G -->|CONTINUE| C[继续监控, 不刷新]
    G -->|REMOVE| RM[取消监控, 刷新UI]
    G -->|RESTART| RS[重置监控, 刷新UI]
    W -->|超时未回弹| T[restorePropertyToPrevious<br/>UI 回退]

五、超时监控与回退

5.1 超时时长(VehiclePropertyConfig.java)

  • NORMAL_TIME_TO_WAIT = 500ms(默认)
  • MIDDLE_TIME_TO_WAIT = 750ms
  • PROJECTION_SCREEN_STATUS 绑定 750ms(:123);下行 SCREEN_CONTROL_COMMAND 通过 mapping(:192-193)复用这个时长。

5.2 getTimeoutFlag 四分支(对抗审查补充)

收到信号后,CarPropertyMgrPreferenceController.getTimeoutFlag()(:315-343)决定超时计时器的命运,涉及四个判断:

分支条件行为
CONTINUEshouldIgnorePropertyFeedback 为真继续监控,不刷新 UI(HMI 等待期)
REMOVEshouldRemoveFromWatchList 为真取消监控,正常刷新
RESTART中间态/需重新计时重置监控,刷新
(默认)shouldVerifyReturnedData按默认处理

SettingsCarPropertyManager.processTimeout()(:155-189)消费这个 flag。

5.3 回退链路

超时 → mTimeoutHandler.handleMessage(:149-201)
     → handlePropertyTimeout → onSetPropertyTimeout(:194)
     → CarPropertyMgrPreferenceController.onSetPropertyTimeout(:393-396)
     → restorePropertyToPrevious(propId, areaId)(:125-133)
     → 读缓存调 onHandlePropertyChange(propertyValue, true) → UI 回退到下发前

保证”点了但车没响应”时 UI 不会一直停在 loading,而是自动回退。

六、信号不可用 → 置灰

最底层的可用性闸:VehiclePropertyUtil.isPropertyAvailable(:173-182):

public static boolean isPropertyAvailable(CarPropertyValue vehicleProperty) {
    if (vehicleProperty == null) return false;
    if (isPropertyMockUnavailable(vehicleProperty.getPropertyId())) return false;  // 调试 mock
    return vehicleProperty.getStatus() == CarPropertyValue.STATUS_AVAILABLE;       // 核心
}
  • STATUS_AVAILABLE(0) → 可用
  • STATUS_UNAVAILABLE(2) / null → 不可用 → Tab 置灰

童锁另有 isValidPropVal 闸:INVALID(0x0) 时置灰且不刷新勾选(见 05-核心类拆解/02)。

七、异常态总结表

异常触发表现处理层
投影系统故障*_NOT_AVAILABLE_REASON = PROJECTION_SYSTEM_FAILURETab 禁用,点击弹 toast业务
床板已升起BED_BOARD_FOLD同上业务
空间不足SPACE_IS_NOT_ENOUGH同上业务
幕布受阻TTS_ANTI_COLLISION_OR_ANTI_PINCH = REQUESTtoast业务
座椅联动失败TTS_SEAT_LINKAGE_FAILED = REQUESTtoast业务
调节中状态信号过渡态loading + “调整中” toast业务
信号不可用status != AVAILABLETab 置灰信号工具
下发无响应超时(750ms)UI 回退信号
回弹抖动HMI 等待期忽略刷新信号