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:
parent
c7d601e923
commit
a9156a05c4
|
@ -484,7 +484,9 @@ public class TouchInteractionService extends Service implements PluginListener<O
|
||||||
base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
|
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
|
// Put the overscroll gesture as higher priority than the Assistant or base gestures
|
||||||
base = new OverscrollInputConsumer(this, newGestureState, base, mInputMonitorCompat,
|
base = new OverscrollInputConsumer(this, newGestureState, base, mInputMonitorCompat,
|
||||||
mOverscrollPlugin);
|
mOverscrollPlugin);
|
||||||
|
|
|
@ -26,14 +26,17 @@ import static com.android.launcher3.Utilities.squaredHypot;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.PointF;
|
import android.graphics.PointF;
|
||||||
|
import android.view.GestureDetector;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.launcher3.BaseDraggingActivity;
|
import com.android.launcher3.BaseDraggingActivity;
|
||||||
|
import com.android.launcher3.R;
|
||||||
import com.android.quickstep.GestureState;
|
import com.android.quickstep.GestureState;
|
||||||
import com.android.quickstep.InputConsumer;
|
import com.android.quickstep.InputConsumer;
|
||||||
|
import com.android.quickstep.views.LauncherRecentsView;
|
||||||
import com.android.quickstep.views.RecentsView;
|
import com.android.quickstep.views.RecentsView;
|
||||||
import com.android.systemui.plugins.OverscrollPlugin;
|
import com.android.systemui.plugins.OverscrollPlugin;
|
||||||
import com.android.systemui.shared.system.InputMonitorCompat;
|
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 String TAG = "OverscrollInputConsumer";
|
||||||
|
|
||||||
private static final int ANGLE_THRESHOLD = 35; // Degrees
|
|
||||||
|
|
||||||
private final PointF mDownPos = new PointF();
|
private final PointF mDownPos = new PointF();
|
||||||
private final PointF mLastPos = new PointF();
|
private final PointF mLastPos = new PointF();
|
||||||
private final PointF mStartDragPos = new PointF();
|
private final PointF mStartDragPos = new PointF();
|
||||||
|
private final int mAngleThreshold;
|
||||||
|
|
||||||
|
private final float mFlingThresholdPx;
|
||||||
private int mActivePointerId = -1;
|
private int mActivePointerId = -1;
|
||||||
private boolean mPassedSlop = false;
|
private boolean mPassedSlop = false;
|
||||||
|
|
||||||
|
@ -60,19 +63,28 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
private final GestureState mGestureState;
|
private final GestureState mGestureState;
|
||||||
@Nullable private final OverscrollPlugin mPlugin;
|
@Nullable
|
||||||
|
private final OverscrollPlugin mPlugin;
|
||||||
|
private final GestureDetector mGestureDetector;
|
||||||
|
|
||||||
private RecentsView mRecentsView;
|
private RecentsView mRecentsView;
|
||||||
|
|
||||||
public OverscrollInputConsumer(Context context, GestureState gestureState,
|
public OverscrollInputConsumer(Context context, GestureState gestureState,
|
||||||
InputConsumer delegate, InputMonitorCompat inputMonitor, OverscrollPlugin plugin) {
|
InputConsumer delegate, InputMonitorCompat inputMonitor, OverscrollPlugin plugin) {
|
||||||
super(delegate, inputMonitor);
|
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;
|
mContext = context;
|
||||||
mGestureState = gestureState;
|
mGestureState = gestureState;
|
||||||
mPlugin = plugin;
|
mPlugin = plugin;
|
||||||
|
|
||||||
float slop = ViewConfiguration.get(context).getScaledTouchSlop();
|
float slop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||||
|
|
||||||
mSquaredSlop = slop * slop;
|
mSquaredSlop = slop * slop;
|
||||||
|
mGestureDetector = new GestureDetector(context, new FlingGestureListener());
|
||||||
|
|
||||||
gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
|
gestureState.getActivityInterface().createActivityInitListener(this::onActivityInit)
|
||||||
.register();
|
.register();
|
||||||
|
@ -139,21 +151,29 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
|
||||||
|
|
||||||
mPassedSlop = true;
|
mPassedSlop = true;
|
||||||
mStartDragPos.set(mLastPos.x, mLastPos.y);
|
mStartDragPos.set(mLastPos.x, mLastPos.y);
|
||||||
|
|
||||||
if (isOverscrolled()) {
|
if (isOverscrolled()) {
|
||||||
setActive(ev);
|
setActive(ev);
|
||||||
|
|
||||||
|
if (mPlugin != null) {
|
||||||
|
mPlugin.onTouchStart(getDeviceState(), getUnderlyingActivity());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mState = STATE_DELEGATE_ACTIVE;
|
mState = STATE_DELEGATE_ACTIVE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mPassedSlop && mState != STATE_DELEGATE_ACTIVE && isOverscrolled()
|
||||||
|
&& mPlugin != null) {
|
||||||
|
mPlugin.onTouchTraveled(getDistancePx());
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ACTION_CANCEL:
|
case ACTION_CANCEL:
|
||||||
case ACTION_UP:
|
case ACTION_UP:
|
||||||
if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) {
|
if (mState != STATE_DELEGATE_ACTIVE && mPassedSlop && mPlugin != null) {
|
||||||
mPlugin.onOverscroll(getDeviceState());
|
mPlugin.onTouchEnd(getDistancePx());
|
||||||
}
|
}
|
||||||
|
|
||||||
mPassedSlop = false;
|
mPassedSlop = false;
|
||||||
|
@ -161,6 +181,10 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mState != STATE_DELEGATE_ACTIVE) {
|
||||||
|
mGestureDetector.onTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
if (mState != STATE_ACTIVE) {
|
if (mState != STATE_ACTIVE) {
|
||||||
mDelegate.onMotionEvent(ev);
|
mDelegate.onMotionEvent(ev);
|
||||||
}
|
}
|
||||||
|
@ -168,12 +192,19 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
|
||||||
|
|
||||||
private boolean isOverscrolled() {
|
private boolean isOverscrolled() {
|
||||||
// Make sure there isn't an app to quick switch to on our right
|
// 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
|
// Check if the gesture is within our angle threshold of horizontal
|
||||||
float deltaY = Math.abs(mLastPos.y - mDownPos.y);
|
float deltaY = Math.abs(mLastPos.y - mDownPos.y);
|
||||||
float deltaX = mDownPos.x - mLastPos.x; // Positive if this is a gesture to the left
|
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;
|
return atRightMostApp && angleInBounds;
|
||||||
}
|
}
|
||||||
|
@ -193,4 +224,36 @@ public class OverscrollInputConsumer<T extends BaseDraggingActivity> extends Del
|
||||||
|
|
||||||
return deviceState;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -377,6 +377,11 @@ public class LauncherRecentsView extends RecentsView<Launcher> implements StateL
|
||||||
addView(mRecentsExtraViewContainer, 0);
|
addView(mRecentsExtraViewContainer, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasRecentsExtraCard() {
|
||||||
|
return mRecentsExtraViewContainer != null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setContentAlpha(float alpha) {
|
public void setContentAlpha(float alpha) {
|
||||||
super.setContentAlpha(alpha);
|
super.setContentAlpha(alpha);
|
||||||
|
|
|
@ -830,6 +830,11 @@ public abstract class RecentsView<T extends BaseActivity> extends PagedView impl
|
||||||
|
|
||||||
public abstract void startHome();
|
public abstract void startHome();
|
||||||
|
|
||||||
|
/** `true` if there is a +1 space available in overview. */
|
||||||
|
public boolean hasRecentsExtraCard() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
setCurrentTask(-1);
|
setCurrentTask(-1);
|
||||||
mIgnoreResetTaskId = -1;
|
mIgnoreResetTaskId = -1;
|
||||||
|
|
|
@ -77,4 +77,7 @@
|
||||||
|
|
||||||
<!-- Distance to move elements when swiping up to go home from launcher -->
|
<!-- Distance to move elements when swiping up to go home from launcher -->
|
||||||
<dimen name="home_pullback_distance">28dp</dimen>
|
<dimen name="home_pullback_distance">28dp</dimen>
|
||||||
|
|
||||||
|
<!-- Overscroll Gesture -->
|
||||||
|
<dimen name="gestures_overscroll_fling_threshold">40dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -114,7 +114,7 @@ public final class FeatureFlags {
|
||||||
"ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
|
"ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
|
||||||
|
|
||||||
public static final TogglableFlag ENABLE_QUICK_CAPTURE_GESTURE = new TogglableFlag(
|
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(
|
public static final TogglableFlag ASSISTANT_GIVES_LAUNCHER_FOCUS = new TogglableFlag(
|
||||||
"ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
|
"ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
|
||||||
|
|
|
@ -24,11 +24,11 @@ import com.android.systemui.plugins.annotations.ProvidesInterface;
|
||||||
* the user to a more recent app).
|
* the user to a more recent app).
|
||||||
*/
|
*/
|
||||||
@ProvidesInterface(action = com.android.systemui.plugins.OverscrollPlugin.ACTION,
|
@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 {
|
public interface OverscrollPlugin extends Plugin {
|
||||||
|
|
||||||
String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL";
|
String ACTION = "com.android.systemui.action.PLUGIN_LAUNCHER_OVERSCROLL";
|
||||||
int VERSION = 1;
|
int VERSION = 3;
|
||||||
|
|
||||||
String DEVICE_STATE_LOCKED = "Locked";
|
String DEVICE_STATE_LOCKED = "Locked";
|
||||||
String DEVICE_STATE_LAUNCHER = "Launcher";
|
String DEVICE_STATE_LAUNCHER = "Launcher";
|
||||||
|
@ -36,9 +36,38 @@ public interface OverscrollPlugin extends Plugin {
|
||||||
String DEVICE_STATE_UNKNOWN = "Unknown";
|
String DEVICE_STATE_UNKNOWN = "Unknown";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the user completed a right to left swipe in the gesture area.
|
* @return true if the plugin is active and will accept overscroll gestures
|
||||||
*
|
|
||||||
* @param deviceState One of the DEVICE_STATE_* constants.
|
|
||||||
*/
|
*/
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue