Removing separate Cancel and End callbacks and using AnimationListener instead

This removes the additional step of invoking these callbacks separately

Change-Id: I0b60047a44f179ba725f15b1e791e336884869c9
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index 5181a86..f73e2f2 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -17,6 +17,8 @@
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
 import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_APPS_EDU;
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS;
@@ -24,7 +26,6 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
-import static com.android.launcher3.touch.AbstractStateChangeTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
 
 import android.animation.ValueAnimator;
@@ -186,8 +187,8 @@
         if (topView != null) {
             topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder);
         }
-        mCurrentAnimation = builder.createPlaybackController()
-                .setOnCancelRunnable(this::clearState);
+        mCurrentAnimation = builder.createPlaybackController();
+        mCurrentAnimation.getTarget().addListener(newCancelListener(this::clearState));
     }
 
     private void clearState() {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 2a3bdbf..702c519 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.uioverrides.touchcontrollers;
 
+import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
@@ -191,15 +192,17 @@
             return;
         }
         mNormalToHintOverviewScrimAnimator = null;
-        mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
+        mCurrentAnimation.getTarget().addListener(newCancelListener(() ->
             mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
                 mOverviewResistYAnim = AnimatorControllerWithResistance
                         .createRecentsResistanceFromOverviewAnim(mLauncher, null)
                         .createPlaybackController();
                 mReachedOverview = true;
                 maybeSwipeInteractionToOverviewComplete();
-            });
-        });
+            })));
+
+        mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
+        mCurrentAnimation.dispatchOnCancel();
         mStartedOverview = true;
         VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index f378848..df433f8 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.uioverrides.touchcontrollers;
 
+import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_BUTTONS;
@@ -44,6 +45,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.graphics.PointF;
@@ -92,6 +94,8 @@
     private final MotionPauseDetector mMotionPauseDetector;
     private final float mMotionPauseMinDisplacement;
     private final LauncherRecentsView mRecentsView;
+    protected final AnimatorListener mClearStateOnCancelListener =
+            newCancelListener(this::clearState);
 
     private boolean mNoIntercept;
     private LauncherState mStartState;
@@ -204,8 +208,8 @@
         config.duration = (long) (Math.max(mXRange, mYRange) * 2);
         config.animFlags = config.animFlags | SKIP_OVERVIEW;
         mNonOverviewAnim = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(toState, config)
-                .setOnCancelRunnable(this::clearState);
+                .createAnimationToNewWorkspace(toState, config);
+        mNonOverviewAnim.getTarget().addListener(mClearStateOnCancelListener);
     }
 
     private void setupOverviewAnimators() {
@@ -379,7 +383,8 @@
             if (canceled) {
                 // Let the state manager know that the animation didn't go to the target state,
                 // but don't clean up yet (we already clean up when the animation completes).
-                mNonOverviewAnim.dispatchOnCancelWithoutCancelRunnable();
+                mNonOverviewAnim.getTarget().removeListener(mClearStateOnCancelListener);
+                mNonOverviewAnim.dispatchOnCancel();
             }
             float startProgress = mNonOverviewAnim.getProgressFraction();
             float endProgress = canceled ? 0 : 1;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 037d988..3c9b808 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -245,29 +245,29 @@
         config.animFlags = animFlags;
         config.duration = maxAccuracy;
 
-        cancelPendingAnim();
+        if (mCurrentAnimation != null) {
+            mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
+            mCurrentAnimation.dispatchOnCancel();
+        }
 
+        mGoingBetweenStates = true;
         if (mFromState == OVERVIEW && mToState == NORMAL
                 && mOverviewPortraitStateTouchHelper.shouldSwipeDownReturnToApp()) {
             // Reset the state manager, when changing the interaction mode
             mLauncher.getStateManager().goToState(OVERVIEW, false /* animate */);
-            mPendingAnimation = mOverviewPortraitStateTouchHelper
-                    .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR);
-            Runnable onCancelRunnable = () -> {
-                cancelPendingAnim();
-                clearState();
-            };
-            mCurrentAnimation = mPendingAnimation.createPlaybackController()
-                    .setOnCancelRunnable(onCancelRunnable);
+            mGoingBetweenStates = false;
+            mCurrentAnimation = mOverviewPortraitStateTouchHelper
+                    .createSwipeDownToTaskAppAnimation(maxAccuracy, Interpolators.LINEAR)
+                    .createPlaybackController();
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
             RecentsView recentsView = mLauncher.getOverviewPanel();
             totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
                     mLauncher.getDeviceProfile(), recentsView.getPagedOrientationHandler());
         } else {
             mCurrentAnimation = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(mToState, config)
-                    .setOnCancelRunnable(this::clearState);
+                    .createAnimationToNewWorkspace(mToState, config);
         }
