From 0956f6e2fdc2e500ab7b9478b654a1fcc3b67c8d Mon Sep 17 00:00:00 2001 From: Arthur Hung Date: Wed, 7 Dec 2022 08:17:19 +0000 Subject: [PATCH] Let BackProgressAnimator could play cancel animation Add cancel and the cancel callback in BackProgressAnimator so it could animate to start position and invoke the callback after finished. Bug: 259608500 Test: atest BackAnimationControllerTest BackNavigationControllerTests Test: atest BackProgressAnimatorTest Change-Id: I94303ba530d155f4b264dafa21bd23185a6b44bd --- core/java/android/window/BackEvent.java | 3 + .../android/window/BackProgressAnimator.java | 23 ++++ .../window/WindowOnBackInvokedDispatcher.java | 11 +- .../wm/shell/back/CrossActivityAnimation.java | 9 +- .../wm/shell/back/CrossTaskBackAnimation.java | 3 +- .../shell/back/BackProgressAnimatorTest.java | 107 ++++++++++++++++++ 6 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java index 40c0feee7c94e..f53737abf7078 100644 --- a/core/java/android/window/BackEvent.java +++ b/core/java/android/window/BackEvent.java @@ -74,6 +74,9 @@ public final class BackEvent { * and animation should seek to its end state. Exact end value may vary depending on * screen size. * + *
  • After the gesture finishes in cancel state, this method keeps getting invoked until the + * progress value animates back to 0. + * * In-between locations are linearly interpolated based on horizontal distance from the starting * edge and smooth clamped to 1 when the distance exceeds a system-wide threshold. */ diff --git a/core/java/android/window/BackProgressAnimator.java b/core/java/android/window/BackProgressAnimator.java index 14a57e047a08f..618670a0a2c4c 100644 --- a/core/java/android/window/BackProgressAnimator.java +++ b/core/java/android/window/BackProgressAnimator.java @@ -16,8 +16,10 @@ package android.window; +import android.annotation.NonNull; import android.util.FloatProperty; +import com.android.internal.dynamicanimation.animation.DynamicAnimation; import com.android.internal.dynamicanimation.animation.SpringAnimation; import com.android.internal.dynamicanimation.animation.SpringForce; @@ -126,6 +128,27 @@ public class BackProgressAnimator { mProgress = 0; } + /** + * Animate the back progress animation from current progress to start position. + * This should be called when back is cancelled. + * + * @param finishCallback the callback to be invoked when the progress is reach to 0. + */ + public void onBackCancelled(@NonNull Runnable finishCallback) { + final DynamicAnimation.OnAnimationEndListener listener = + new DynamicAnimation.OnAnimationEndListener() { + @Override + public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, + float velocity) { + mSpring.removeEndListener(this); + finishCallback.run(); + reset(); + } + }; + mSpring.addEndListener(listener); + mSpring.animateToFinalPosition(0); + } + private void updateProgressValue(float progress) { if (mLastBackEvent == null || mCallback == null || !mStarted) { return; diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 64992b9ebef8d..7a5510cc252c5 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -266,11 +266,12 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @Override public void onBackCancelled() { Handler.getMain().post(() -> { - mProgressAnimator.reset(); - final OnBackAnimationCallback callback = getBackAnimationCallback(); - if (callback != null) { - callback.onBackCancelled(); - } + mProgressAnimator.onBackCancelled(() -> { + final OnBackAnimationCallback callback = getBackAnimationCallback(); + if (callback != null) { + callback.onBackCancelled(); + } + }); }); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java index e36e16c82da60..5d384944821ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java @@ -171,6 +171,7 @@ class CrossActivityAnimation { mInitialTouchPos.set(0, 0); mEnteringWindowShow = false; mEnteringMargin = 0; + mEnteringAnimator = null; if (mFinishCallback != null) { try { @@ -276,7 +277,7 @@ class CrossActivityAnimation { } // End the fade in animation. - if (mEnteringAnimator.isRunning()) { + if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) { mEnteringAnimator.cancel(); } @@ -329,12 +330,10 @@ class CrossActivityAnimation { @Override public void onBackCancelled() { // End the fade in animation. - if (mEnteringAnimator.isRunning()) { + if (mEnteringAnimator != null && mEnteringAnimator.isRunning()) { mEnteringAnimator.cancel(); } - // TODO (b259608500): Let BackProgressAnimator could play cancel animation. - mProgressAnimator.reset(); - finishAnimation(); + mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 676e25923301f..99a434aff7991 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -329,8 +329,7 @@ class CrossTaskBackAnimation { @Override public void onBackCancelled() { - mProgressAnimator.reset(); - finishAnimation(); + mProgressAnimator.onBackCancelled(CrossTaskBackAnimation.this::finishAnimation); } @Override diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java new file mode 100644 index 0000000000000..3608474bd90e7 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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; + +import android.os.Handler; +import android.os.Looper; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.window.BackEvent; +import android.window.BackMotionEvent; +import android.window.BackProgressAnimator; + +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner.class) +public class BackProgressAnimatorTest { + private BackProgressAnimator mProgressAnimator; + private BackEvent mReceivedBackEvent; + private float mTargetProgress = 0.5f; + private CountDownLatch mTargetProgressCalled = new CountDownLatch(1); + private Handler mMainThreadHandler; + + @Before + public void setUp() throws Exception { + mMainThreadHandler = new Handler(Looper.getMainLooper()); + final BackMotionEvent backEvent = new BackMotionEvent( + 0, 0, + 0, EDGE_LEFT, null); + mMainThreadHandler.post( + () -> { + mProgressAnimator = new BackProgressAnimator(); + mProgressAnimator.onBackStarted(backEvent, this::onGestureProgress); + }); + } + + @Test + public void testBackProgressed() throws InterruptedException { + final BackMotionEvent backEvent = new BackMotionEvent( + 100, 0, + mTargetProgress, EDGE_LEFT, null); + mMainThreadHandler.post( + () -> mProgressAnimator.onBackProgressed(backEvent)); + + mTargetProgressCalled.await(1, TimeUnit.SECONDS); + + assertNotNull(mReceivedBackEvent); + assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */); + } + + @Test + public void testBackCancelled() throws InterruptedException { + // Give the animator some progress. + final BackMotionEvent backEvent = new BackMotionEvent( + 100, 0, + mTargetProgress, EDGE_LEFT, null); + mMainThreadHandler.post( + () -> mProgressAnimator.onBackProgressed(backEvent)); + mTargetProgressCalled.await(1, TimeUnit.SECONDS); + assertNotNull(mReceivedBackEvent); + + // Trigger animation cancel, the target progress should be 0. + mTargetProgress = 0; + mTargetProgressCalled = new CountDownLatch(1); + CountDownLatch cancelCallbackCalled = new CountDownLatch(1); + mMainThreadHandler.post( + () -> mProgressAnimator.onBackCancelled(() -> cancelCallbackCalled.countDown())); + cancelCallbackCalled.await(1, TimeUnit.SECONDS); + mTargetProgressCalled.await(1, TimeUnit.SECONDS); + assertNotNull(mReceivedBackEvent); + assertEquals(mReceivedBackEvent.getProgress(), mTargetProgress, 0 /* delta */); + } + + private void onGestureProgress(BackEvent backEvent) { + if (mTargetProgress == backEvent.getProgress()) { + mReceivedBackEvent = backEvent; + mTargetProgressCalled.countDown(); + } + } +}