Files
Murine-launcher/docs/InputConsumer触发条件详细分析.md
T
2026-01-31 23:33:54 +08:00

21 KiB

InputConsumer触发条件详细分析

概述

本文档详细分析了AOSP Launcher3中所有InputConsumer实现类的具体触发条件,基于对TouchInteractionService.java和InputConsumerUtils.kt源码的深入分析。

1. TouchInteractionService中的触发条件分析

1.1 核心触发逻辑位置

  • 文件: quickstep/src/com/android/quickstep/TouchInteractionService.java
  • 方法: onInputEvent(InputEvent ev) (第1057-1285行)

1.2 主要触发条件分支

分支1: 三键导航模式 + 助手手势支持

if (deviceState.isButtonNavMode() 
    && deviceState.supportsAssistantGestureInButtonNav()) {
    // 条件: 三键导航模式且支持助手手势
    if (deviceState.canTriggerAssistantAction(event)) {
        // 子条件: 事件可触发助手动作
        mUncheckedConsumer = tryCreateAssistantInputConsumer(...);
    } else {
        // 子条件: 事件不可触发助手动作
        mUncheckedConsumer = createNoOpInputConsumer(displayId);
    }
}

分支2: 常规手势区域或悬停操作

else if ((!isOneHandedModeActive && isInSwipeUpTouchRegion) 
         || isHoverActionWithoutConsumer || isOnBubbles) {
    // 条件1: 单手模式未激活且在滑动手势区域
    // 条件2: 悬停操作且无当前消费者
    // 条件3: 事件在气泡上
    mConsumer = newConsumer(...); // 调用InputConsumerUtils.newConsumer
}

分支3: 全手势导航或触控板多指滑动 + 助手触发

else if ((deviceState.isFullyGesturalNavMode() || isTrackpadMultiFingerSwipe(event))
         && deviceState.canTriggerAssistantAction(event)) {
    // 条件1: 全手势导航模式
    // 条件2: 触控板多指滑动
    // 条件3: 事件可触发助手动作
    mUncheckedConsumer = tryCreateAssistantInputConsumer(...);
}

分支4: 单手模式触发

else if (deviceState.canTriggerOneHandedAction(event)) {
    // 条件: 事件可触发单手模式
    mUncheckedConsumer = new OneHandedModeInputConsumer(...);
}

分支5: 默认情况

else {
    // 默认: 无操作消费者
    mUncheckedConsumer = InputConsumer.createNoOpInputConsumer(displayId);
}

2. InputConsumerUtils.kt中的详细触发条件

2.1 优先级1: 气泡栏检查

if (bubbleControllers != null && BubbleBarInputConsumer.isEventOnBubbles(tac, event)) {
    // 触发条件: 存在气泡控制器且事件在气泡上
    return BubbleBarInputConsumer(...)
}

2.2 优先级2: 进度委托检查

if (progressProxy != null) {
    // 触发条件: 存在进度代理
    return ProgressDelegateInputConsumer(...)
}

2.3 优先级3: 设备锁定状态检查

if (!get(context).isUserUnlocked) {
    // 触发条件: 设备未解锁
    return if (canStartSystemGesture) {
        createDeviceLockedInputConsumer(...) // 可启动系统手势
    } else {
        getDefaultInputConsumer(...) // 不可启动系统手势
    }
}

2.4 优先级4: 基础消费者选择

val base = if (canStartSystemGesture || previousGestureState.isRecentsAnimationRunning) {
    // 条件1: 可启动系统手势
    // 条件2: 最近动画正在运行
    newBaseConsumer(...)
} else {
    // 默认情况
    getDefaultInputConsumer(...)
}

2.5 优先级5: 分层包装逻辑

2.5.1 助手手势检查

if (deviceState.canTriggerAssistantAction(event)) {
    // 触发条件: 事件可触发助手动作
    base = tryCreateAssistantInputConsumer(...)
}

2.5.2 任务栏检查

if (tac != null && base !is AssistantInputConsumer) {
    val useTaskbarConsumer = (tac.deviceProfile.isTaskbarPresent 
                            && !tac.isPhoneMode 
                            && !tac.isInStashedLauncherState)
    if (canStartSystemGesture && useTaskbarConsumer) {
        // 触发条件: 
        // 1. 存在TaskbarActivityContext
        // 2. 不是AssistantInputConsumer
        // 3. 可启动系统手势
        // 4. 任务栏存在且不在手机模式且不在隐藏状态
        base = TaskbarUnstashInputConsumer(...)
    }
}

