Merge "Revert "Revert "Exposing AnimatorListeners in StateManager to receive both success and failure callback""" into sc-dev
diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 67ed5fb..5dcf84c 100644
--- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -17,6 +17,7 @@
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.hybridhotseat.HotseatEduController.getSettingsIntent;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_PREDICTION_PINNED;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOTSEAT_RANKED;
@@ -150,7 +151,7 @@
      * Shows appropriate hotseat education based on prediction enabled and migration states.
      */
     public void showEdu() {
-        mLauncher.getStateManager().goToState(NORMAL, true, () -> {
+        mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
             if (mPredictedItems.isEmpty()) {
                 // launcher has empty predictions set
                 Snackbar.show(mLauncher, R.string.hotsaet_tip_prediction_disabled,
@@ -165,7 +166,7 @@
                         .collect(Collectors.toList()));
                 eduController.showEdu();
             }
-        });
+        }));
     }
 
     /**
@@ -255,8 +256,8 @@
             }
         }
         if (animate) {
-            animationSet.addListener(AnimationSuccessListener
-                    .forRunnable(this::removeOutlineDrawings));
+            animationSet.addListener(
+                    forSuccessCallback(this::removeOutlineDrawings));
             animationSet.start();
         } else {
             removeOutlineDrawings();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 0652d48..996d36a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -35,7 +35,7 @@
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.states.StateAnimationConfig;
@@ -78,7 +78,7 @@
             mRecentsView.updateEmptyMessage();
         } else {
             builder.addListener(
-                    AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
+                    AnimatorListeners.forSuccessCallback(mRecentsView::resetTaskVisuals));
         }
 
         // Create or dismiss split screen select animations
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index b8caf81..521dd23 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -22,6 +22,7 @@
 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;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_ALL_APPS_EDU;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_GESTURE;
@@ -38,7 +39,6 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PendingAnimation;
@@ -199,7 +199,7 @@
                         .animateWithVelocity(velocity);
             } else {
                 mLauncher.getStateManager().goToState(mEndState, true,
-                        () -> onSwipeInteractionCompleted(mEndState));
+                        forSuccessCallback(() -> onSwipeInteractionCompleted(mEndState)));
             }
             if (mStartState != mEndState) {
                 logHomeGesture();
@@ -214,8 +214,7 @@
             // Quickly return to the state we came from (we didn't move far).
             ValueAnimator anim = mCurrentAnimation.getAnimationPlayer();
             anim.setFloatValues(progress, 0);
-            anim.addListener(AnimationSuccessListener.forRunnable(
-                    () -> onSwipeInteractionCompleted(mStartState)));
+            anim.addListener(forSuccessCallback(() -> onSwipeInteractionCompleted(mStartState)));
             anim.setDuration(80).start();
         }
     }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 729e2cb..eb62110 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
@@ -172,13 +173,13 @@
         }
         mNormalToHintOverviewScrimAnimator = null;
         mCurrentAnimation.getTarget().addListener(newCancelListener(() ->
-                mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
+                mLauncher.getStateManager().goToState(OVERVIEW, true, forSuccessCallback(() -> {
                     mOverviewResistYAnim = AnimatorControllerWithResistance
                             .createRecentsResistanceFromOverviewAnim(mLauncher, null)
                             .createPlaybackController();
                     mReachedOverview = true;
                     maybeSwipeInteractionToOverviewComplete();
-                })));
+                }))));
 
         mCurrentAnimation.getTarget().removeListener(mClearStateOnCancelListener);
         mCurrentAnimation.dispatchOnCancel();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 0f64abc..62687c5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -21,6 +21,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
 import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
@@ -418,7 +419,7 @@
                         targetState.ordinal > mStartState.ordinal
                                 ? LAUNCHER_UNKNOWN_SWIPEUP
                                 : LAUNCHER_UNKNOWN_SWIPEDOWN));
-        mLauncher.getStateManager().goToState(targetState, false, this::clearState);
+        mLauncher.getStateManager().goToState(targetState, false, forEndCallback(this::clearState));
     }
 
     private void cancelAnimations() {
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 1ca4fd7..5217c3b 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -19,6 +19,7 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
+import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
@@ -197,7 +198,8 @@
 
         closeOverlay();
         launcher.getStateManager().goToState(OVERVIEW,
-                launcher.getStateManager().shouldAnimateStateChange(), onCompleteCallback);
+                launcher.getStateManager().shouldAnimateStateChange(),
+                onCompleteCallback == null ? null : forEndCallback(onCompleteCallback));
         return true;
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index 1aa64fa..66e0400 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -43,7 +43,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.quickstep.AnimatedFloat;
@@ -156,7 +156,7 @@
                         fadeAnim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
                     }
                     if (onEndRunnable != null) {
-                        fadeAnim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
+                        fadeAnim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable));
                     }
                     AnimatorSet animset = fadeAnim.buildAnim();
                     animset.setStartDelay(100);
@@ -174,7 +174,7 @@
                 anim.setViewAlpha(mFakePreviousTaskView, 0, ACCEL);
             }
             if (onEndRunnable != null) {
-                anim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
+                anim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable));
             }
         }
         AnimatorSet animset = anim.buildAnim();
@@ -205,10 +205,10 @@
         PendingAnimation fadeAnim = new PendingAnimation(300);
         fadeAnim.setViewAlpha(mFakeIconView, 0, ACCEL);
         if (onEndRunnable != null) {
-            fadeAnim.addListener(AnimationSuccessListener.forRunnable(onEndRunnable));
+            fadeAnim.addListener(AnimatorListeners.forSuccessCallback(onEndRunnable));
         }
         AnimatorSet animset = fadeAnim.buildAnim();
-        rectAnim.addAnimatorListener(AnimationSuccessListener.forRunnable(animset::start));
+        rectAnim.addAnimatorListener(AnimatorListeners.forSuccessCallback(animset::start));
         mRunningWindowAnim = RunningWindowAnim.wrap(rectAnim);
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/TutorialController.java b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
index e86062c..f9d5c94 100644
--- a/quickstep/src/com/android/quickstep/interaction/TutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/TutorialController.java
@@ -38,7 +38,7 @@
 import androidx.appcompat.content.res.AppCompatResources;
 
 import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.views.ClipIconView;
 import com.android.quickstep.interaction.EdgeBackGestureHandler.BackGestureAttemptCallback;
 import com.android.quickstep.interaction.NavBarGestureHandler.NavBarGestureAttemptCallback;
@@ -141,7 +141,7 @@
     }
 
     void fadeTaskViewAndRun(Runnable r) {
-        mFakeTaskView.animate().alpha(0).setListener(AnimationSuccessListener.forRunnable(r));
+        mFakeTaskView.animate().alpha(0).setListener(AnimatorListeners.forSuccessCallback(r));
     }
 
     @StringRes
@@ -359,8 +359,8 @@
                     mContext, getMockLauncherResId()));
             mFakeTaskView.setBackground(AppCompatResources.getDrawable(
                     mContext, getMockAppTaskThumbnailResId()));
-            mFakeTaskView.animate().alpha(1).setListener(AnimationSuccessListener.forRunnable(
-                    () -> mFakeTaskView.animate().cancel()));
+            mFakeTaskView.animate().alpha(1).setListener(
+                    AnimatorListeners.forSuccessCallback(() -> mFakeTaskView.animate().cancel()));
             mFakePreviousTaskView.setBackground(AppCompatResources.getDrawable(
                     mContext, getMockPreviousAppTaskThumbnailResId()));
             mFakeIconView.setBackground(AppCompatResources.getDrawable(
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 396dafb..5bad5e8 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -111,6 +111,7 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringProperty;
@@ -120,7 +121,6 @@
 import com.android.launcher3.statehandlers.DepthController;
 import com.android.launcher3.statemanager.BaseState;
 import com.android.launcher3.statemanager.StatefulActivity;
-import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.OverScroll;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.DynamicResource;
@@ -1677,7 +1677,7 @@
             return;
         }
         AnimatorSet pa = setRecentsChangedOrientation(true);
-        pa.addListener(AnimationSuccessListener.forRunnable(() -> {
+        pa.addListener(AnimatorListeners.forSuccessCallback(() -> {
             setLayoutRotation(newRotation, mOrientationState.getDisplayRotation());
             mActivity.getDragLayer().recreateControllers();
             updateChildTaskOrientations();
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 47c67b9..5ba7623 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -25,6 +25,7 @@
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -1946,8 +1947,8 @@
             }
             parent.onDropChild(cell);
 
-            mLauncher.getStateManager().goToState(
-                    NORMAL, SPRING_LOADED_EXIT_DELAY, onCompleteRunnable);
+            mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY,
+                    onCompleteRunnable == null ? null : forSuccessCallback(onCompleteRunnable));
             mStatsLogManager.logger().withItemInfo(d.dragInfo).withInstanceId(d.logInstanceId)
                     .log(LauncherEvent.LAUNCHER_ITEM_DROP_COMPLETED);
         }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index c580d47..2b36f19 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -3,6 +3,7 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.IGNORE;
 
 import android.appwidget.AppWidgetProviderInfo;
@@ -220,30 +221,26 @@
         } else if (action == ADD_TO_WORKSPACE) {
             final int[] coordinates = new int[2];
             final int screenId = findSpaceOnWorkspace(item, coordinates);
-            mLauncher.getStateManager().goToState(NORMAL, true, new Runnable() {
+            mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
+                if (item instanceof AppInfo) {
+                    WorkspaceItemInfo info = ((AppInfo) item).makeWorkspaceItem();
+                    mLauncher.getModelWriter().addItemToDatabase(info,
+                            Favorites.CONTAINER_DESKTOP,
+                            screenId, coordinates[0], coordinates[1]);
 
-                @Override
-                public void run() {
-                    if (item instanceof AppInfo) {
-                        WorkspaceItemInfo info = ((AppInfo) item).makeWorkspaceItem();
-                        mLauncher.getModelWriter().addItemToDatabase(info,
-                                Favorites.CONTAINER_DESKTOP,
-                                screenId, coordinates[0], coordinates[1]);
-
-                        mLauncher.bindItems(
-                                Collections.singletonList(info),
-                                /* forceAnimateIcons= */ true,
-                                /* focusFirstItemForAccessibility= */ true);
-                        announceConfirmation(R.string.item_added_to_workspace);
-                    } else if (item instanceof PendingAddItemInfo) {
-                        PendingAddItemInfo info = (PendingAddItemInfo) item;
-                        Workspace workspace = mLauncher.getWorkspace();
-                        workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
-                        mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP,
-                                screenId, coordinates, info.spanX, info.spanY);
-                    }
+                    mLauncher.bindItems(
+                            Collections.singletonList(info),
+                            /* forceAnimateIcons= */ true,
+                            /* focusFirstItemForAccessibility= */ true);
+                    announceConfirmation(R.string.item_added_to_workspace);
+                } else if (item instanceof PendingAddItemInfo) {
+                    PendingAddItemInfo info = (PendingAddItemInfo) item;
+                    Workspace workspace = mLauncher.getWorkspace();
+                    workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
+                    mLauncher.addPendingItem(info, Favorites.CONTAINER_DESKTOP,
+                            screenId, coordinates, info.spanX, info.spanY);
                 }
-            });
+            }));
             return true;
         } else if (action == MOVE_TO_WORKSPACE) {
             Folder folder = Folder.getOpen(mLauncher);
diff --git a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
index 1733e5d..f96afa8 100644
--- a/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/ShortcutMenuAccessibilityDelegate.java
@@ -17,6 +17,7 @@
 package com.android.launcher3.accessibility;
 
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
 
 import android.view.KeyEvent;
 import android.view.View;
@@ -67,19 +68,14 @@
             final WorkspaceItemInfo info = ((DeepShortcutView) host.getParent()).getFinalInfo();
             final int[] coordinates = new int[2];
             final int screenId = findSpaceOnWorkspace(item, coordinates);
-            Runnable onComplete = new Runnable() {
-                @Override
-                public void run() {
-                    mLauncher.getModelWriter().addItemToDatabase(info,
-                            LauncherSettings.Favorites.CONTAINER_DESKTOP,
-                            screenId, coordinates[0], coordinates[1]);
-                    mLauncher.bindItems(Collections.singletonList(info), true);
-                    AbstractFloatingView.closeAllOpenViews(mLauncher);
-                    announceConfirmation(R.string.item_added_to_workspace);
-                }
-            };
-
-            mLauncher.getStateManager().goToState(NORMAL, true, onComplete);
+            mLauncher.getStateManager().goToState(NORMAL, true, forSuccessCallback(() -> {
+                mLauncher.getModelWriter().addItemToDatabase(info,
+                        LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                        screenId, coordinates[0], coordinates[1]);
+                mLauncher.bindItems(Collections.singletonList(info), true);
+                AbstractFloatingView.closeAllOpenViews(mLauncher);
+                announceConfirmation(R.string.item_added_to_workspace);
+            }));
             return true;
         } else if (action == DISMISS_NOTIFICATION) {
             if (!(host instanceof NotificationMainView)) {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 75f3149..3a61609 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -25,7 +25,7 @@
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALLAPPS;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import android.animation.Animator.AnimatorListener;
 import android.animation.ObjectAnimator;
 import android.util.FloatProperty;
 import android.view.View;
@@ -36,7 +36,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorListeners;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.config.FeatureFlags;
@@ -185,8 +185,8 @@
         mScrimView.setDrawingController(shouldProtectHeader ? mAppsView : null);
     }
 
-    public AnimatorListenerAdapter getProgressAnimatorListener() {
-        return AnimationSuccessListener.forRunnable(this::onProgressAnimationEnd);
+    public AnimatorListener getProgressAnimatorListener() {
+        return AnimatorListeners.forSuccessCallback(this::onProgressAnimationEnd);
     }
 
     /**
diff --git a/src/com/android/launcher3/anim/AnimationSuccessListener.java b/src/com/android/launcher3/anim/AnimationSuccessListener.java
index 9905e81..a312070 100644
--- a/src/com/android/launcher3/anim/AnimationSuccessListener.java
+++ b/src/com/android/launcher3/anim/AnimationSuccessListener.java
@@ -40,24 +40,4 @@
 
     public abstract void onAnimationSuccess(Animator animator);
 
-    /**
-     * Returns an AnimationSuccessListener which runs the provided action on success
-     */
-    public static AnimationSuccessListener forRunnable(Runnable r) {
-        return new RunnableSuccessListener(r);
-    }
-
-    private static class RunnableSuccessListener extends AnimationSuccessListener {
-
-        private final Runnable mRunnable;
-
-        private RunnableSuccessListener(Runnable r) {
-            mRunnable = r;
-        }
-
-        @Override
-        public void onAnimationSuccess(Animator animator) {
-            mRunnable.run();
-        }
-    }
 }
diff --git a/src/com/android/launcher3/anim/AnimatorListeners.java b/src/com/android/launcher3/anim/AnimatorListeners.java
new file mode 100644
index 0000000..57c2f8d
--- /dev/null
+++ b/src/com/android/launcher3/anim/AnimatorListeners.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2021 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.launcher3.anim;
+
+import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
+
+import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+
+import java.util.function.Consumer;
+
+/**
+ * Utility class for creating common {@link AnimatorListener}
+ */
+public class AnimatorListeners {
+
+    /**
+     * Returns an AnimatorListener which executes the callback on successful animation completion
+     */
+    public static AnimatorListener forSuccessCallback(Runnable callback) {
+        return new RunnableSuccessListener(callback);
+    }
+
+    /**
+     * Returns an AnimatorListener which executes the callback on animation completion,
+     * with the boolean representing success
+     */
+    public static AnimatorListener forEndCallback(Consumer<Boolean> callback) {
+        return new EndStateCallbackWrapper(callback);
+    }
+
+    /**
+     * Returns an AnimatorListener which executes the callback on animation completion
+     */
+    public static AnimatorListener forEndCallback(Runnable callback) {
+        return new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation, boolean isReverse) {
+                callback.run();
+            }
+        };
+    }
+
+    private static class EndStateCallbackWrapper extends AnimatorListenerAdapter {
+
+        private final Consumer<Boolean> mListener;
+        private boolean mListenerCalled = false;
+
+        EndStateCallbackWrapper(Consumer<Boolean> listener) {
+            mListener = listener;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+            if (!mListenerCalled) {
+                mListenerCalled = true;
+                mListener.accept(false);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator anim) {
+            if (!mListenerCalled) {
+                mListenerCalled = true;
+                mListener.accept(anim instanceof ValueAnimator
+                        ? ((ValueAnimator) anim).getAnimatedFraction() > SUCCESS_TRANSITION_PROGRESS
+                        : true);
+            }
+        }
+    }
+
+    private static class RunnableSuccessListener extends AnimationSuccessListener {
+
+        private final Runnable mRunnable;
+
+        private RunnableSuccessListener(Runnable r) {
+            mRunnable = r;
+        }
+
+        @Override
+        public void onAnimationSuccess(Animator animator) {
+            mRunnable.run();
+        }
+    }
+}
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 8057475..01f7de6 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -15,13 +15,11 @@
  */
 package com.android.launcher3.anim;
 
