BackMotionEvent: add velocity (in pixels/second)

The velocity of the `BackMotionEvent` will be used to fire additional events when the user lifts their finger.

Note: Velocity is calculated for the last event only, for performance reasons (see `VelocityTracker.computeCurrentVelocity`).
`Float.NaN` will indicate that the value has not been calculated.

Test: atest BackAnimationControllerTest
Bug: 263402927
Change-Id: I301636f572eb4e59abc69f6fee11d8dba5647a16
This commit is contained in:
omarmt
2023-02-15 12:50:26 +00:00
parent 4872084330
commit 4ad57489bf
10 changed files with 188 additions and 55 deletions

View File

@@ -34,6 +34,8 @@ public final class BackMotionEvent implements Parcelable {
private final float mTouchX;
private final float mTouchY;
private final float mProgress;
private final float mVelocityX;
private final float mVelocityY;
@BackEvent.SwipeEdge
private final int mSwipeEdge;
@@ -43,19 +45,32 @@ public final class BackMotionEvent implements Parcelable {
/**
* Creates a new {@link BackMotionEvent} instance.
*
* <p>Note: Velocity is only computed for last event, for performance reasons.</p>
*
* @param touchX Absolute X location of the touch point of this event.
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param velocityX X velocity computed from the touch point of this event.
* Value in pixels/second. {@link Float#NaN} if was not computed.
* @param velocityY Y velocity computed from the touch point of this event.
* Value in pixels/second. {@link Float#NaN} if was not computed.
* @param swipeEdge Indicates which edge the swipe starts from.
* @param departingAnimationTarget The remote animation target of the departing
* application window.
*/
public BackMotionEvent(float touchX, float touchY, float progress,
public BackMotionEvent(
float touchX,
float touchY,
float progress,
float velocityX,
float velocityY,
@BackEvent.SwipeEdge int swipeEdge,
@Nullable RemoteAnimationTarget departingAnimationTarget) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mVelocityX = velocityX;
mVelocityY = velocityY;
mSwipeEdge = swipeEdge;
mDepartingAnimationTarget = departingAnimationTarget;
}
@@ -64,6 +79,8 @@ public final class BackMotionEvent implements Parcelable {
mTouchX = in.readFloat();
mTouchY = in.readFloat();
mProgress = in.readFloat();
mVelocityX = in.readFloat();
mVelocityY = in.readFloat();
mSwipeEdge = in.readInt();
mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
@@ -91,20 +108,12 @@ public final class BackMotionEvent implements Parcelable {
dest.writeFloat(mTouchX);
dest.writeFloat(mTouchY);
dest.writeFloat(mProgress);
dest.writeFloat(mVelocityX);
dest.writeFloat(mVelocityY);
dest.writeInt(mSwipeEdge);
dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
* Returns the progress of a {@link BackEvent}.
*
* @see BackEvent#getProgress()
*/
@FloatRange(from = 0, to = 1)
public float getProgress() {
return mProgress;
}
/**
* Returns the absolute X location of the touch point.
*/
@@ -119,6 +128,34 @@ public final class BackMotionEvent implements Parcelable {
return mTouchY;
}
/**
* Returns the progress of a {@link BackEvent}.
*
* @see BackEvent#getProgress()
*/
@FloatRange(from = 0, to = 1)
public float getProgress() {
return mProgress;
}
/**
* Returns the X velocity computed from the touch point.
*
* @return value in pixels/second or {@link Float#NaN} if was not computed.
*/
public float getVelocityX() {
return mVelocityX;
}
/**
* Returns the Y velocity computed from the touch point.
*
* @return value in pixels/second or {@link Float#NaN} if was not computed.
*/
public float getVelocityY() {
return mVelocityY;
}
/**
* Returns the screen edge that the swipe starts from.
*/
@@ -143,6 +180,8 @@ public final class BackMotionEvent implements Parcelable {
+ "mTouchX=" + mTouchX
+ ", mTouchY=" + mTouchY
+ ", mProgress=" + mProgress
+ ", mVelocityX=" + mVelocityX
+ ", mVelocityY=" + mVelocityY
+ ", mSwipeEdge" + mSwipeEdge
+ ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ "}";

View File

@@ -70,7 +70,13 @@ public class WindowOnBackInvokedDispatcherTest {
private ApplicationInfo mApplicationInfo;
private final BackMotionEvent mBackEvent = new BackMotionEvent(
0, 0, 0, BackEvent.EDGE_LEFT, null);
/* touchX = */ 0,
/* touchY = */ 0,
/* progress = */ 0,
/* velocityX = */ 0,
/* velocityY = */ 0,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null);
@Before
public void setUp() throws Exception {

View File

@@ -33,12 +33,19 @@ public interface BackAnimation {
*
* @param touchX the X touch position of the {@link MotionEvent}.
* @param touchY the Y touch position of the {@link MotionEvent}.
* @param velocityX the X velocity computed from the {@link MotionEvent}.
* @param velocityY the Y velocity computed from the {@link MotionEvent}.
* @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to
* the process. This is forwarded separately because the input pipeline may mutate
* the {#event} action state later.
* @param swipeEdge the edge from which the swipe begins.
*/
void onBackMotion(float touchX, float touchY, int keyAction,
void onBackMotion(
float touchX,
float touchY,
float velocityX,
float velocityY,
int keyAction,
@BackEvent.SwipeEdge int swipeEdge);
/**

View File

@@ -256,8 +256,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private class BackAnimationImpl implements BackAnimation {
@Override
public void onBackMotion(
float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) {
mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge));
float touchX,
float touchY,
float velocityX,
float velocityY,
int keyAction,
@BackEvent.SwipeEdge int swipeEdge
) {
mShellExecutor.execute(() -> onMotionEvent(
/* touchX = */ touchX,
/* touchY = */ touchY,
/* velocityX = */ velocityX,
/* velocityY = */ velocityY,
/* keyAction = */ keyAction,
/* swipeEdge = */ swipeEdge));
}
@Override
@@ -332,13 +344,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
* Called when a new motion event needs to be transferred to this
* {@link BackAnimationController}
*/
public void onMotionEvent(float touchX, float touchY, int keyAction,
public void onMotionEvent(
float touchX,
float touchY,
float velocityX,
float velocityY,
int keyAction,
@BackEvent.SwipeEdge int swipeEdge) {
if (mPostCommitAnimationInProgress) {
return;
}
mTouchTracker.update(touchX, touchY);
mTouchTracker.update(touchX, touchY, velocityX, velocityY);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;

View File

@@ -42,11 +42,13 @@ class TouchTracker {
*/
private float mInitTouchX;
private float mInitTouchY;
private float mLatestVelocityX;
private float mLatestVelocityY;
private float mStartThresholdX;
private int mSwipeEdge;
private boolean mCancelled;
void update(float touchX, float touchY) {
void update(float touchX, float touchY, float velocityX, float velocityY) {
/**
* If back was previously cancelled but the user has started swiping in the forward
* direction again, restart back.
@@ -58,6 +60,8 @@ class TouchTracker {
}
mLatestTouchX = touchX;
mLatestTouchY = touchY;
mLatestVelocityX = velocityX;
mLatestVelocityY = velocityY;
}
void setTriggerBack(boolean triggerBack) {
@@ -84,7 +88,14 @@ class TouchTracker {
}
BackMotionEvent createStartEvent(RemoteAnimationTarget target) {
return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target);
return new BackMotionEvent(
/* touchX = */ mInitTouchX,
/* touchY = */ mInitTouchY,
/* progress = */ 0,
/* velocityX = */ 0,
/* velocityY = */ 0,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ target);
}
BackMotionEvent createProgressEvent() {
@@ -111,7 +122,14 @@ class TouchTracker {
}
BackMotionEvent createProgressEvent(float progress) {
return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null);
return new BackMotionEvent(
/* touchX = */ mLatestTouchX,
/* touchY = */ mLatestTouchY,
/* progress = */ progress,
/* velocityX = */ mLatestVelocityX,
/* velocityY = */ mLatestVelocityY,
/* swipeEdge = */ mSwipeEdge,
/* departingAnimationTarget = */ null);
}
public void setProgressThreshold(float progressThreshold) {

View File

@@ -458,9 +458,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
private void doMotionEvent(int actionDown, int coordinate) {
mController.onMotionEvent(
coordinate, coordinate,
actionDown,
BackEvent.EDGE_LEFT);
/* touchX */ coordinate,
/* touchY */ coordinate,
/* velocityX = */ 0,
/* velocityY = */ 0,
/* keyAction */ actionDown,
/* swipeEdge */ BackEvent.EDGE_LEFT);
}
private void simulateRemoteAnimationStart(int type) throws RemoteException {

View File

@@ -16,8 +16,6 @@
package com.android.wm.shell.back;
import static android.window.BackEvent.EDGE_LEFT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -48,12 +46,21 @@ public class BackProgressAnimatorTest {
private CountDownLatch mTargetProgressCalled = new CountDownLatch(1);
private Handler mMainThreadHandler;
private BackMotionEvent backMotionEventFrom(float touchX, float progress) {
return new BackMotionEvent(
/* touchX = */ touchX,
/* touchY = */ 0,
/* progress = */ progress,
/* velocityX = */ 0,
/* velocityY = */ 0,
/* swipeEdge = */ BackEvent.EDGE_LEFT,
/* departingAnimationTarget = */ null);
}
@Before
public void setUp() throws Exception {
mMainThreadHandler = new Handler(Looper.getMainLooper());
final BackMotionEvent backEvent = new BackMotionEvent(
0, 0,
0, EDGE_LEFT, null);
final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
mMainThreadHandler.post(
() -> {
mProgressAnimator = new BackProgressAnimator();
@@ -63,9 +70,7 @@ public class BackProgressAnimatorTest {
@Test
public void testBackProgressed() throws InterruptedException {
final BackMotionEvent backEvent = new BackMotionEvent(
100, 0,
mTargetProgress, EDGE_LEFT, null);
final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
mMainThreadHandler.post(
() -> mProgressAnimator.onBackProgressed(backEvent));
@@ -78,9 +83,7 @@ public class BackProgressAnimatorTest {
@Test
public void testBackCancelled() throws InterruptedException {
// Give the animator some progress.
final BackMotionEvent backEvent = new BackMotionEvent(
100, 0,
mTargetProgress, EDGE_LEFT, null);
final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress);
mMainThreadHandler.post(
() -> mProgressAnimator.onBackProgressed(backEvent));
mTargetProgressCalled.await(1, TimeUnit.SECONDS);

View File

@@ -47,43 +47,45 @@ public class TouchTrackerTest {
public void generatesProgress_leftEdge() {
mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT);
float touchX = 10;
float velocityX = 0;
float velocityY = 0;
// Pre-commit
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
// Post-commit
touchX += 100;
mTouchTracker.setTriggerBack(true);
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
// Cancel
touchX -= 10;
mTouchTracker.setTriggerBack(false);
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Cancel more
touchX -= 10;
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restart
touchX += 10;
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restarted, but pre-commit
float restartX = touchX;
touchX += 10;
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f);
// Restarted, post-commit
touchX += 10;
mTouchTracker.setTriggerBack(true);
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f);
}
@@ -91,43 +93,45 @@ public class TouchTrackerTest {
public void generatesProgress_rightEdge() {
mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT);
float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge
float velocityX = 0f;
float velocityY = 0f;
// Pre-commit
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
// Post-commit
touchX -= 100;
mTouchTracker.setTriggerBack(true);
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
// Cancel
touchX += 10;
mTouchTracker.setTriggerBack(false);
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Cancel more
touchX += 10;
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restart
touchX -= 10;
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), 0, 0f);
// Restarted, but pre-commit
float restartX = touchX;
touchX -= 10;
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f);
// Restarted, post-commit
touchX -= 10;
mTouchTracker.setTriggerBack(true);
mTouchTracker.update(touchX, 0);
mTouchTracker.update(touchX, 0, velocityX, velocityY);
assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f);
}

View File

@@ -55,6 +55,7 @@ import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.VelocityTracker;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -173,7 +174,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
};
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final Context mContext;
private final UserTracker mUserTracker;
private final OverviewProxyService mOverviewProxyService;
@@ -901,6 +902,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev);
}
// ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new
// ACTION_DOWN, in that case we should just reuse the old instance.
mVelocityTracker.clear();
// Verify if this is in within the touch region and we aren't in immersive mode, and
// either the bouncer is showing or the notification panel is hidden
mInputEventReceiver.setBatchingEnabled(false);
@@ -1027,11 +1032,30 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private void dispatchToBackAnimation(MotionEvent event) {
if (mBackAnimation != null) {
mVelocityTracker.addMovement(event);
final float velocityX;
final float velocityY;
if (event.getAction() == MotionEvent.ACTION_UP) {
// Compute the current velocity is expensive (see computeCurrentVelocity), so we
// are only doing it when the user completes the gesture.
int unitPixelPerSecond = 1000;
int maxVelocity = mViewConfiguration.getScaledMaximumFlingVelocity();
mVelocityTracker.computeCurrentVelocity(unitPixelPerSecond, maxVelocity);
velocityX = mVelocityTracker.getXVelocity();
velocityY = mVelocityTracker.getYVelocity();
} else {
velocityX = Float.NaN;
velocityY = Float.NaN;
}
mBackAnimation.onBackMotion(
event.getX(),
event.getY(),
event.getActionMasked(),
mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
/* touchX = */ event.getX(),
/* touchY = */ event.getY(),
/* velocityX = */ velocityX,
/* velocityY = */ velocityY,
/* keyAction = */ event.getActionMasked(),
/* swipeEdge = */ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
}
}

View File

@@ -37,7 +37,13 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() {
@Test
fun onBackProgressed_shouldInvoke_onBackProgress() {
val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
val backEvent =
BackEvent(
/* touchX = */ 0f,
/* touchY = */ 0f,
/* progress = */ 0f,
/* swipeEdge = */ BackEvent.EDGE_LEFT
)
onBackAnimationCallback.onBackStarted(backEvent)
onBackAnimationCallback.onBackProgressed(backEvent)
@@ -47,7 +53,13 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() {
@Test
fun onBackStarted_shouldInvoke_onBackStart() {
val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT)
val backEvent =
BackEvent(
/* touchX = */ 0f,
/* touchY = */ 0f,
/* progress = */ 0f,
/* swipeEdge = */ BackEvent.EDGE_LEFT
)
onBackAnimationCallback.onBackStarted(backEvent)