2.5.3 导航手柄长按检查

if (canStartSystemGesture 
    && !previousGestureState.isRecentsAnimationRunning 
    && navHandle.canNavHandleBeLongPressed() 
    && !ignoreThreeFingerTrackpadForNavHandleLongPress(gestureState)) {
    // 触发条件:
    // 1. 可启动系统手势
    // 2. 最近动画未运行
    // 3. 导航手柄可长按
    // 4. 不是触控板三指手势
    base = NavHandleLongPressInputConsumer(...)
}

2.5.4 系统UI对话框检查

if (deviceState.isSystemUiDialogShowing) {
    // 触发条件: 系统UI对话框正在显示
    base = SysUiOverlayInputConsumer(...)
}

2.5.5 触控板状态栏检查

if (gestureState.isTrackpadGesture 
    && canStartSystemGesture 
    && !previousGestureState.isRecentsAnimationRunning) {
    // 触发条件:
    // 1. 触控板手势
    // 2. 可启动系统手势
    // 3. 最近动画未运行
    base = TrackpadStatusBarInputConsumer(...)
}

2.5.6 屏幕固定检查

if (deviceState.isScreenPinningActive) {
    // 触发条件: 屏幕固定模式激活
    base = ScreenPinnedInputConsumer(...)
}

2.5.7 单手模式检查

if (deviceState.canTriggerOneHandedAction(event)) {
    // 触发条件: 事件可触发单手模式
    base = OneHandedModeInputConsumer(...)
}

2.5.8 无障碍功能检查

if (deviceState.isAccessibilityMenuAvailable) {
    // 触发条件: 无障碍菜单可用
    base = AccessibilityInputConsumer(...)
}

3. newBaseConsumer方法中的基础消费者选择

3.1 设备锁定状态检查

if (deviceState.isKeyguardShowingOccluded) {
    // 触发条件: 锁屏被遮挡显示
    return createDeviceLockedInputConsumer(...)
}

3.2 Launcher输入消费者选择条件

条件1: Live Tile模式

if (containerInterface.isInLiveTileMode()) {
    // 触发条件: 处于Live Tile模式
    return createLauncherInputConsumer(...)
}

条件2: 运行任务为空

if (runningTask == null) {
    // 触发条件: 运行任务为空
    return getDefaultInputConsumer(...)
}

条件3: Launcher相关状态

if (previousGestureAnimatedToLauncher 
    || launcherResumedThroughShellTransition 
    || forceLauncherInputConsumer) {
    // 触发条件1: 前一个手势动画到Launcher
    // 触发条件2: Launcher通过Shell Transition恢复
    // 触发条件3: 强制使用Launcher输入消费者
    return createLauncherInputConsumer(...)
}

条件4: 手势阻塞任务或Launcher子活动

if (deviceState.isGestureBlockedTask(runningTask) 
    || launcherChildActivityResumed 
    || ignoreNonTrackpadMouseEvent(context, gestureState, event)) {
    // 触发条件1: 手势被任务阻塞
    // 触发条件2: Launcher子活动恢复
    // 触发条件3: 忽略非触控板鼠标事件
    return getDefaultInputConsumer(...)
}

条件5: 默认情况

else {
    // 默认: 使用OtherActivityInputConsumer
    return createOtherActivityInputConsumer(...)
}

4. 各InputConsumer实现类的具体触发条件

4.1 LauncherInputConsumer

  • 触发条件:
    • 处于Live Tile模式
    • 前一个手势动画到Launcher
    • Launcher通过Shell Transition恢复
    • 强制使用Launcher输入消费者
  • 适用场景: Launcher界面活动时的输入处理

4.2 OtherActivityInputConsumer

  • 触发条件: 默认情况,当不满足其他特定条件时
  • 适用场景: 其他应用界面手势操作

4.3 AssistantInputConsumer

  • 触发条件:
    • 设备支持助手手势
    • 事件满足助手触发条件(距离、角度等)
    • 不是手势阻塞任务
  • 适用场景: 助手手势识别和处理

4.4 DeviceLockedInputConsumer

  • 触发条件:
    • 设备未解锁
    • 锁屏被遮挡显示
  • 适用场景: 设备锁定状态下的输入处理

4.5 AccessibilityInputConsumer

  • 触发条件: 无障碍菜单可用
  • 适用场景: 无障碍功能输入处理

