From caec34ff2cf1af60d756627b733ef80b06696c93 Mon Sep 17 00:00:00 2001 From: omarmt Date: Tue, 4 Apr 2023 10:02:05 +0000 Subject: [PATCH] BackEvent: add generated events for fling gesture The system generates additional progress events based on finger speed. The idea is to capture the energy of the fling gesture and continue the movement adding up to 100ms. Test: atest BackAnimationControllerTest Bug: 263402927 Change-Id: I88898435680b22ffbb06e41692bb041b5da03076 --- .../shell/back/BackAnimationController.java | 99 ++++++++++++++++++- .../android/wm/shell/back/TouchTracker.java | 47 ++++++--- .../back/BackAnimationControllerTest.java | 65 ++++++++++-- 3 files changed, 184 insertions(+), 27 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 47d3a5c520745..dc27ceb7f51cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -20,6 +20,9 @@ import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTas import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW; import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityTaskManager; @@ -37,7 +40,9 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Global; +import android.util.DisplayMetrics; import android.util.Log; +import android.util.MathUtils; import android.util.SparseArray; import android.view.IRemoteAnimationRunner; import android.view.InputDevice; @@ -56,6 +61,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.view.AppearanceRegion; +import com.android.wm.shell.animation.FlingAnimationUtils; import com.android.wm.shell.common.ExternalInterfaceBinder; import com.android.wm.shell.common.RemoteCallable; import com.android.wm.shell.common.ShellExecutor; @@ -80,6 +86,17 @@ public class BackAnimationController implements RemoteCallable= minVelocity) { + ValueAnimator animator = ValueAnimator.ofFloat(currentX, endX); + + mFlingAnimationUtils.apply( + /* animator = */ animator, + /* currValue = */ currentX, + /* endValue = */ endX, + /* velocity = */ velocity, + /* maxDistance = */ maxFlingDistance + ); + + animator.addUpdateListener(animation -> { + Float animatedValue = (Float) animation.getAnimatedValue(); + float progress = mTouchTracker.getProgress(animatedValue); + final BackMotionEvent backEvent = mTouchTracker + .createProgressEvent(progress); + dispatchOnBackProgressed(mActiveCallback, backEvent); + }); + + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + dispatchOnBackInvoked(callback); + } + }); + animator.start(); + animationStarted = true; + } + } + } + + if (!animationStarted) { + dispatchOnBackInvoked(callback); + } + } + private void dispatchOnBackInvoked(IOnBackInvokedCallback callback) { if (callback == null) { return; @@ -530,7 +625,7 @@ public class BackAnimationController implements RemoteCallable= 0 - ? PROGRESS_THRESHOLD : mProgressThreshold; - progressThreshold = progressThreshold == 0 ? 1 : progressThreshold; float progress = 0; // Progress is always 0 when back is cancelled and not restarted. if (!mCancelled) { - // If back is committed, progress is the distance between the last and first touch - // point, divided by the max drag distance. Otherwise, it's the distance between - // the last touch point and the starting threshold, divided by max drag distance. - // The starting threshold is initially the first touch location, and updated to - // the location everytime back is restarted after being cancelled. - float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; - float deltaX = Math.max( - mSwipeEdge == BackEvent.EDGE_LEFT - ? mLatestTouchX - startX - : startX - mLatestTouchX, - 0); - progress = Math.min(Math.max(deltaX / progressThreshold, 0), 1); + progress = getProgress(mLatestTouchX); } return createProgressEvent(progress); } + /** + * Progress value computed from the touch position. + * + * @param touchX the X touch position of the {@link MotionEvent}. + * @return progress value + */ + @FloatRange(from = 0.0, to = 1.0) + float getProgress(float touchX) { + // If back is committed, progress is the distance between the last and first touch + // point, divided by the max drag distance. Otherwise, it's the distance between + // the last touch point and the starting threshold, divided by max drag distance. + // The starting threshold is initially the first touch location, and updated to + // the location everytime back is restarted after being cancelled. + float startX = mTriggerBack ? mInitTouchX : mStartThresholdX; + float deltaX = Math.abs(startX - touchX); + float maxX = getMaxX(); + maxX = maxX == 0 ? 1 : maxX; + return MathUtils.constrain(deltaX / maxX, 0, 1); + } + + /** + * Maximum X value (in pixels). + * Progress is considered to be completed (1f) when this limit is exceeded. + */ + float getMaxX() { + return PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold; + } + BackMotionEvent createProgressEvent(float progress) { return new BackMotionEvent( /* touchX = */ mLatestTouchX, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index d95c7a488ea1c..3d8bd3854a453 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -135,12 +135,15 @@ public class BackAnimationControllerTest extends ShellTestCase { mShellExecutor.flushAll(); } - private void createNavigationInfo(int backType, boolean enableAnimation) { + private void createNavigationInfo(int backType, + boolean enableAnimation, + boolean isAnimationCallback) { BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder() .setType(backType) .setOnBackNavigationDone(new RemoteCallback((bundle) -> {})) .setOnBackInvokedCallback(mAppCallback) - .setPrepareRemoteAnimation(enableAnimation); + .setPrepareRemoteAnimation(enableAnimation) + .setAnimationCallback(isAnimationCallback); createNavigationInfo(builder); } @@ -218,7 +221,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void backToHome_dispatchesEvents() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); @@ -239,6 +244,32 @@ public class BackAnimationControllerTest extends ShellTestCase { verify(mAnimatorCallback).onBackInvoked(); } + @Test + public void backToHomeWithAnimationCallback_dispatchesEvents() throws RemoteException { + registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ true); + + doMotionEvent(MotionEvent.ACTION_DOWN, 0); + + // Check that back start and progress is dispatched when first move. + doMotionEvent(MotionEvent.ACTION_MOVE, 100, 3000); + + simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + + verify(mAnimatorCallback).onBackStarted(any(BackMotionEvent.class)); + verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); + ArgumentCaptor backEventCaptor = + ArgumentCaptor.forClass(BackMotionEvent.class); + verify(mAnimatorCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture()); + + // Check that back invocation is dispatched. + mController.setTriggerBack(true); // Fake trigger back + doMotionEvent(MotionEvent.ACTION_UP, 0); + verify(mAnimatorCallback).onBackInvoked(); + } + @Test public void animationDisabledFromSettings() throws RemoteException { // Toggle the setting off @@ -254,7 +285,9 @@ public class BackAnimationControllerTest extends ShellTestCase { ArgumentCaptor backEventCaptor = ArgumentCaptor.forClass(BackMotionEvent.class); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ false, + /* isAnimationCallback = */ false); triggerBackGesture(); releaseBackGesture(); @@ -271,7 +304,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void ignoresGesture_transitionInProgress() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); @@ -309,7 +344,9 @@ public class BackAnimationControllerTest extends ShellTestCase { @Test public void acceptsGesture_transitionTimeout() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); // In case it is still running in animation. doNothing().when(mAnimatorCallback).onBackInvoked(); @@ -334,7 +371,9 @@ public class BackAnimationControllerTest extends ShellTestCase { public void cancelBackInvokeWhenLostFocus() throws RemoteException { registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); - createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, true); + createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); // Check that back start and progress is dispatched when first move. @@ -454,7 +493,9 @@ public class BackAnimationControllerTest extends ShellTestCase { mController.registerAnimation(type, animationRunner); - createNavigationInfo(type, true); + createNavigationInfo(type, + /* enableAnimation = */ true, + /* isAnimationCallback = */ false); doMotionEvent(MotionEvent.ACTION_DOWN, 0); @@ -473,11 +514,15 @@ public class BackAnimationControllerTest extends ShellTestCase { } private void doMotionEvent(int actionDown, int coordinate) { + doMotionEvent(actionDown, coordinate, 0); + } + + private void doMotionEvent(int actionDown, int coordinate, float velocity) { mController.onMotionEvent( /* touchX */ coordinate, /* touchY */ coordinate, - /* velocityX = */ 0, - /* velocityY = */ 0, + /* velocityX = */ velocity, + /* velocityY = */ velocity, /* keyAction */ actionDown, /* swipeEdge */ BackEvent.EDGE_LEFT); }