Compose overscroll gesture updates

Two changes for the latest Compose prototype:
1. Pass the identity of the underlying app to the Overscroll plugin.
2. Enable the Compose gesture over an app when there's a Recents extra
card plugin active (otherwise the current app won't count as the
rightmost one).

Some changes to the gesture:
- Angle decreased from 35° to 25° to remove overlap with
Assistant gesture
- Distance increased from 8 to 110 dp. 110 dp is 2x the Assistant
gesture and roughly the same as scrubbing into an app from Home.
- Fling detection added; uses same distance threshold, 110 dp.
- If a touch was recognized as another gesture, the touch will not be
reinterpreted as a Compose gesture, no matter what touch movement occurs
- Fixes issue where Assistant + Compose could both be triggered
- Fixes issue where scrubbing apps to the left, then back to the right,
would bring in Compose. i.e. if a touch down + touch movement starts
bringing in Assistant UI elements, then, the user moves their touch
below the Assistant angle, the Compose gesture will not start
being recognized
- Gesture length required for fling lowered from 110 dp to 40 dp, per
tuning with PM.

Bug: b/146508473
Change-Id: I414573d1a92684d1d992837a5f1df522346ec211
This commit is contained in:
James O'Leary 2019-12-20 14:24:10 -05:00
parent c7d601e923
commit a9156a05c4
7 changed files with 122 additions and 15 deletions

View File