4.6 ScreenPinnedInputConsumer

  • 触发条件: 屏幕固定模式激活
  • 适用场景: 屏幕固定状态下的输入限制

4.7 ProgressDelegateInputConsumer

  • 触发条件: 存在进度代理
  • 适用场景: 进度委托输入处理

4.8 SysUiOverlayInputConsumer

  • 触发条件: 系统UI对话框正在显示
  • 适用场景: 系统UI覆盖层输入处理

4.9 OneHandedModeInputConsumer

  • 触发条件: 事件可触发单手模式
  • 适用场景: 单手模式手势处理

4.10 TaskbarUnstashInputConsumer

  • 触发条件:
    • 任务栏存在
    • 不在手机模式
    • 任务栏不在隐藏状态
    • 可启动系统手势
  • 适用场景: 任务栏显示/隐藏操作

4.11 TrackpadStatusBarInputConsumer

  • 触发条件:
    • 触控板手势
    • 可启动系统手势
    • 最近动画未运行
  • 适用场景: 触控板状态栏操作

4.12 BubbleBarInputConsumer

  • 触发条件: 事件在气泡上
  • 适用场景: 气泡栏输入处理

4.13 NavHandleLongPressInputConsumer

  • 触发条件:
    • 可启动系统手势
    • 最近动画未运行
    • 导航手柄可长按
    • 不是触控板三指手势
  • 适用场景: 导航手柄长按操作

5. 触发条件优先级总结

5.1 最高优先级(立即返回)

  1. 气泡栏检查 - 事件在气泡上 → BubbleBarInputConsumer
  2. 进度委托检查 - 存在进度代理 → ProgressDelegateInputConsumer
  3. 设备锁定检查 - 设备未解锁 → DeviceLockedInputConsumer

5.2 基础消费者选择

  1. 系统手势能力检查 - 决定使用基础消费者还是默认消费者
    • 可启动系统手势或最近动画运行 → newBaseConsumer() (LauncherInputConsumer/OtherActivityInputConsumer)
    • 不可启动系统手势 → getDefaultInputConsumer() (默认无操作消费者)

5.3 分层包装(按顺序)

  1. 助手手势检查 - 可触发助手动作 → AssistantInputConsumer
  2. 任务栏检查 - 任务栏相关操作 → TaskbarUnstashInputConsumer
  3. 导航手柄检查 - 长按操作 → NavHandleLongPressInputConsumer
  4. 系统UI检查 - 对话框显示 → SysUiOverlayInputConsumer
  5. 触控板检查 - 状态栏操作 → TrackpadStatusBarInputConsumer
  6. 屏幕固定检查 - 固定模式 → ScreenPinnedInputConsumer
  7. 单手模式检查 - 单手操作 → OneHandedModeInputConsumer
  8. 无障碍检查 - 无障碍功能 → AccessibilityInputConsumer

5.4 默认情况

  1. OtherActivityInputConsumer - 其他应用手势处理 → OtherActivityInputConsumer

6. 条件检查的性能优化

6.1 短路评估

条件检查采用短路评估,一旦满足条件立即返回,避免不必要的检查。

6.2 成本排序

条件按照检查成本从低到高排序,低成本检查在前。

6.3 缓存利用

充分利用设备状态缓存,避免重复的状态检查。

7. 扩展性考虑

7.1 新InputConsumer添加

添加新的InputConsumer时,需要:

  1. 在合适的位置插入条件检查
  2. 确定正确的优先级
  3. 提供清晰的触发条件文档

7.2 条件配置化

支持通过配置动态调整条件检查顺序和阈值。

8. InputConsumer在手机上的触发区域分析

8.1 基础手势区域定义

8.1.1 滑动手势区域 (Swipe Up Touch Region)

区域定义: 屏幕底部特定高度的区域,用于检测上滑手势

实现代码:

// RotationTouchHelper.java - 滑动手势区域检查
public boolean isInSwipeUpTouchRegion(MotionEvent event, int pointerIndex) {
    if (isTrackpadScroll(event)) {
        return false; // 触控板滚动不处理
    }
    if (isTrackpadMultiFingerSwipe(event)) {
        return true; // 触控板多指滑动
    }
    // 检查触摸点是否在有效滑动区域内
    return mOrientationTouchTransformer.touchInValidSwipeRegions(event.getX(pointerIndex),
            event.getY(pointerIndex));
}

// OrientationTouchTransformer.java - 滑动区域定义
boolean touchInValidSwipeRegions(float x, float y) {
    if (mLastRectTouched != null) {
        return mLastRectTouched.contains(x, y);
    }
    return false;
}

