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 最高优先级(立即返回)
- 气泡栏检查 - 事件在气泡上 → BubbleBarInputConsumer
- 进度委托检查 - 存在进度代理 → ProgressDelegateInputConsumer
- 设备锁定检查 - 设备未解锁 → DeviceLockedInputConsumer
5.2 基础消费者选择
- 系统手势能力检查 - 决定使用基础消费者还是默认消费者
- 可启动系统手势或最近动画运行 → newBaseConsumer() (LauncherInputConsumer/OtherActivityInputConsumer)
- 不可启动系统手势 → getDefaultInputConsumer() (默认无操作消费者)
5.3 分层包装(按顺序)
- 助手手势检查 - 可触发助手动作 → AssistantInputConsumer
- 任务栏检查 - 任务栏相关操作 → TaskbarUnstashInputConsumer
- 导航手柄检查 - 长按操作 → NavHandleLongPressInputConsumer
- 系统UI检查 - 对话框显示 → SysUiOverlayInputConsumer
- 触控板检查 - 状态栏操作 → TrackpadStatusBarInputConsumer
- 屏幕固定检查 - 固定模式 → ScreenPinnedInputConsumer
- 单手模式检查 - 单手操作 → OneHandedModeInputConsumer
- 无障碍检查 - 无障碍功能 → AccessibilityInputConsumer
5.4 默认情况
- OtherActivityInputConsumer - 其他应用手势处理 → OtherActivityInputConsumer
6. 条件检查的性能优化
6.1 短路评估
条件检查采用短路评估,一旦满足条件立即返回,避免不必要的检查。
6.2 成本排序
条件按照检查成本从低到高排序,低成本检查在前。
6.3 缓存利用
充分利用设备状态缓存,避免重复的状态检查。
7. 扩展性考虑
7.1 新InputConsumer添加
添加新的InputConsumer时,需要:
- 在合适的位置插入条件检查
- 确定正确的优先级
- 提供清晰的触发条件文档
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 区域重叠优先级
- 气泡栏区域 (最高优先级) - 立即返回
- 助手区域 - 高优先级处理
- 单手模式区域 - 中等优先级
- 基础手势区域 - 默认优先级
- 全局区域 - 最低优先级
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系统对复杂输入场景的精细处理能力。通过分层条件检查和优先级排序,系统能够高效地选择最适合的输入处理策略,为用户提供流畅的交互体验。
区域设计的关键优势:
- 精确的区域划分: 不同功能对应不同的触发区域
- 动态的区域适配: 根据设备状态和屏幕方向动态调整
- 高效的冲突解决: 明确的优先级规则处理区域重叠
- 良好的扩展性: 支持新增区域类型和功能
这种区域化的输入处理机制确保了Android系统在各种复杂场景下都能提供一致且流畅的用户体验。