+        mCurrentAnimation.getTarget().addListener(mClearStateOnCancelListener);
 
         if (totalShift == 0) {
             totalShift = Math.signum(mFromState.ordinal - mToState.ordinal)
@@ -276,13 +276,6 @@
         return 1 / totalShift;
     }
 
-    private void cancelPendingAnim() {
-        if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
-            mPendingAnimation = null;
-        }
-    }
-
     @Override
     protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
             LauncherState targetState, float velocity, boolean isFling) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index d39b6f5..fc9e1bb 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -106,8 +106,8 @@
         setupInterpolators(config);
         config.duration = (long) (getShiftRange() * 2);
         mCurrentAnimation = mLauncher.getStateManager()
-                .createAnimationToNewWorkspace(mToState, config)
-                .setOnCancelRunnable(this::clearState);
+                .createAnimationToNewWorkspace(mToState, config);
+        mCurrentAnimation.getTarget().addListener(mClearStateOnCancelListener);
         mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator ->
                 updateFullscreenProgress((Float) valueAnimator.getAnimatedValue()));
         return 1 / getShiftRange();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 186caf6..9729695 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -16,6 +16,7 @@
 package com.android.launcher3.uioverrides.touchcontrollers;
 
 import static com.android.launcher3.AbstractFloatingView.TYPE_ACCESSIBLE;
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_BOTH;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE;
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_POSITIVE;
@@ -50,16 +51,12 @@
         extends AnimatorListenerAdapter implements TouchController,
         SingleAxisSwipeDetector.Listener {
 
-    // Progress after which the transition is assumed to be a success in case user does not fling
-    public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
-
     protected final T mActivity;
     private final SingleAxisSwipeDetector mDetector;
     private final RecentsView mRecentsView;
     private final int[] mTempCords = new int[2];
     private final boolean mIsRtl;
 
-    private PendingAnimation mPendingAnimation;
     private AnimatorPlaybackController mCurrentAnimation;
     private boolean mCurrentAnimationIsGoingUp;
 
@@ -200,10 +197,8 @@
         }
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setPlayFraction(0);
-        }
-        if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
-            mPendingAnimation = null;
+            mCurrentAnimation.getTarget().removeListener(this);
+            mCurrentAnimation.dispatchOnCancel();
         }
 
         PagedOrientationHandler orientationHandler = mRecentsView.getPagedOrientationHandler();
@@ -216,15 +211,16 @@
         // The interpolator controlling the most prominent visual movement. We use this to determine
         // whether we passed SUCCESS_TRANSITION_PROGRESS.
         final Interpolator currentInterpolator;
+        PendingAnimation pa;
         if (goingUp) {
             currentInterpolator = Interpolators.LINEAR;
-            mPendingAnimation = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
+            pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
                     true /* animateTaskView */, true /* removeTask */, maxDuration);
 
             mEndDisplacement = -secondaryTaskDimension;
         } else {
             currentInterpolator = Interpolators.ZOOM_IN;
-            mPendingAnimation = mRecentsView.createTaskLaunchAnimation(
+            pa = mRecentsView.createTaskLaunchAnimation(
                     mTaskBeingDragged, maxDuration, currentInterpolator);
 
             // Since the thumbnail is what is filling the screen, based the end displacement on it.
@@ -234,12 +230,8 @@
             mEndDisplacement = secondaryLayerDimension - mTempCords[1];
         }
         mEndDisplacement *= verticalFactor;
+        mCurrentAnimation = pa.createPlaybackController();
 
-        if (mCurrentAnimation != null) {
-            mCurrentAnimation.setOnCancelRunnable(null);
-        }
-        mCurrentAnimation = mPendingAnimation.createPlaybackController()
-                .setOnCancelRunnable(this::clearState);
         // Setting this interpolator doesn't affect the visual motion, but is used to determine
         // whether we successfully reached the target state in onDragEnd().
         mCurrentAnimation.getTarget().setInterpolator(currentInterpolator);
@@ -303,27 +295,15 @@
             animationDuration *= LauncherAnimUtils.blockedFlingDurationFactor(velocity);
         }
 