区域位置:

  • 竖屏模式 (ROTATION_0): 屏幕底部 mNavBarGesturalHeight 像素高度区域
  • 横屏模式 (ROTATION_90/270): 屏幕底部 mNavBarGesturalHeight 像素高度区域
  • 倒屏模式 (ROTATION_180): 屏幕顶部 mNavBarGesturalHeight 像素高度区域

默认高度: 约 100-150dp(根据设备密度调整)

8.1.2 助手手势区域 (Assistant Gesture Region)

区域定义: 屏幕左右两侧特定宽度的区域,用于检测助手手势

实现代码:

// OrientationTouchTransformer.java - 助手区域定义
boolean touchInAssistantRegion(MotionEvent ev) {
    return mAssistantLeftRegion.contains(ev.getX(), ev.getY())
            || mAssistantRightRegion.contains(ev.getX(), ev.getY());
}

private void updateAssistantRegions(OrientationRectF orientationRectF) {
    // 助手区域宽度约为屏幕宽度的1/6
    float assistantWidth = orientationRectF.width() / 6f;
    
    // 左侧助手区域
    mAssistantLeftRegion.left = 0;
    mAssistantLeftRegion.right = assistantWidth;
    
    // 右侧助手区域
    mAssistantRightRegion.right = orientationRectF.right;
    mAssistantRightRegion.left = orientationRectF.right - assistantWidth;
}

区域位置:

  • 左侧助手区域: 屏幕左侧 1/6 宽度区域
  • 右侧助手区域: 屏幕右侧 1/6 宽度区域
  • 高度: 与滑动手势区域相同

8.1.3 单手模式区域 (One-Handed Mode Region)

区域定义: 仅在竖屏模式下有效的底部扩展区域

实现代码:

// OrientationTouchTransformer.java - 单手模式区域定义
boolean touchInOneHandedModeRegion(MotionEvent ev) {
    return mOneHandedModeRegion.contains(ev.getX(), ev.getY());
}

private void updateOneHandedRegions(OrientationRectF orientationRectF) {
    // 单手模式仅在竖屏模式下有效
    mOneHandedModeRegion.set(0, orientationRectF.bottom - mNavBarLargerGesturalHeight,
            orientationRectF.right, orientationRectF.bottom);
}

区域位置:

  • 仅竖屏模式有效
  • 高度: mNavBarLargerGesturalHeight(比普通手势区域更高)
  • 默认高度: 约 200-250dp

8.2 各InputConsumer的具体触发区域

8.2.1 LauncherInputConsumer

触发区域: 整个屏幕区域(当应用在Launcher界面时) 区域类型: 全局区域 特殊条件: 仅在Launcher活动时有效

8.2.2 OtherActivityInputConsumer

触发区域: 基础滑动手势区域 区域位置: 屏幕底部 mNavBarGesturalHeight 高度区域 触发条件: 在基础手势区域内的滑动操作

8.2.3 AssistantInputConsumer

触发区域: 助手手势区域 + 全屏助手手势 区域位置:

  • 三键导航模式: 屏幕左右两侧助手区域
  • 全手势导航模式: 全屏区域(支持从任意位置触发)
  • 触控板多指滑动: 全屏区域

触发条件:

  • 在助手区域内的滑动
  • 全手势导航模式下的对角线滑动
  • 触控板三指滑动

8.2.4 DeviceLockedInputConsumer

触发区域: 基础滑动手势区域 区域位置: 屏幕底部 mNavBarGesturalHeight 高度区域 特殊条件: 设备锁定状态下,仅在基础手势区域有效

8.2.5 AccessibilityInputConsumer

触发区域: 基础滑动手势区域 区域位置: 屏幕底部 mNavBarGesturalHeight 高度区域 特殊条件: 无障碍功能启用时,支持在基础区域内的特殊手势

8.2.6 ScreenPinnedInputConsumer

触发区域: 无特定区域限制 区域类型: 全局区域 特殊条件: 屏幕固定模式下,限制所有输入操作

8.2.7 ProgressDelegateInputConsumer

触发区域: 基础滑动手势区域 区域位置: 屏幕底部 mNavBarGesturalHeight 高度区域 特殊条件: 存在进度代理时,优先处理进度相关手势

8.2.8 SysUiOverlayInputConsumer

触发区域: 系统UI覆盖层区域 区域位置: 系统对话框和覆盖层显示区域 特殊条件: 系统UI显示时,覆盖应用界面输入

