diff options
author | 2020-05-04 16:41:58 -0400 | |
---|---|---|
committer | 2020-05-06 13:40:47 -0400 | |
commit | d9c86d4b3b5fe2c5db94f4a801782d83f056e35c (patch) | |
tree | 9163f3e9fdd4bdfeb95f5e774ca108d9142143d3 | |
parent | e93d6d615cd39873b970e41a7a536030c9233377 (diff) |
Compose gesture integrated fully into Launcher
- Support dismissing Compose via the reverse gesture from the appear
gesture
- Use Tony Wickham's ag/10204761 with some glue code to enable the
app below Compose panning in the same direction as the gesture as
Compose peeks in
- Add feature flag to use Compose hosted in a window (permits underlying
app panning)
- Use InterpolatingVelocityTracker to fix OtherActivityInputConsumer
processing swipes in the wrong direction ~20% of the time due to a bug
in VelocityTracker (see go/quirky-bubbles)
Change-Id: I65aa07ac112db8bd89cec9acfa0ce2b6ebacd43f
6 files changed, 133 insertions, 77 deletions
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java index 1f6c506d3a..6b0d7a3e87 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java @@ -26,6 +26,7 @@ import static android.view.MotionEvent.INVALID_POINTER_ID; import static com.android.launcher3.Utilities.EDGE_NAV_BAR; import static com.android.launcher3.Utilities.squaredHypot; import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS; +import static com.android.quickstep.GestureState.STATE_OVERSCROLL_WINDOW_CREATED; import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID; import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; @@ -430,6 +431,6 @@ public class OtherActivityInputConsumer extends ContextWrapper implements InputC @Override public boolean allowInterceptByParent() { - return !mPassedPilferInputSlop; + return !mPassedPilferInputSlop || mGestureState.hasState(STATE_OVERSCROLL_WINDOW_CREATED); } } diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java index c49b8f2d14..1941830134 100644 --- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java +++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OverscrollInputConsumer.java @@ -24,9 +24,11 @@ import static android.view.MotionEvent.ACTION_UP; import static com.android.launcher3.Utilities.squaredHypot; +import static java.lang.Math.abs; + import android.content.Context; import android.graphics.PointF; -import android.view.GestureDetector; +import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; @@ -44,24 +46,31 @@ import com.android.systemui.shared.system.InputMonitorCompat; * Input consumer for handling events to pass to an {@code OverscrollPlugin}. */ public class OverscrollInputConsumer extends DelegateInputConsumer { - private static final String TAG = "OverscrollInputConsumer"; + private static final boolean DEBUG_LOGS_ENABLED = false; + private static void debugPrint(String log) { + if (DEBUG_LOGS_ENABLED) { + Log.v(TAG, log); + } + } private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); private final PointF mStartDragPos = new PointF(); private final int mAngleThreshold; - private final float mFlingThresholdPx; + private final int mFlingDistanceThresholdPx; + private final int mFlingVelocityThresholdPx; private int mActivePointerId = -1; private boolean mPassedSlop = false; - + // True if we set ourselves as active, meaning we no longer pass events to the delegate. + private boolean mPassedActiveThreshold = false; + private final float mSquaredActiveThreshold; private final float mSquaredSlop; private final GestureState mGestureState; @Nullable private final OverscrollPlugin mPlugin; - private final GestureDetector mGestureDetector; @Nullable private RecentsView mRecentsView; @@ -72,15 +81,19 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { mAngleThreshold = context.getResources() .getInteger(R.integer.assistant_gesture_corner_deg_threshold); - mFlingThresholdPx = context.getResources() - .getDimension(R.dimen.gestures_overscroll_fling_threshold); + mFlingDistanceThresholdPx = (int) context.getResources() + .getDimension(R.dimen.gestures_overscroll_fling_threshold); + mFlingVelocityThresholdPx = ViewConfiguration.get(context).getScaledMinimumFlingVelocity(); mGestureState = gestureState; mPlugin = plugin; float slop = ViewConfiguration.get(context).getScaledTouchSlop(); mSquaredSlop = slop * slop; - mGestureDetector = new GestureDetector(context, new FlingGestureListener()); + + float dragThreshold = (int) context.getResources() + .getDimension(R.dimen.gestures_overscroll_drag_threshold); + mSquaredActiveThreshold = dragThreshold * dragThreshold; } @Override @@ -90,12 +103,27 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { @Override public void onMotionEvent(MotionEvent ev) { + if (mPlugin == null) { + return; + } + switch (ev.getActionMasked()) { case ACTION_DOWN: { + if (mPlugin.blockOtherGestures()) { + // When an Activity is visible, blocking other gestures prevents the Activity + // from disappearing upon ACTION_DOWN in the navigation bar. (it will reappear + // on ACTION_MOVE or ACTION_UP) + debugPrint("Becoming active on ACTION_DOWN"); + if (mState != STATE_ACTIVE) { + setActive(ev); + } + } mActivePointerId = ev.getPointerId(0); mDownPos.set(ev.getX(), ev.getY()); mLastPos.set(mDownPos); - + mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(), + (int) Math.sqrt(mSquaredActiveThreshold), mFlingDistanceThresholdPx, + mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity()); break; } case ACTION_POINTER_DOWN: { @@ -131,53 +159,82 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { } mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex)); + float squaredDist = squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y); + + + if (!mPassedSlop) { // Normal gesture, ensure we pass the slop before we start tracking the gesture - if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y) - > mSquaredSlop) { - + if (squaredDist > mSquaredSlop) { + debugPrint("passed slop"); mPassedSlop = true; mStartDragPos.set(mLastPos.x, mLastPos.y); if (isOverscrolled()) { - setActive(ev); - - if (mPlugin != null) { - mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity()); + debugPrint("setting STATE_OVERSCROLL_WINDOW_CREATED"); + mGestureState.setState(GestureState.STATE_OVERSCROLL_WINDOW_CREATED); + if (!mPlugin.allowsUnderlyingActivityOverscroll() + && (mState != STATE_ACTIVE)) { + debugPrint("setting active gesture handler to overscroll to " + + "prevent losing active touch when Activity starts"); + setActive(ev); } - } else { - mState = STATE_DELEGATE_ACTIVE; + } + } else { + debugPrint("Not past slop"); + } + } + + if (mPassedSlop && !mPassedActiveThreshold && isOverscrolled()) { + if ((squaredDist > mSquaredActiveThreshold)) { + debugPrint("Past slop and past threshold, set active"); + + mPassedActiveThreshold = true; + if (mState != STATE_ACTIVE) { + setActive(ev); } } } - if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled() - && mPlugin != null) { - mPlugin.onTouchTraveled(getDistancePx()); + if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled()) { + debugPrint("Relaying touch event"); + mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(), + (int) Math.sqrt(mSquaredActiveThreshold), mFlingDistanceThresholdPx, + mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity()); } break; } case ACTION_CANCEL: case ACTION_UP: - if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) { - mPlugin.onTouchEnd(getDistancePx()); + if (mPassedSlop && isOverscrolled()) { + mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(), + (int) Math.sqrt(mSquaredActiveThreshold), mFlingDistanceThresholdPx, + mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity()); } mPassedSlop = false; + mPassedActiveThreshold = false; mState = STATE_INACTIVE; break; } - if (mState != STATE_DELEGATE_ACTIVE) { - mGestureDetector.onTouchEvent(ev); - } - if (mState != STATE_ACTIVE) { mDelegate.onMotionEvent(ev); } } private boolean isOverscrolled() { + if (mPlugin.blockOtherGestures()) { + // When an Activity is visible, this `InputConsumer` immediately becomes + // the active gesture handler to prevent the Activity from disappearing on TOUCH_DOWN + // in the navbar. + // + // Returning `true` ensures that case will still result in touches being handled, + // instead of dropping touches until the gesture reaches the thresholds calculated + // below. + return true; + } + if (mRecentsView == null) { BaseDraggingActivity activity = mGestureState.getActivityInterface() .getCreatedActivity(); @@ -196,9 +253,10 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { || mRecentsView.getRunningTaskIndex() <= maxIndex); // Check if the gesture is within our angle threshold of horizontal - float deltaY = Math.abs(mLastPos.y - mDownPos.y); - float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left - boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < mAngleThreshold; + float deltaY = abs(mLastPos.y - mDownPos.y); + float deltaX = abs(mDownPos.x - mLastPos.x); + + boolean angleInBounds = (Math.toDegrees(Math.atan2(deltaY, deltaX)) < mAngleThreshold); return atRightMostApp && angleInBounds; } @@ -219,35 +277,22 @@ public class OverscrollInputConsumer extends DelegateInputConsumer { return deviceState; } - private int getDistancePx() { - return (int) Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y); + private int getHorizontalDistancePx() { + return (int) (mLastPos.x - mDownPos.x); } - private String getUnderlyingActivity() { - return mGestureState.getRunningTask().topActivity.flattenToString(); + private int getVerticalDistancePx() { + return (int) (mLastPos.y - mDownPos.y); } - private class FlingGestureListener extends GestureDetector.SimpleOnGestureListener { - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (isValidAngle(velocityX, -velocityY) - && getDistancePx() >= mFlingThresholdPx - && mState != STATE_DELEGATE_ACTIVE) { - - if (mPlugin != null) { - mPlugin.onFling(-velocityX); - } - } - return true; - } - - private boolean isValidAngle(float deltaX, float deltaY) { - float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX)); - // normalize so that angle is measured clockwise from horizontal in the bottom right - // corner and counterclockwise from horizontal in the bottom left corner - - angle = angle > 90 ? 180 - angle : angle; - return (angle < mAngleThreshold); + private String getUnderlyingActivity() { + // Overly defensive, got guidance on code review that something in the chain of + // `mGestureState.getRunningTask().topActivity` can be null and thus cause a null pointer + // exception to be thrown, but we aren't sure which part can be null. + if ((mGestureState == null) || (mGestureState.getRunningTask() == null) + || (mGestureState.getRunningTask().topActivity == null)) { + return ""; } + return mGestureState.getRunningTask().topActivity.flattenToString(); } } diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index b06dc6b01d..6624ff95c5 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -79,6 +79,7 @@ <!-- Overscroll Gesture --> <dimen name="gestures_overscroll_fling_threshold">40dp</dimen> + <dimen name="gestures_overscroll_drag_threshold">136dp</dimen> <!-- Tips Gesture Tutorial --> <dimen name="gesture_tutorial_title_margin_start_end">40dp</dimen> diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index 544f420811..209412a295 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -106,6 +106,10 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL public static final int STATE_RECENTS_ANIMATION_ENDED = getFlagForIndex("STATE_RECENTS_ANIMATION_ENDED"); + // Called when we create an overscroll window when swiping right to left on the most recent app + public static final int STATE_OVERSCROLL_WINDOW_CREATED = + getFlagForIndex("STATE_OVERSCROLL_WINDOW_CREATED"); + // Called when RecentsView stops scrolling and settles on a TaskView. public static final int STATE_RECENTS_SCROLLING_FINISHED = getFlagForIndex("STATE_RECENTS_SCROLLING_FINISHED"); diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java index 78d194bd98..69193399f7 100644 --- a/src/com/android/launcher3/config/FeatureFlags.java +++ b/src/com/android/launcher3/config/FeatureFlags.java @@ -110,6 +110,9 @@ public final class FeatureFlags { public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag( "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture"); + public static final BooleanFlag ENABLE_QUICK_CAPTURE_WINDOW = getDebugFlag( + "ENABLE_QUICK_CAPTURE_WINDOW", false, "Use window to host quick capture"); + public static final BooleanFlag FORCE_LOCAL_OVERSCROLL_PLUGIN = getDebugFlag( "FORCE_LOCAL_OVERSCROLL_PLUGIN", false, "Use a launcher-provided OverscrollPlugin if available"); diff --git a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java index 28a9193bec..a434d078bc 100644 --- a/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java +++ b/src_plugins/com/android/systemui/plugins/OverscrollPlugin.java @@ -15,6 +15,8 @@ */ package com.android.systemui.plugins; +import android.view.MotionEvent; + import com.android.systemui.plugins.annotations.ProvidesInterface; /** @@ -28,7 +30,7 @@ import com.android.systemui.plugins.annotations.ProvidesInterface; public interface OverscrollPlugin extends Plugin { String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL"; - int VERSION = 3; + int VERSION = 4; String DEVICE_STATE_LOCKED = "Locked"; String DEVICE_STATE_LAUNCHER = "Launcher"; @@ -41,33 +43,33 @@ public interface OverscrollPlugin extends Plugin { boolean isActive(); /** - * Called when a touch is down and has been recognized as an overscroll gesture. - * A call of this method will always result in `onTouchUp` being called, and possibly - * `onFling` as well. - * + * Called when a touch has been recognized as an overscroll gesture. + * @param horizontalDistancePx Horizontal distance from the last finger location to the finger + * location when it first touched the screen. + * @param verticalDistancePx Horizontal distance from the last finger location to the finger + * location when it first touched the screen. + * @param thresholdPx Minimum distance for gesture. + * @param flingDistanceThresholdPx Minimum distance for gesture by fling. + * @param flingVelocityThresholdPx Minimum velocity for gesture by fling. * @param deviceState String representing the current device state * @param underlyingActivity String representing the currently active Activity */ - void onTouchStart(String deviceState, String underlyingActivity); - - /** - * Called when a touch that was previously recognized has moved. - * - * @param px distance between the position of touch on this update and the position of the - * touch when it was initially recognized. - */ - void onTouchTraveled(int px); + void onTouchEvent(MotionEvent event, + int horizontalDistancePx, + int verticalDistancePx, + int thresholdPx, + int flingDistanceThresholdPx, + int flingVelocityThresholdPx, + String deviceState, + String underlyingActivity); /** - * Called when a touch that was previously recognized has ended. - * - * @param px distance between the position of touch on this update and the position of the - * touch when it was initially recognized. + * @return `true` if overscroll gesture handling should override all other gestures. */ - void onTouchEnd(int px); + boolean blockOtherGestures(); /** - * Called when the user starts Compose with a fling. `onTouchUp` will also be called. + * @return `true` if the overscroll gesture can pan the underlying app. */ - void onFling(float velocity); + boolean allowsUnderlyingActivityOverscroll(); } |