From 7dc9e3a08899cb23389a0dacfe7de88feef47bf5 Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Wed, 15 Mar 2023 01:12:30 -0700 Subject: [PATCH] Support 4-finger gesture to quick switch Fixes: 270610237 Test: 4-finger horizontal swipe -> quick switch Change-Id: I981a7e93ecd07357c7a9cda075f476b397c448cc --- .../NoButtonQuickSwitchTouchController.java | 19 ++++++-- .../android/quickstep/AbsSwipeUpHandler.java | 4 +- .../com/android/quickstep/GestureState.java | 47 +++++++++++++++---- .../quickstep/OverviewCommandHelper.java | 2 +- .../quickstep/TouchInteractionService.java | 20 ++++---- .../OtherActivityInputConsumer.java | 29 ++++++++++-- .../android/launcher3/MotionEventsUtils.java | 8 ++++ 7 files changed, 101 insertions(+), 28 deletions(-) diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java index f1c4f68e28..80f5558315 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java @@ -15,11 +15,15 @@ */ package com.android.launcher3.uioverrides.touchcontrollers; +import static android.view.MotionEvent.ACTION_DOWN; +import static android.view.MotionEvent.ACTION_MOVE; + import static com.android.launcher3.LauncherAnimUtils.newCancelListener; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS; import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME; +import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe; import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe; import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD; import static com.android.launcher3.anim.AnimatorListeners.forEndCallback; @@ -106,6 +110,7 @@ public class NoButtonQuickSwitchTouchController implements TouchController, newCancelListener(this::clearState); private boolean mNoIntercept; + private Boolean mIsTrackpadFourFingerSwipe; private LauncherState mStartState; private boolean mIsHomeScreenVisible = true; @@ -131,7 +136,9 @@ public class NoButtonQuickSwitchTouchController implements TouchController, @Override public boolean onControllerInterceptTouchEvent(MotionEvent ev) { - if (ev.getAction() == MotionEvent.ACTION_DOWN) { + int action = ev.getActionMasked(); + if (action == ACTION_DOWN) { + mIsTrackpadFourFingerSwipe = null; mNoIntercept = !canInterceptTouch(ev); if (mNoIntercept) { return false; @@ -140,6 +147,13 @@ public class NoButtonQuickSwitchTouchController implements TouchController, // Only detect horizontal swipe for intercept, then we will allow swipe up as well. mSwipeDetector.setDetectableScrollConditions(DIRECTION_RIGHT, false /* ignoreSlopWhenSettling */); + } else if (isTrackpadMultiFingerSwipe(ev) && mIsTrackpadFourFingerSwipe == null + && action == ACTION_MOVE) { + mIsTrackpadFourFingerSwipe = isTrackpadFourFingerSwipe(ev); + mNoIntercept = !mIsTrackpadFourFingerSwipe; + if (mNoIntercept) { + return false; + } } if (mNoIntercept) { @@ -162,9 +176,6 @@ public class NoButtonQuickSwitchTouchController implements TouchController, if ((ev.getEdgeFlags() & Utilities.EDGE_NAV_BAR) == 0) { return false; } - if (isTrackpadMultiFingerSwipe(ev)) { - return false; - } int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags(); if ((stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) { return false; diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 97956701ab..534eec1367 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -2063,8 +2063,8 @@ public abstract class AbsSwipeUpHandler, mRecentsView.setRecentsAnimationTargets(mRecentsAnimationController, mRecentsAnimationTargets)); - // Disable scrolling in RecentsView for trackpad gestures. - if (!mGestureState.isTrackpadGesture()) { + // Disable scrolling in RecentsView for trackpad 3-finger swipe up gesture. + if (!mGestureState.isThreeFingerTrackpadGesture()) { mRecentsViewScrollLinked = true; } } diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index 2b0623a67d..02f9f57ec2 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -15,6 +15,9 @@ */ package com.android.quickstep; +import static com.android.launcher3.MotionEventsUtils.isTrackpadFourFingerSwipe; +import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe; +import static com.android.launcher3.MotionEventsUtils.isTrackpadThreeFingerSwipe; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME; import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_OVERVIEW; @@ -27,6 +30,7 @@ import android.annotation.Nullable; import android.annotation.TargetApi; import android.content.Intent; import android.os.Build; +import android.view.MotionEvent; import android.view.RemoteAnimationTarget; import com.android.launcher3.statemanager.BaseState; @@ -139,8 +143,30 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL private final BaseActivityInterface mActivityInterface; private final MultiStateCallback mStateCallback; private final int mGestureId; - private boolean mIsTrackpadGesture; + public enum TrackpadGestureType { + NONE, + // Assigned before we know whether it's a 3-finger or 4-finger gesture. + MULTI_FINGER, + THREE_FINGER, + FOUR_FINGER; + + public static TrackpadGestureType getTrackpadGestureType(MotionEvent event) { + if (!isTrackpadMultiFingerSwipe(event)) { + return TrackpadGestureType.NONE; + } + if (isTrackpadThreeFingerSwipe(event)) { + return TrackpadGestureType.THREE_FINGER; + } + if (isTrackpadFourFingerSwipe(event)) { + return TrackpadGestureType.FOUR_FINGER; + } + + return TrackpadGestureType.MULTI_FINGER; + } + } + + private TrackpadGestureType mTrackpadGestureType = TrackpadGestureType.NONE; private CachedTaskInfo mRunningTask; private GestureEndTarget mEndTarget; private RemoteAnimationTarget mLastAppearedTaskTarget; @@ -249,17 +275,22 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL } /** - * Sets if the gesture is is from the trackpad. + * Sets if the gesture is is from the trackpad, if so, whether 3-finger, or 4-finger */ - public void setIsTrackpadGesture(boolean isTrackpadGesture) { - mIsTrackpadGesture = isTrackpadGesture; + public void setTrackpadGestureType(TrackpadGestureType trackpadGestureType) { + mTrackpadGestureType = trackpadGestureType; } - /** - * @return if the gesture is from the trackpad. - */ public boolean isTrackpadGesture() { - return mIsTrackpadGesture; + return mTrackpadGestureType != TrackpadGestureType.NONE; + } + + public boolean isThreeFingerTrackpadGesture() { + return mTrackpadGestureType == TrackpadGestureType.THREE_FINGER; + } + + public boolean isFourFingerTrackpadGesture() { + return mTrackpadGestureType == TrackpadGestureType.FOUR_FINGER; } /** diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index fbe2778fdb..07db194ff4 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -249,7 +249,7 @@ public class OverviewCommandHelper { } GestureState gestureState = mService.createGestureState(GestureState.DEFAULT_STATE, - false /* isTrackpadGesture */); + GestureState.TrackpadGestureType.NONE); gestureState.setHandlingAtomicEvent(true); AbsSwipeUpHandler interactionHandler = mService.getSwipeUpHandlerFactory() .newHandler(gestureState, cmd.createTime); diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 0531b47941..038c6743dd 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -24,11 +24,11 @@ import static android.view.MotionEvent.ACTION_POINTER_UP; import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Launcher.INTENT_ACTION_ALL_APPS_TOGGLE; -import static com.android.launcher3.MotionEventsUtils.isTrackpadMultiFingerSwipe; import static com.android.launcher3.config.FeatureFlags.ASSISTANT_GIVES_LAUNCHER_FOCUS; import static com.android.launcher3.config.FeatureFlags.ENABLE_TRACKPAD_GESTURE; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.quickstep.GestureState.DEFAULT_STATE; +import static com.android.quickstep.GestureState.TrackpadGestureType.getTrackpadGestureType; import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.FLAG_USING_OTHER_ACTIVITY_INPUT_CONSUMER; import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_DOWN; import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.MOTION_MOVE; @@ -647,7 +647,7 @@ public class TouchInteractionService extends Service // onConsumerInactive and wipe the previous gesture state GestureState prevGestureState = new GestureState(mGestureState); GestureState newGestureState = createGestureState(mGestureState, - isTrackpadMultiFingerSwipe(event)); + getTrackpadGestureType(event)); newGestureState.setSwipeUpStartTimeMs(SystemClock.uptimeMillis()); mConsumer.onConsumerAboutToBeSwitched(); mGestureState = newGestureState; @@ -656,7 +656,7 @@ public class TouchInteractionService extends Service } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode() && mDeviceState.canTriggerAssistantAction(event)) { mGestureState = createGestureState(mGestureState, - isTrackpadMultiFingerSwipe(event)); + getTrackpadGestureType(event)); // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we // should not interrupt it. QuickSwitch assumes that interruption can only // happen if the next gesture is also quick switch. @@ -713,9 +713,13 @@ public class TouchInteractionService extends Service event.setAction(ACTION_CANCEL); } - // Skip ACTION_POINTER_DOWN and ACTION_POINTER_UP events from trackpad. - if (!mGestureState.isTrackpadGesture() || (action != ACTION_POINTER_DOWN - && action != ACTION_POINTER_UP)) { + if (mGestureState.isTrackpadGesture() && (action == ACTION_POINTER_DOWN + || action == ACTION_POINTER_UP)) { + // Skip ACTION_POINTER_DOWN and ACTION_POINTER_UP events from trackpad. + if (action == ACTION_POINTER_DOWN) { + mGestureState.setTrackpadGestureType(getTrackpadGestureType(event)); + } + } else { mUncheckedConsumer.onMotionEvent(event); } @@ -749,7 +753,7 @@ public class TouchInteractionService extends Service } public GestureState createGestureState(GestureState previousGestureState, - boolean isTrackpadGesture) { + GestureState.TrackpadGestureType trackpadGestureType) { final GestureState gestureState; TopTaskTracker.CachedTaskInfo taskInfo; if (mTaskAnimationManager.isRecentsAnimationRunning()) { @@ -766,7 +770,7 @@ public class TouchInteractionService extends Service taskInfo = TopTaskTracker.INSTANCE.get(this).getCachedTopTask(false); gestureState.updateRunningTask(taskInfo); } - gestureState.setIsTrackpadGesture(isTrackpadGesture); + gestureState.setTrackpadGestureType(trackpadGestureType); // Log initial state for the gesture. ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=") diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index a8963f602d..2dcbbb907a 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -203,8 +203,24 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC } int edgeFlags = ev.getEdgeFlags(); ev.setEdgeFlags(edgeFlags | EDGE_NAV_BAR); - // Disable scrolling in RecentsView for trackpad gestures. - if (!mGestureState.isTrackpadGesture()) { + + if (mGestureState.isTrackpadGesture()) { + // Disable scrolling in RecentsView for 3-finger trackpad gesture. We don't know if a + // trackpad motion event is 3-finger or 4-finger with the U API until ACTION_MOVE (we + // skip ACTION_POINTER_UP events in TouchInteractionService), so in order to make sure + // that RecentsView always get a closed sequence of motion events and yet disable + // 3-finger scroll, we do the following (1) always dispatch ACTION_DOWN and ACTION_UP + // trackpad multi-finger motion events. (2) only dispatch 4-finger ACTION_MOVE motion + // events. + switch (ev.getActionMasked()) { + case ACTION_MOVE -> { + if (mGestureState.isFourFingerTrackpadGesture()) { + mRecentsViewDispatcher.dispatchEvent(ev); + } + } + default -> mRecentsViewDispatcher.dispatchEvent(ev); + } + } else { mRecentsViewDispatcher.dispatchEvent(ev); } ev.setEdgeFlags(edgeFlags); @@ -312,9 +328,12 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC // Do not allow quick switch for trackpad 3-finger gestures // TODO(b/261815244): might need to impose stronger conditions for the swipe // angle - boolean noQuickSwitchForTrackpadGesture = mGestureState.isTrackpadGesture() - && isLikelyToStartNewTask; - if (isHorizontalSwipeWhenDisabled || noQuickSwitchForTrackpadGesture) { + boolean noQuickSwitchForThreeFingerGesture = isLikelyToStartNewTask + && mGestureState.isThreeFingerTrackpadGesture(); + boolean noQuickstepForFourFingerGesture = !isLikelyToStartNewTask + && mGestureState.isFourFingerTrackpadGesture(); + if (isHorizontalSwipeWhenDisabled || noQuickSwitchForThreeFingerGesture + || noQuickstepForFourFingerGesture) { forceCancelGesture(ev); break; } diff --git a/src/com/android/launcher3/MotionEventsUtils.java b/src/com/android/launcher3/MotionEventsUtils.java index 7f8794dfe8..40de003bd4 100644 --- a/src/com/android/launcher3/MotionEventsUtils.java +++ b/src/com/android/launcher3/MotionEventsUtils.java @@ -42,6 +42,14 @@ public class MotionEventsUtils { && event.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE; } + public static boolean isTrackpadThreeFingerSwipe(MotionEvent event) { + return isTrackpadMultiFingerSwipe(event) && event.getPointerCount() == 3; + } + + public static boolean isTrackpadFourFingerSwipe(MotionEvent event) { + return isTrackpadMultiFingerSwipe(event) && event.getPointerCount() == 4; + } + public static boolean isTrackpadMotionEvent(MotionEvent event) { return isTrackpadScroll(event) || isTrackpadMultiFingerSwipe(event); }