8.2.9 OneHandedModeInputConsumer

触发区域: 单手模式区域 区域位置: 屏幕底部 mNavBarLargerGesturalHeight 高度区域 特殊条件: 仅在竖屏模式下有效,支持向下滑动触发

8.2.10 TaskbarUnstashInputConsumer

触发区域: 任务栏显示区域 区域位置: 屏幕底部任务栏位置 特殊条件: 任务栏存在且不在隐藏状态时有效

8.2.11 TrackpadStatusBarInputConsumer

触发区域: 状态栏区域 + 触控板手势 区域位置: 屏幕顶部状态栏区域 特殊条件: 触控板手势操作状态栏

8.2.12 BubbleBarInputConsumer

触发区域: 气泡栏显示区域 区域位置: 气泡栏具体位置(浮动窗口) 特殊条件: 事件在气泡上时立即触发

8.2.13 NavHandleLongPressInputConsumer

触发区域: 导航手柄区域 区域位置: 屏幕底部导航条中心区域 特殊条件: 长按导航手柄触发

8.3 区域优先级和重叠处理

8.3.1 区域重叠优先级

  1. 气泡栏区域 (最高优先级) - 立即返回
  2. 助手区域 - 高优先级处理
  3. 单手模式区域 - 中等优先级
  4. 基础手势区域 - 默认优先级
  5. 全局区域 - 最低优先级

8.3.2 区域冲突解决

当多个区域重叠时,系统按照以下规则处理:

  • 气泡栏优先: 事件在气泡上时,忽略其他区域
  • 助手区域优先: 在助手区域内的事件优先于基础手势区域
  • 单手模式限制: 仅在竖屏模式下有效
  • 导航模式影响: 不同导航模式下的区域定义不同

8.4 设备适配和动态调整

8.4.1 设备尺寸适配

// 根据设备尺寸动态调整区域大小
private int calculateDefaultGesturalHeight() {
    return getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
}

private int getNavbarSize(String resName) {
    return ResourceUtils.getNavbarSize(resName, mResources);
}

8.4.2 屏幕方向适配

系统根据当前屏幕方向动态调整触摸区域:

  • 竖屏: 底部手势区域 + 左右助手区域
  • 横屏: 底部手势区域(适配横屏布局)
  • 倒屏: 顶部手势区域

8.4.3 导航模式适配

  • 三键导航: 仅支持助手区域手势
  • 手势导航: 支持全屏手势区域
  • 二键导航: 特定区域限制

8.5 可视化区域分布

┌─────────────────────────────────────────┐
│               状态栏区域                 │ ← TrackpadStatusBarInputConsumer
├─────────────────────────────────────────┤
│                                          │
│            应用内容区域                  │
│                                          │
│                                          │
├───助手区域───┼─────────────┼───助手区域───┤
│              │  基础手势   │              │ ← AssistantInputConsumer
│  左侧助手    │    区域     │  右侧助手    │
│              │             │              │
├──────────────┴─────────────┴──────────────┤
│          单手模式扩展区域                │ ← OneHandedModeInputConsumer (竖屏)
│                                          │
│          基础手势区域                    │ ← OtherActivityInputConsumer等
│         (mNavBarGesturalHeight)          │
└─────────────────────────────────────────┘

浮动区域:
┌───┐
│ ● │ ← BubbleBarInputConsumer (气泡栏)
└───┘

8.6 性能优化考虑

8.6.1 区域检查优化

  • 短路评估: 高概率区域在前检查
  • 区域缓存: 避免重复计算区域边界
  • 分层检查: 按优先级顺序检查区域

8.6.2 内存优化

  • 区域复用: 相同方向的区域复用
  • 动态创建: 只在需要时创建区域对象
  • 及时清理: 手势结束后清理区域缓存

9. 总结

InputConsumer的触发条件设计体现了Android系统对复杂输入场景的精细处理能力。通过分层条件检查和优先级排序,系统能够高效地选择最适合的输入处理策略,为用户提供流畅的交互体验。

区域设计的关键优势:

  1. 精确的区域划分: 不同功能对应不同的触发区域
  2. 动态的区域适配: 根据设备状态和屏幕方向动态调整
  3. 高效的冲突解决: 明确的优先级规则处理区域重叠
  4. 良好的扩展性: 支持新增区域类型和功能

这种区域化的输入处理机制确保了Android系统在各种复杂场景下都能提供一致且流畅的用户体验。