-import static com.android.launcher3.LauncherAnimUtils.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR;
 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;
@@ -182,32 +180,6 @@
         if (mProgressAnimator == null) {
             mProgressAnimator = ValueAnimator.ofFloat(0, 1);
         }
-        mProgressAnimator.addListener(new EndStateCallbackWrapper(listener));
-    }
-
-    private static class EndStateCallbackWrapper extends AnimatorListenerAdapter {
-
-        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);
-            }
-        }
+        mProgressAnimator.addListener(AnimatorListeners.forEndCallback(listener));
     }
 }
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index 14ef2dc..03b6853 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -129,14 +129,14 @@
     }
 
     /**
-     * @see #goToState(STATE_TYPE, boolean, Runnable)
+     * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
      */
     public void goToState(STATE_TYPE state) {
         goToState(state, shouldAnimateStateChange());
     }
 
     /**
-     * @see #goToState(STATE_TYPE, boolean, Runnable)
+     * @see #goToState(STATE_TYPE, boolean, AnimatorListener)
      */
     public void goToState(STATE_TYPE state, boolean animated) {
         goToState(state, animated, 0, null);
@@ -149,15 +149,15 @@
      *                true otherwise
      * @paras onCompleteRunnable any action to perform at the end of the transition, of null.
      */
-    public void goToState(STATE_TYPE state, boolean animated, Runnable onCompleteRunnable) {
-        goToState(state, animated, 0, onCompleteRunnable);
+    public void goToState(STATE_TYPE state, boolean animated, AnimatorListener listener) {
+        goToState(state, animated, 0, listener);
     }
 
     /**
      * Changes the Launcher state to the provided state after the given delay.
      */
-    public void goToState(STATE_TYPE state, long delay, Runnable onCompleteRunnable) {
-        goToState(state, true, delay, onCompleteRunnable);
+    public void goToState(STATE_TYPE state, long delay, AnimatorListener listener) {
+        goToState(state, true, delay, listener);
     }
 
     /**
@@ -187,21 +187,20 @@
         }
     }
 
-    private void goToState(STATE_TYPE state, boolean animated, long delay,
-            final Runnable onCompleteRunnable) {
+    private void goToState(
+            STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
         animated &= areAnimatorsEnabled();
         if (mActivity.isInState(state)) {
             if (mConfig.currentAnimation == null) {
                 // Run any queued runnable
-                if (onCompleteRunnable != null) {
-                    onCompleteRunnable.run();
+                if (listener != null) {
+                    listener.onAnimationEnd(null);
                 }
                 return;
             } else if (!mConfig.userControlled && animated && mConfig.targetState == state) {
                 // We are running the same animation as requested
-                if (onCompleteRunnable != null) {
-                    mConfig.currentAnimation.addListener(
-                            AnimationSuccessListener.forRunnable(onCompleteRunnable));
+                if (listener != null) {
+                    mConfig.currentAnimation.addListener(listener);
                 }
                 return;
             }
@@ -221,8 +220,8 @@
             onStateTransitionEnd(state);
 
             // Run any queued runnable
-            if (onCompleteRunnable != null) {
-                onCompleteRunnable.run();
+            if (listener != null) {
+                listener.onAnimationEnd(null);
             }
             return;
         }
@@ -233,16 +232,16 @@
             int startChangeId = mConfig.changeId;
             mUiHandler.postDelayed(() -> {
                 if (mConfig.changeId == startChangeId) {
-                    goToStateAnimated(state, fromState, onCompleteRunnable);
+                    goToStateAnimated(state, fromState, listener);
                 }
             }, delay);
         } else {
-            goToStateAnimated(state, fromState, onCompleteRunnable);
+            goToStateAnimated(state, fromState, listener);
         }
     }
 
     private void goToStateAnimated(STATE_TYPE state, STATE_TYPE fromState,
-            Runnable onCompleteRunnable) {
+            AnimatorListener listener) {
         // Since state mBaseState can be reached from multiple states, just assume that the
         // transition plays in reverse and use the same duration as previous state.
         mConfig.duration = state == mBaseState
@@ -250,8 +249,8 @@
                 : state.getTransitionDuration(mActivity);
         prepareForAtomicAnimation(fromState, state, mConfig);
         AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).buildAnim();
-        if (onCompleteRunnable != null) {
-            animation.addListener(AnimationSuccessListener.forRunnable(onCompleteRunnable));
+        if (listener != null) {
+            animation.addListener(listener);
         }
         mUiHandler.post(new StartAnimRunnable(animation));
     }