-        mCurrentAnimation.setEndAction(() -> onCurrentAnimationEnd(goingToEnd));
+        mCurrentAnimation.setEndAction(this::clearState);
         mCurrentAnimation.startWithVelocity(mActivity, goingToEnd,
                 velocity, mEndDisplacement, animationDuration);
     }
 
-    private void onCurrentAnimationEnd(boolean wasSuccess) {
-        if (mPendingAnimation != null) {
-            mPendingAnimation.finish(wasSuccess);
-            mPendingAnimation = null;
-        }
-        clearState();
-    }
-
     private void clearState() {
         mDetector.finishedScrolling();
         mDetector.setDetectableScrollConditions(0, false);
         mTaskBeingDragged = null;
         mCurrentAnimation = null;
-        if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
-            mPendingAnimation = null;
-        }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index f35eb1f..7efa5bd 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
@@ -39,7 +40,6 @@
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_DISMISS_SWIPE_UP;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK_LAUNCH_SWIPE_DOWN;
 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
-import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.TaskUtils.checkCurrentOrManagedUserId;
@@ -99,7 +99,6 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
-import com.android.launcher3.anim.PendingAnimation.EndState;
 import com.android.launcher3.anim.SpringProperty;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
@@ -772,7 +771,7 @@
 
     protected void applyLoadPlan(ArrayList<Task> tasks) {
         if (mPendingAnimation != null) {
-            mPendingAnimation.addEndListener((endState) -> applyLoadPlan(tasks));
+            mPendingAnimation.addEndListener(success -> applyLoadPlan(tasks));
             return;
         }
 
@@ -1464,19 +1463,10 @@
                 verticalFactor * secondaryTaskDimension).setDuration(duration), LINEAR, sp);
     }
 
-    private void removeTask(TaskView taskView, int index, EndState endState) {
-        if (taskView.getTask() != null) {
-            UI_HELPER_EXECUTOR.execute(() ->
-                    ActivityManagerWrapper.getInstance().removeTask(taskView.getTask().key.id));
-            mActivity.getStatsLogManager().logger().withItemInfo(taskView.getItemInfo())
-                    .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
-        }
-    }
-
     public PendingAnimation createTaskDismissAnimation(TaskView taskView, boolean animateTaskView,
             boolean shouldRemoveTask, long duration) {
         if (mPendingAnimation != null) {
-            mPendingAnimation.finish(false);
+            mPendingAnimation.createPlaybackController().dispatchOnCancel();
         }
         PendingAnimation anim = new PendingAnimation(duration);
 
@@ -1559,22 +1549,27 @@
         }
 
         mPendingAnimation = anim;
