后屏投影:异常态与依赖信号
本篇梳理后排投影的异常处理机制——这是该模块最复杂的部分,约占 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_REASON | 0x62a | ProjectionCommandExecutionFailedReason |
| 投影模式 | PROJECTOR_NOT_AVAILABLE_REASON | 0x62f | ProjectorNotAvailableReason |
| 屏风模式 | SCREEN_NOT_AVAILABLE_REASON | 0x630 | ProjectorNotAvailableReason |
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/>&& 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_PINCH、STOP_COMMAND两个值,但showDisableReason只处理了PROJECTION_SYSTEM_FAILURE,其余落入 else 只打日志(见03-信号体系§七)。
三、TTS 安全提示信号
车端在幕布运动过程中遇到异常时主动上报,Settings 弹 toast:
| 信号 | hex | 触发值 | Toast |
|---|---|---|---|
TTS_ANTI_COLLISION_OR_ANTI_PINCH | 0x63a | CommonParams.Request.REQUEST | 幕布运动受阻,请检查是否有障碍物后再试 |
TTS_SEAT_LINKAGE_FAILED | 0x63b | CommonParams.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 = 750msPROJECTION_SCREEN_STATUS绑定 750ms(:123);下行SCREEN_CONTROL_COMMAND通过 mapping(:192-193)复用这个时长。
5.2 getTimeoutFlag 四分支(对抗审查补充)
收到信号后,CarPropertyMgrPreferenceController.getTimeoutFlag()(:315-343)决定超时计时器的命运,涉及四个判断:
| 分支 | 条件 | 行为 |
|---|---|---|
| CONTINUE | shouldIgnorePropertyFeedback 为真 | 继续监控,不刷新 UI(HMI 等待期) |
| REMOVE | shouldRemoveFromWatchList 为真 | 取消监控,正常刷新 |
| 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_FAILURE | Tab 禁用,点击弹 toast | 业务 |
| 床板已升起 | BED_BOARD_FOLD | 同上 | 业务 |
| 空间不足 | SPACE_IS_NOT_ENOUGH | 同上 | 业务 |
| 幕布受阻 | TTS_ANTI_COLLISION_OR_ANTI_PINCH = REQUEST | toast | 业务 |
| 座椅联动失败 | TTS_SEAT_LINKAGE_FAILED = REQUEST | toast | 业务 |
| 调节中 | 状态信号过渡态 | loading + “调整中” toast | 业务 |
| 信号不可用 | status != AVAILABLE | Tab 置灰 | 信号工具 |
| 下发无响应 | 超时(750ms) | UI 回退 | 信号 |
| 回弹抖动 | HMI 等待期 | 忽略刷新 | 信号 |