Merge "Compose gesture integrated fully into Launcher" into ub-launcher3-rvc-dev

This commit is contained in:
James O'Leary
2020-05-14 20:20:13 +00:00
committed by Android (Google) Code Review
6 changed files with 136 additions and 90 deletions

View File

@@ -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);
}
}

View File

@@ -24,12 +24,15 @@ 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;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseDraggingActivity;
@@ -44,24 +47,36 @@ 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;
// When a gesture crosses this length, this recognizer will attempt to interpret touch events.
private final float mSquaredSlop;
// When a gesture crosses this length, this recognizer will become the sole active recognizer.
private final float mSquaredActiveThreshold;
// When a gesture crosses this length, the overscroll view should be shown.
private final float mSquaredFinishThreshold;
private boolean mThisDownIsIgnored = false;
private final GestureState mGestureState;
@Nullable
private final OverscrollPlugin mPlugin;
private final GestureDetector mGestureDetector;
@Nullable
private RecentsView mRecentsView;
@@ -72,15 +87,24 @@ 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 finishGestureThreshold = (int) context.getResources()
.getDimension(R.dimen.gestures_overscroll_finish_threshold);
mSquaredFinishThreshold = finishGestureThreshold * finishGestureThreshold;
float activeThreshold = (int) context.getResources()
.getDimension(R.dimen.gestures_overscroll_active_threshold);
mSquaredActiveThreshold = activeThreshold * activeThreshold;
}
@Override
@@ -90,12 +114,26 @@ public class OverscrollInputConsumer extends DelegateInputConsumer {
@Override
public void onMotionEvent(MotionEvent ev) {
if (mPlugin == null) {
return;
}
debugPrint("got event, underlying activity is " + getUnderlyingActivity());
switch (ev.getActionMasked()) {
case ACTION_DOWN: {
debugPrint("ACTION_DOWN");
mActivePointerId = ev.getPointerId(0);
mDownPos.set(ev.getX(), ev.getY());
mLastPos.set(mDownPos);
if (mPlugin.blockOtherGestures()) {
debugPrint("mPlugin.blockOtherGestures(), becoming active on ACTION_DOWN");
// Otherwise, if an appear gesture is performed when the Activity is visible,
// the Activity will dismiss its keyboard.
mPassedActiveThreshold = true;
mPassedSlop = true;
mStartDragPos.set(mLastPos.x, mLastPos.y);
setActive(ev);
}
break;
}
case ACTION_POINTER_DOWN: {
@@ -121,57 +159,61 @@ public class OverscrollInputConsumer extends DelegateInputConsumer {
if (mState == STATE_DELEGATE_ACTIVE) {
break;
}
if (!mDelegate.allowInterceptByParent()) {
mState = STATE_DELEGATE_ACTIVE;
break;
}
// Update last touch position.
int pointerIndex = ev.findPointerIndex(mActivePointerId);
if (pointerIndex == -1) {
break;
}
mLastPos.set(ev.getX(pointerIndex), ev.getY(pointerIndex));
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) {
mPassedSlop = true;
mStartDragPos.set(mLastPos.x, mLastPos.y);
if (isOverscrolled()) {
setActive(ev);
if (mPlugin != null) {
mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity());
}
} else {
mState = STATE_DELEGATE_ACTIVE;
}
}
float squaredDist = squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y);
if ((!mPassedSlop) && (squaredDist > mSquaredSlop)) {
mPassedSlop = true;
mStartDragPos.set(mLastPos.x, mLastPos.y);
mGestureState.setState(GestureState.STATE_OVERSCROLL_WINDOW_CREATED);
}
if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled()
&& mPlugin != null) {
mPlugin.onTouchTraveled(getDistancePx());
boolean becomeActive = mPassedSlop && !mPassedActiveThreshold && isOverscrolled()
&& (squaredDist > mSquaredActiveThreshold);
if (becomeActive) {
debugPrint("Past slop and past threshold, set active");
mPassedActiveThreshold = true;
setActive(ev);
}
if (mPassedActiveThreshold) {
debugPrint("ACTION_MOVE Relaying touch event");
mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(),
(int) Math.sqrt(mSquaredFinishThreshold), mFlingDistanceThresholdPx,
mFlingVelocityThresholdPx, getDeviceState(), getUnderlyingActivity());
}
break;
}
case ACTION_CANCEL:
case ACTION_UP:
if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) {
mPlugin.onTouchEnd(getDistancePx());
debugPrint("ACTION_UP");
if (mPassedActiveThreshold) {
debugPrint("ACTION_UP Relaying touch event");
mPlugin.onTouchEvent(ev, getHorizontalDistancePx(), getVerticalDistancePx(),
(int) Math.sqrt(mSquaredFinishThreshold), 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);
}
@@ -192,15 +234,20 @@ public class OverscrollInputConsumer extends DelegateInputConsumer {
maxIndex = 1;
}
boolean atRightMostApp = (mRecentsView == null
|| mRecentsView.getRunningTaskIndex() <= maxIndex);
boolean atRightMostApp = mRecentsView == null
|| (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 = mLastPos.x - mDownPos.x;
return atRightMostApp && angleInBounds;
boolean angleInBounds = (Math.toDegrees(Math.atan2(deltaY, abs(deltaX))) < mAngleThreshold);
boolean overscrollVisible = mPlugin.blockOtherGestures();
boolean overscrollInvisibleAndLeftSwipe = !overscrollVisible && deltaX < 0;
boolean gestureDirectionMatchesVisibility = overscrollVisible
|| overscrollInvisibleAndLeftSwipe;
return atRightMostApp && angleInBounds && gestureDirectionMatchesVisibility;
}
private String getDeviceState() {
@@ -219,35 +266,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() {
private int getVerticalDistancePx() {
return (int) (mLastPos.y - mDownPos.y);
}
private @NonNull 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();
}
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);
}
}
}

View File

@@ -84,6 +84,8 @@
<!-- Overscroll Gesture -->
<dimen name="gestures_overscroll_fling_threshold">40dp</dimen>
<dimen name="gestures_overscroll_active_threshold">80dp</dimen>
<dimen name="gestures_overscroll_finish_threshold">136dp</dimen>
<!-- Tips Gesture Tutorial -->
<dimen name="gesture_tutorial_title_margin_start_end">40dp</dimen>

View File

@@ -109,6 +109,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");

View File

@@ -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");

View File

@@ -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);
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 moved.
*
* @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 onTouchTraveled(int px);
boolean blockOtherGestures();
/**
* 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 the overscroll gesture can pan the underlying app.
*/
void onTouchEnd(int px);
/**
* Called when the user starts Compose with a fling. `onTouchUp` will also be called.
*/
void onFling(float velocity);
boolean allowsUnderlyingActivityOverscroll();
}