@ -484,7 +484,9 @@ public class TouchInteractionService extends Service implements PluginListener<O
base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
}
if (mOverscrollPlugin != null) {
if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()
&& (mOverscrollPlugin != null)
&& mOverscrollPlugin.isActive()) {
// Put the overscroll gesture as higher priority than the Assistant or base gestures
base = new OverscrollInputConsumer(this, newGestureState, base, mInputMonitorCompat,
mOverscrollPlugin);

View File

@ -26,14 +26,17 @@ import static com.android.launcher3.Utilities.squaredHypot;
import android.content.Context;
import android.graphics.PointF;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.annotation.Nullable;
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.R;
import com.android.quickstep.GestureState;
import com.android.quickstep.InputConsumer;
import com.android.quickstep.views.LauncherRecentsView;
import com.android.quickstep.views.RecentsView;
import com.android.systemui.plugins.OverscrollPlugin;
import com.android.systemui.shared.system.InputMonitorCompat;
@ -47,12 +50,12 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
private static final String TAG = "OverscrollInputConsumer";
private static final int ANGLE_THRESHOLD = 35; // Degrees
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 int mActivePointerId = -1;
private boolean mPassedSlop = false;
@ -60,19 +63,28 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
private final Context mContext;
private final GestureState mGestureState;
@Nullable private final OverscrollPlugin mPlugin;
@Nullable
private final OverscrollPlugin mPlugin;
private final GestureDetector mGestureDetector;
private RecentsView mRecentsView;
public OverscrollInputConsumer(Context context, GestureState gestureState,
InputConsumer delegate, InputMonitorCompat inputMonitor, OverscrollPlugin plugin) {
super(delegate, inputMonitor);
mAngleThreshold = context.getResources()
.getInteger(R.integer.assistant_gesture_corner_deg_threshold);
mFlingThresholdPx = context.getResources()
.getDimension(R.dimen.gestures_overscroll_fling_threshold);
mContext = context;
mGestureState = gestureState;
mPlugin = plugin;
float slop = ViewConfiguration.get(context).getScaledTouchSlop();
mSquaredSlop = slop * slop;
mGestureDetector = new GestureDetector(context, new FlingGestureListener());
gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
.register();
@ -139,21 +151,29 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
mPassedSlop = true;
mStartDragPos.set(mLastPos.x, mLastPos.y);
if (isOverscrolled()) {
setActive(ev);
if (mPlugin != null) {
mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity());
}
} else {
mState = STATE_DELEGATE_ACTIVE;
}
}
}
if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled()
&& mPlugin != null) {
mPlugin.onTouchTraveled(getDistancePx());
}
break;
}
case ACTION_CANCEL:
case ACTION_UP:
if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) {
mPlugin.onOverscroll(getDeviceState());
mPlugin.onTouchEnd(getDistancePx());
}
mPassedSlop = false;
@ -161,6 +181,10 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
break;
}
if (mState != STATE_DELEGATE_ACTIVE) {
mGestureDetector.onTouchEvent(ev);
}
if (mState != STATE_ACTIVE) {
mDelegate.onMotionEvent(ev);
}
@ -168,12 +192,19 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
private boolean isOverscrolled() {
// Make sure there isn't an app to quick switch to on our right
boolean atRightMostApp = (mRecentsView == null || mRecentsView.getRunningTaskIndex() <= 0);
int maxIndex = 0;
if ((mRecentsView instanceof LauncherRecentsView)
&& ((LauncherRecentsView) mRecentsView).hasRecentsExtraCard()) {
maxIndex = 1;
}
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)) < ANGLE_THRESHOLD;
boolean angleInBounds = Math.toDegrees(Math.atan2(deltaY, deltaX)) < mAngleThreshold;
return atRightMostApp && angleInBounds;
}
@ -193,4 +224,36 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
return deviceState;
}
private int getDistancePx() {
return (int) Math.hypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y);
}
private String getUnderlyingActivity() {
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

@ -377,6 +377,11 @@ public class LauncherRecentsView extends RecentsView<Launcher> implements StateL
addView(mRecentsExtraViewContainer, 0);
}
@Override
public boolean hasRecentsExtraCard() {
return mRecentsExtraViewContainer != null;
}
@Override
public void setContentAlpha(float alpha) {
super.setContentAlpha(alpha);

View File

@ -830,6 +830,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
public abstract void startHome();
/** `true` if there is a +1 space available in overview. */
public boolean hasRecentsExtraCard() {
return false;
}
public void reset() {
setCurrentTask(-1);
mIgnoreResetTaskId = -1;

View File

@ -77,4 +77,7 @@
<!-- Distance to move elements when swiping up to go home from launcher -->
<dimen name="home_pullback_distance">28dp</dimen>
<!-- Overscroll Gesture -->
<dimen name="gestures_overscroll_fling_threshold">40dp</dimen>
</resources>

View File

@ -114,7 +114,7 @@ public final class FeatureFlags {
"ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
public static final TogglableFlag ENABLE_QUICK_CAPTURE_GESTURE = new TogglableFlag(
"ENABLE_QUICK_CAPTURE_GESTURE", false, "Swipe from right to left to quick capture");
"ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture");
public static final TogglableFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag(
"ASSISTANT_GIVES_LAUNCHER_FOCUS", false,

View File

@ -24,11 +24,11 @@ import com.android.systemui.plugins.annotations.ProvidesInterface;
* the user to a more recent app).
*/
@ProvidesInterface(action = com.android.systemui.plugins.OverscrollPlugin.ACTION,
version = com.android.systemui.plugins.OverlayPlugin.VERSION)
version = com.android.systemui.plugins.OverscrollPlugin.VERSION)
public interface OverscrollPlugin extends Plugin {
String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL";
int VERSION = 1;
int VERSION = 3;
String DEVICE_STATE_LOCKED = "Locked";
String DEVICE_STATE_LAUNCHER = "Launcher";
@ -36,9 +36,38 @@ public interface OverscrollPlugin extends Plugin {
String DEVICE_STATE_UNKNOWN = "Unknown";
/**
* Called when the user completed a right to left swipe in the gesture area.
*
* @param deviceState One of the DEVICE_STATE_* constants.
* @return true if the plugin is active and will accept overscroll gestures
*/
void onOverscroll(String deviceState);
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.
*
* @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);
/**
* 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.
*/
void onTouchEnd(int px);
/**
* Called when the user starts Compose with a fling. `onTouchUp` will also be called.
*/
void onFling(float velocity);
}