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