-        mPendingAnimation.addEndListener(new Consumer<EndState>() {
+        mPendingAnimation.addEndListener(new Consumer<Boolean>() {
             @Override
-            public void accept(EndState endState) {
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
-                        taskView.isRunningTask() && endState.isSuccess) {
-                    finishRecentsAnimation(true /* toHome */, () -> onEnd(endState));
+            public void accept(Boolean success) {
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask() && success) {
+                    finishRecentsAnimation(true /* toHome */, () -> onEnd(success));
                 } else {
-                    onEnd(endState);
+                    onEnd(success);
                 }
             }
 
             @SuppressWarnings("WrongCall")
-            private void onEnd(EndState endState) {
-                if (endState.isSuccess) {
+            private void onEnd(boolean success) {
+                if (success) {
                     if (shouldRemoveTask) {
-                        removeTask(taskView, draggedIndex, endState);
+                        if (taskView.getTask() != null) {
+                            UI_HELPER_EXECUTOR.execute(() -> ActivityManagerWrapper.getInstance()
+                                    .removeTask(taskView.getTask().key.id));
+                            mActivity.getStatsLogManager().logger()
+                                    .withItemInfo(taskView.getItemInfo())
+                                    .log(LAUNCHER_TASK_DISMISS_SWIPE_UP);
+                        }
                     }
 
                     int pageToSnapTo = mCurrentPage;
@@ -1613,8 +1608,8 @@
         }
 
         mPendingAnimation = anim;
-        mPendingAnimation.addEndListener((endState) -> {
-            if (endState.isSuccess) {
+        mPendingAnimation.addEndListener(isSuccess -> {
+            if (isSuccess) {
                 // Remove all the task views now
                 UI_HELPER_EXECUTOR.execute(
                         ActivityManagerWrapper.getInstance()::removeAllRecentTasks);
@@ -1642,7 +1637,6 @@
     protected void runDismissAnimation(PendingAnimation pendingAnim) {
         AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
         controller.dispatchOnStart();
-        controller.setEndAction(() -> pendingAnim.finish(true));
         controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
         controller.start();
     }
@@ -2186,8 +2180,8 @@
             mLiveTileTaskViewSimulator.addOverviewToAppAnim(mPendingAnimation, interpolator);
             mPendingAnimation.addOnFrameCallback(this::redrawLiveTile);
         }
-        mPendingAnimation.addEndListener((endState) -> {
-            if (endState.isSuccess) {
+        mPendingAnimation.addEndListener(isSuccess -> {
+            if (isSuccess) {
                 Consumer<Boolean> onLaunchResult = (result) -> {
                     onTaskLaunchAnimationEnd(result);
                     if (!result) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 5154018..1b85b2e 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -72,7 +72,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.SystemShortcut;
 import com.android.launcher3.statemanager.StatefulActivity;
@@ -380,11 +379,9 @@
     }
 
     public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
-        final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation(
-                this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR);
-        AnimatorPlaybackController currentAnimation = pendingAnimation.createPlaybackController();
-        currentAnimation.setEndAction(() -> pendingAnimation.finish(true));
-        return currentAnimation;
+        return getRecentsView().createTaskLaunchAnimation(
+                this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR)
+                .createPlaybackController();
     }
 
     public void launchTask(boolean animate) {
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index d9cf7f1..803f8d2 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.launcher3;
 
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.graphics.drawable.Drawable;
 import android.util.FloatProperty;
 import android.util.IntProperty;
@@ -29,8 +32,8 @@
      */
     public static final int SPRING_LOADED_EXIT_DELAY = 500;
 
-    // The progress of an animation to all apps must be at least this far along to snap to all apps.
-    public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
+    // Progress after which the transition is assumed to be a success
+    public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
 
     public static final IntProperty<Drawable> DRAWABLE_ALPHA =
             new IntProperty<Drawable>("drawableAlpha") {
@@ -131,4 +134,23 @@
                             return view.getAlpha();
                         }
                     };
+
+    /**
+     * Utility method to create an {@link AnimatorListener} which executes a callback on animation
+     * cancel.
+     */
+    public static AnimatorListener newCancelListener(Runnable callback) {
+        return new AnimatorListenerAdapter() {
+
+            boolean mDispatched = false;
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                if (!mDispatched) {
+                    mDispatched = true;
+                    callback.run();
+                }
+            }
+        };
+    }
 }
diff --git a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
index 08d573f..01d01ea 100644
--- a/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsInsetTransitionController.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.allapps;
 
+import android.annotation.TargetApi;
 import android.graphics.Insets;
 import android.os.Build;
 import android.util.Log;
@@ -26,9 +27,8 @@
 import android.view.animation.LinearInterpolator;
 
 import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.core.os.BuildCompat;
 
+import com.android.launcher3.Utilities;
 import com.android.launcher3.util.UiThreadHelper;
 
 /**
@@ -68,7 +68,7 @@
     }
 
     public void hide() {
-        if (!BuildCompat.isAtLeastR()) return;
+        if (!Utilities.ATLEAST_R) return;
 
         WindowInsets insets = mApps.getRootWindowInsets();
         if (insets == null) return;
@@ -89,9 +89,9 @@
      *
      * @param progress value between 0..1
      */
-    @RequiresApi(api = Build.VERSION_CODES.R)
+    @TargetApi(Build.VERSION_CODES.R)
     public void onDragStart(float progress) {
-        if (!BuildCompat.isAtLeastR()) return;
+        if (!Utilities.ATLEAST_R) return;
 
         // Until getRootWindowInsets().isVisible(...) method returns correct value,
         // only support InsetController based IME transition during swipe up and
@@ -164,7 +164,7 @@
      * If IME bounds after touch sequence finishes, call finish.
      */
     private boolean handleFinishOnFling(WindowInsetsAnimationController controller) {
-        if (!BuildCompat.isAtLeastR()) return false;
+        if (!Utilities.ATLEAST_R) return false;
 
         if (mState == State.FLING_END_TOP) {
             controller.finish(true);
@@ -181,9 +181,9 @@
      *
      * @param progress value between 0..1
      */
-    @RequiresApi(api = 30)
+    @TargetApi(Build.VERSION_CODES.R)
     public void setProgress(float progress) {
-        if (!BuildCompat.isAtLeastR()) return;
+        if (!Utilities.ATLEAST_R) return;
         // progress that equals to 0 or 1 is error prone. Do not use them.
         // Instead use onDragStart and onAnimationEnd
         if (mAnimationController == null || progress <= 0f || progress >= 1f) return;
@@ -218,7 +218,7 @@
      *
      * @param progress value between 0..1
      */
-    @RequiresApi(api = 30)
+    @TargetApi(Build.VERSION_CODES.R)
     public void onAnimationEnd(float progress) {
         if (DEBUG) {
             Log.d(TAG, "onAnimationEnd progress=" + progress
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index dcdfb6e..edaf51d 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -29,8 +29,6 @@
 import android.animation.ValueAnimator;
 import android.content.Context;
 
-import androidx.annotation.Nullable;
-
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -72,7 +70,6 @@
     private Runnable mEndAction;
 
     protected boolean mTargetCancelled = false;
-    protected Runnable mOnCancelRunnable;
 
     /** package private */
     AnimatorPlaybackController(AnimatorSet anim, long duration, ArrayList<Holder> childAnims) {
@@ -88,16 +85,11 @@
             @Override
             public void onAnimationCancel(Animator animation) {
                 mTargetCancelled = true;
-                if (mOnCancelRunnable != null) {
-                    mOnCancelRunnable.run();
-                    mOnCancelRunnable = null;
-                }
             }
 
             @Override
             public void onAnimationEnd(Animator animation) {
                 mTargetCancelled = false;
-                mOnCancelRunnable = null;
             }
 
             @Override
@@ -269,33 +261,6 @@
         }
     }
 
-    /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */
-    public void dispatchOnCancelWithoutCancelRunnable() {
-        dispatchOnCancelWithoutCancelRunnable(null);
-    }
-
-    /**
-     * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
-     * is intended to be used only if you need to cancel but want to defer cleaning up yourself.
-     * @param callback An optional callback to run after dispatching the cancel but before resetting
-     *                 the onCancelRunnable.
-     */
-    public void dispatchOnCancelWithoutCancelRunnable(@Nullable Runnable callback) {
-        Runnable onCancel = mOnCancelRunnable;
-        setOnCancelRunnable(null);
-        dispatchOnCancel();
-        if (callback != null) {
-            callback.run();
-        }
-        setOnCancelRunnable(onCancel);
-    }
-
-
-    public AnimatorPlaybackController setOnCancelRunnable(Runnable runnable) {
-        mOnCancelRunnable = runnable;
-        return this;
-    }
-
     public void dispatchOnStart() {
         callListenerCommandRecursively(mAnim, AnimatorListener::onAnimationStart);
     }
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 6dd316e..4e90c9e 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -15,10 +15,12 @@
  */
 package com.android.launcher3.anim;
 
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.anim.AnimatorPlaybackController.addAnimationHoldersRecur;
 
 import android.animation.Animator;
 import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -43,8 +45,6 @@
  */
 public class PendingAnimation implements PropertySetter {
 
-    private final ArrayList<Consumer<EndState>> mEndListeners = new ArrayList<>();
-
     private final ArrayList<Holder> mAnimHolders = new ArrayList<>();
     private final AnimatorSet mAnim;
     private final long mDuration;
@@ -73,13 +73,6 @@
         addAnimationHoldersRecur(a, mDuration, springProperty, mAnimHolders);
     }
 
-    public void finish(boolean isSuccess) {
-        for (Consumer<EndState> listeners : mEndListeners) {
-            listeners.accept(new EndState(isSuccess));
-        }
-        mEndListeners.clear();
-    }
-
     @Override
     public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
         if (view == null || view.getAlpha() == alpha) {
@@ -163,19 +156,38 @@
     }
 
     /**
-     * Add a listener of receiving the end state.
-     * Note that the listeners are called as a result of calling {@link #finish(boolean)}
-     * and not automatically
+     * Add a listener of receiving the success/failure callback in the end.
      */
-    public void addEndListener(Consumer<EndState> listener) {
-        mEndListeners.add(listener);
+    public void addEndListener(Consumer<Boolean> listener) {
+        if (mProgressAnimator == null) {
+            mProgressAnimator = ValueAnimator.ofFloat(0, 1);
+        }
+        mProgressAnimator.addListener(new EndStateCallbackWrapper(listener));
     }
 
-    public static class EndState {
-        public boolean isSuccess;
+    private static class EndStateCallbackWrapper extends AnimatorListenerAdapter {
 
-        public EndState(boolean isSuccess) {
-            this.isSuccess = isSuccess;
+        private final Consumer<Boolean> mListener;
+        private boolean mCalled = false;
+
+        EndStateCallbackWrapper(Consumer<Boolean> listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (!mCalled) {
+                mCalled = true;
+                mListener.accept(false);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            if (!mCalled) {
+                ValueAnimator anim = (ValueAnimator) animation;
+                mListener.accept(anim.getAnimatedFraction() > SUCCESS_TRANSITION_PROGRESS);
+            }
         }
     }
 }
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 57c0ad9..a9d0e61 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -15,7 +15,8 @@
  */
 package com.android.launcher3.touch;
 
-import static com.android.launcher3.LauncherAnimUtils.MIN_PROGRESS_TO_ALL_APPS;
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
+import static com.android.launcher3.LauncherAnimUtils.newCancelListener;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -32,6 +33,7 @@
 import static com.android.launcher3.util.DisplayController.getSingleFrameMs;
 
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ValueAnimator;
@@ -48,7 +50,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logger.LauncherAtom;
 import com.android.launcher3.logging.StatsLogManager;
@@ -64,9 +65,6 @@
 public abstract class AbstractStateChangeTouchController
         implements TouchController, SingleAxisSwipeDetector.Listener {
 
-    // Progress after which the transition is assumed to be a success in case user does not fling
-    public static final float SUCCESS_TRANSITION_PROGRESS = 0.5f;
-
     /**
      * Play an atomic recents animation when the progress from NORMAL to OVERVIEW reaches this.
      */
@@ -77,6 +75,9 @@
     protected final SingleAxisSwipeDetector mDetector;
     protected final SingleAxisSwipeDetector.Direction mSwipeDirection;
 
+    protected final AnimatorListener mClearStateOnCancelListener =
+            newCancelListener(this::clearState);
+
     private boolean mNoIntercept;
     private boolean mIsLogContainerSet;
     protected int mStartContainerType;
@@ -85,7 +86,7 @@
     protected LauncherState mFromState;
     protected LauncherState mToState;
     protected AnimatorPlaybackController mCurrentAnimation;
-    protected PendingAnimation mPendingAnimation;
+    protected boolean mGoingBetweenStates = true;
 
     private float mStartProgress;
     // Ratio of transition process [0, 1] to drag displacement (px)
@@ -209,7 +210,7 @@
         mStartProgress = 0;
         mPassedOverviewAtomicThreshold = false;
         if (mCurrentAnimation != null) {
-            mCurrentAnimation.setOnCancelRunnable(null);
+            mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
         }
         int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
                 ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS;
@@ -237,7 +238,7 @@
             LauncherState toState) {
         return (fromState == NORMAL || fromState == OVERVIEW)
                 && (toState == NORMAL || toState == OVERVIEW)
-                && mPendingAnimation == null;
+                && mGoingBetweenStates;
     }
 
     @Override
@@ -412,9 +413,8 @@
                             ? mToState : mFromState;
             // snap to top or bottom using the release velocity
         } else {
-            float successProgress = mToState == ALL_APPS
-                    ? MIN_PROGRESS_TO_ALL_APPS : SUCCESS_TRANSITION_PROGRESS;
-            targetState = (interpolatedProgress > successProgress) ? mToState : mFromState;
+            targetState =
+                    (interpolatedProgress > SUCCESS_TRANSITION_PROGRESS) ? mToState : mFromState;
         }
 
         final float endProgress;
@@ -438,7 +438,8 @@
         } else {
             // Let the state manager know that the animation didn't go to the target state,
             // but don't cancel ourselves (we already clean up when the animation completes).
-            mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable();
+            mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
+            mCurrentAnimation.dispatchOnCancel();
 
             endProgress = 0;
             if (progress <= 0) {
@@ -522,13 +523,7 @@
             mAtomicComponentsController = null;
         }
         clearState();
-        boolean shouldGoToTargetState = true;
-        if (mPendingAnimation != null) {
-            boolean reachedTarget = mToState == targetState;
-            mPendingAnimation.finish(reachedTarget);
-            mPendingAnimation = null;
-            shouldGoToTargetState = !reachedTarget;
-        }
+        boolean shouldGoToTargetState = mGoingBetweenStates || (mToState != targetState);
         if (shouldGoToTargetState) {
             goToTargetState(targetState);
         }
@@ -568,6 +563,7 @@
             mAtomicAnim.cancel();
             mAtomicAnim = null;
         }
+        mGoingBetweenStates = true;
         mScheduleResumeAtomicComponent = false;
         mDetector.finishedScrolling();
         mDetector.setDetectableScrollConditions(0, false);