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