Moving animation config so a separate class

Using PendingAnimation for animation builder.
This will allow us to easily add SpringAnimation to stateAnimation

Change-Id: I8d88489a5da6fc85747ef9be7c13858b441cd28a
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
index 95198b8..9afa862 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/LauncherAppTransitionManagerImpl.java
@@ -20,15 +20,14 @@
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch;
 import static com.android.quickstep.TaskViewUtils.getRecentsWindowAnimator;
 
@@ -47,9 +46,9 @@
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.SpringAnimationBuilder;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.views.RecentsView;
@@ -210,17 +209,20 @@
                         .setValues(values)
                         .build(mLauncher);
             case INDEX_PAUSE_TO_OVERVIEW_ANIM: {
-                AnimatorSetBuilder builder = new AnimatorSetBuilder();
-                builder.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
-                builder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
+                StateAnimationConfig config = new StateAnimationConfig();
+                config.duration = ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW;
+
+                config.setInterpolator(ANIM_VERTICAL_PROGRESS, OVERSHOOT_1_2);
+                config.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_3);
                 if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
-                    builder.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
-                    builder.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
+                    config.setInterpolator(ANIM_HOTSEAT_SCALE, OVERSHOOT_1_2);
+                    config.setInterpolator(ANIM_HOTSEAT_TRANSLATE, OVERSHOOT_1_2);
                 }
+
+
                 LauncherStateManager stateManager = mLauncher.getStateManager();
                 return stateManager.createAtomicAnimation(
-                        stateManager.getCurrentStableState(), OVERVIEW, builder,
-                        ANIM_ALL_COMPONENTS, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
+                        stateManager.getCurrentStableState(), OVERVIEW, config);
             }
 
             default:
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 65aaf22..549187f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -20,7 +20,6 @@
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 
-import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
 import android.os.Build;
 import android.util.FloatProperty;
@@ -29,10 +28,10 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.quickstep.views.ClearAllButton;
 import com.android.quickstep.views.LauncherRecentsView;
 import com.android.quickstep.views.RecentsView;
@@ -61,25 +60,22 @@
     }
 
     @Override
-    void setStateWithAnimationInternal(@NonNull final LauncherState toState,
-            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
-        super.setStateWithAnimationInternal(toState, builder, config);
+    void setStateWithAnimationInternal(@NonNull LauncherState toState,
+            @NonNull StateAnimationConfig config, @NonNull PendingAnimation builder) {
+        super.setStateWithAnimationInternal(toState, config, builder);
 
-        ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1).setDuration(config.duration);
         if (toState.overviewUi) {
             // While animating into recents, update the visible task data as needed
-            updateAnim.addUpdateListener(valueAnimator -> mRecentsView.loadVisibleTaskData());
+            builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
             mRecentsView.updateEmptyMessage();
         } else {
-            updateAnim.addListener(
+            builder.getAnim().addListener(
                     AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
-        builder.play(updateAnim);
 
-        PropertySetter propertySetter = config.getPropertySetter(builder);
-        setAlphas(propertySetter, toState.getVisibleElements(mLauncher));
-        float fullscreenProgress = toState.getOverviewFullscreenProgress();
-        propertySetter.setFloat(mRecentsView, FULLSCREEN_PROGRESS, fullscreenProgress, LINEAR);
+        setAlphas(builder, toState.getVisibleElements(mLauncher));
+        builder.setFloat(mRecentsView, FULLSCREEN_PROGRESS,
+                toState.getOverviewFullscreenProgress(), LINEAR);
     }
 
     private void setAlphas(PropertySetter propertySetter, int visibleElements) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
index 427206a..8087611 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewPeekState.java
@@ -14,17 +14,18 @@
  * limitations under the License.
  */
 package com.android.launcher3.uioverrides.states;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
+
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.R;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.states.StateAnimationConfig;
 
 public class OverviewPeekState extends OverviewState {
     public OverviewPeekState(int id) {
@@ -41,11 +42,11 @@
 
     @Override
     public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
-            AnimatorSetBuilder builder) {
+            StateAnimationConfig config) {
         if (this == OVERVIEW_PEEK && fromState == NORMAL) {
-            builder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
-            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
-            builder.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
+            config.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
+            config.setInterpolator(ANIM_OVERVIEW_SCRIM_FADE, FAST_OUT_SLOW_IN);
         }
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index bfbb630..6a34917 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -18,13 +18,6 @@
 import static android.view.View.VISIBLE;
 
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
@@ -32,6 +25,13 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.quickstep.SysUINavigationMode.Mode.NO_BUTTON;
 import static com.android.quickstep.SysUINavigationMode.removeShelfFromOverview;
 
@@ -48,8 +48,8 @@
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.allapps.DiscoveryBounce;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
@@ -225,14 +225,14 @@
 
     @Override
     public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
-            AnimatorSetBuilder builder) {
+            StateAnimationConfig config) {
         if ((fromState == NORMAL || fromState == HINT_STATE) && this == OVERVIEW) {
             if (SysUINavigationMode.getMode(launcher) == NO_BUTTON) {
-                builder.setInterpolator(ANIM_WORKSPACE_SCALE,
+                config.setInterpolator(ANIM_WORKSPACE_SCALE,
                         fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
-                builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
+                config.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
             } else {
-                builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
+                config.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
 
                 // Scale up the recents, if it is not coming from the side
                 RecentsView overview = launcher.getOverviewPanel();
@@ -240,15 +240,15 @@
                     SCALE_PROPERTY.set(overview, RECENTS_PREPARE_SCALE);
                 }
             }
-            builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
-            builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
+            config.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
+            config.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
             Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
                     && removeShelfFromOverview(launcher)
                     ? OVERSHOOT_1_2
                     : OVERSHOOT_1_7;
-            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
-            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
-            builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
+            config.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index b347013..785a480 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -21,16 +21,16 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.OVERVIEW_PEEK;
-import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
@@ -44,10 +44,10 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.SystemUiProxy;
@@ -105,8 +105,12 @@
         LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
         LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
         long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
-        mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
-                new AnimatorSetBuilder(), PLAY_ATOMIC_OVERVIEW_PEEK, peekDuration);
+
+        StateAnimationConfig config = new StateAnimationConfig();
+        config.duration = peekDuration;
+        config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
+        mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(
+                fromState, toState, config);
         mPeekAnim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -130,10 +134,10 @@
     }
 
     @Override
-    protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
-            LauncherState toState) {
+    protected StateAnimationConfig getConfigForStates(
+            LauncherState fromState, LauncherState toState) {
         if (fromState == NORMAL && toState == ALL_APPS) {
-            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            StateAnimationConfig builder = new StateAnimationConfig();
             // Fade in prediction icons quickly, then rest of all apps after reaching overview.
             float progressToReachOverview = NORMAL.getVerticalProgress(mLauncher)
                     - OVERVIEW.getVerticalProgress(mLauncher);
@@ -152,7 +156,7 @@
             builder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_3);
             return builder;
         } else if (fromState == ALL_APPS && toState == NORMAL) {
-            AnimatorSetBuilder builder = new AnimatorSetBuilder();
+            StateAnimationConfig builder = new StateAnimationConfig();
             // Keep all apps/predictions opaque until the very end of the transition.
             float progressToReachOverview = OVERVIEW.getVerticalProgress(mLauncher);
             builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(
@@ -165,7 +169,7 @@
                     1));
             return builder;
         }
-        return super.getAnimatorSetBuilderForStates(fromState, toState);
+        return super.getConfigForStates(fromState, toState);
     }
 
     @Override
@@ -211,9 +215,11 @@
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 if (mCancelled) {
-                    mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(mFromState,
-                            mToState, new AnimatorSetBuilder(), PLAY_ATOMIC_OVERVIEW_PEEK,
-                            PEEK_OUT_ANIM_DURATION);
+                    StateAnimationConfig config = new StateAnimationConfig();
+                    config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
+                    config.duration = PEEK_OUT_ANIM_DURATION;
+                    mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(
+                            mFromState, mToState, config);
                     mPeekAnim.start();
                 }
                 mAtomicAnim = null;
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
index a0ca886..77118d5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NavBarToHomeTouchController.java
@@ -15,8 +15,7 @@
  */
 package com.android.launcher3.uioverrides.touchcontrollers;
 
-import static android.view.View.TRANSLATION_X;
-
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
@@ -26,9 +25,6 @@
 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.Animator;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
@@ -36,16 +32,16 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
 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.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -132,46 +128,37 @@
 
     private void initCurrentAnimation() {
         long accuracy = (long) (getShiftRange() * 2);
-        final AnimatorSet anim = new AnimatorSet();
+        final PendingAnimation builder = new PendingAnimation(accuracy);
         if (mStartState == OVERVIEW) {
             RecentsView recentsView = mLauncher.getOverviewPanel();
             float pullbackDist = mPullbackDistance;
             if (!recentsView.isRtl()) {
                 pullbackDist = -pullbackDist;
             }
-            ObjectAnimator pullback = ObjectAnimator.ofFloat(recentsView, TRANSLATION_X,
-                    pullbackDist);
-            pullback.setInterpolator(PULLBACK_INTERPOLATOR);
+
+            builder.setFloat(recentsView, VIEW_TRANSLATE_X, pullbackDist, PULLBACK_INTERPOLATOR);
             if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-                pullback.addUpdateListener(
-                        valueAnimator -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
+                builder.addOnFrameCallback(
+                        () -> recentsView.redrawLiveTile(false /* mightNeedToRefill */));
             }
-            anim.play(pullback);
         } else if (mStartState == ALL_APPS) {
-            AnimatorSetBuilder builder = new AnimatorSetBuilder();
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            Animator allAppsProgress = ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS,
-                    -mPullbackDistance / allAppsController.getShiftRange());
-            allAppsProgress.setInterpolator(PULLBACK_INTERPOLATOR);
-            builder.play(allAppsProgress);
+            builder.setFloat(allAppsController, ALL_APPS_PROGRESS,
+                    -mPullbackDistance / allAppsController.getShiftRange(), PULLBACK_INTERPOLATOR);
+
             // Slightly fade out all apps content to further distinguish from scrolling.
-            builder.setInterpolator(AnimatorSetBuilder.ANIM_ALL_APPS_FADE, Interpolators
-                    .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
-            AnimationConfig config = new AnimationConfig();
+            StateAnimationConfig config = new StateAnimationConfig();
             config.duration = accuracy;
-            allAppsController.setAlphas(mEndState.getVisibleElements(mLauncher), config, builder);
-            anim.play(builder.build());
+            config.setInterpolator(StateAnimationConfig.ANIM_ALL_APPS_FADE, Interpolators
+                    .mapToProgress(PULLBACK_INTERPOLATOR, 0, 0.5f));
+
+            allAppsController.setAlphas(mEndState, config, builder);
         }
         AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher);
         if (topView != null) {
-            Animator hintCloseAnim = topView.createHintCloseAnim(mPullbackDistance);
-            if (hintCloseAnim != null) {
-                hintCloseAnim.setInterpolator(PULLBACK_INTERPOLATOR);
-                anim.play(hintCloseAnim);
-            }
+            topView.addHintCloseAnim(mPullbackDistance, PULLBACK_INTERPOLATOR, builder);
         }
-        anim.setDuration(accuracy);
-        mCurrentAnimation = AnimatorPlaybackController.wrap(anim, accuracy)
+        mCurrentAnimation = builder.createPlaybackController()
                 .setOnCancelRunnable(this::clearState);
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
index 7cebabe..064133c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -19,9 +19,9 @@
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
 import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
 
 import android.animation.AnimatorSet;
@@ -34,7 +34,7 @@
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
 import com.android.launcher3.util.VibratorWrapper;
 import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -171,11 +171,11 @@
 
                 // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
                 stateManager.cancelAnimation();
-                AnimatorSetBuilder builder = new AnimatorSetBuilder();
-                long duration = OVERVIEW.getTransitionDuration(mLauncher);
+                StateAnimationConfig config = new StateAnimationConfig();
+                config.duration = OVERVIEW.getTransitionDuration(mLauncher);
+                config.animFlags = PLAY_ATOMIC_OVERVIEW_PEEK;
                 AnimatorSet anim = stateManager.createAtomicAnimation(
-                        stateManager.getState(), NORMAL, builder,
-                        PLAY_ATOMIC_OVERVIEW_PEEK, duration);
+                        stateManager.getState(), NORMAL, config);
                 anim.addListener(AnimationSuccessListener.forRunnable(
                         () -> onSwipeInteractionCompleted(NORMAL, Touch.SWIPE)));
                 anim.start();
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 81a6d9b..c92a872 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -21,18 +21,18 @@
 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.LauncherStateManager.ANIM_ALL_COMPONENTS;
-import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
 import static com.android.launcher3.anim.AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.ACCEL_0_75;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_5;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
+import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_RIGHT;
 import static com.android.launcher3.touch.BothAxesSwipeDetector.DIRECTION_UP;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
@@ -55,15 +55,13 @@
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.BaseSwipeDetector;
 import com.android.launcher3.touch.BothAxesSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -192,11 +190,9 @@
         ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
         if (shelfState == PEEK) {
             // Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
-            AnimatorSetBuilder builder = new AnimatorSetBuilder();
             AllAppsTransitionController allAppsController = mLauncher.getAllAppsController();
-            allAppsController.setAlphas(NORMAL.getVisibleElements(mLauncher),
-                    new AnimationConfig(), builder);
-            builder.build().setDuration(0).start();
+            allAppsController.setAlphas(
+                    NORMAL, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
 
             if ((OVERVIEW.getVisibleElements(mLauncher) & HOTSEAT_ICONS) != 0) {
                 // Hotseat was hidden, but we need it visible when peeking.
@@ -209,12 +205,12 @@
 
     private void setupAnimators() {
         // Animate the non-overview components (e.g. workspace, shelf) out of the way.
-        AnimatorSetBuilder nonOverviewBuilder = new AnimatorSetBuilder();
+        StateAnimationConfig nonOverviewBuilder = new StateAnimationConfig();
         nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_FADE, FADE_OUT_INTERPOLATOR);
         nonOverviewBuilder.setInterpolator(ANIM_ALL_APPS_FADE, FADE_OUT_INTERPOLATOR);
         nonOverviewBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, TRANSLATE_OUT_INTERPOLATOR);
         nonOverviewBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, TRANSLATE_OUT_INTERPOLATOR);
-        updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder, ANIM_ALL_COMPONENTS);
+        updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder);
         mNonOverviewAnim.dispatchOnStart();
 
         if (mRecentsView.getTaskViewCount() == 0) {
@@ -230,11 +226,12 @@
     }
 
     /** Create state animation to control non-overview components. */
-    private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
-            @AnimationFlags int animComponents) {
-        long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
-        mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
-                builder, accuracy, this::clearState, animComponents | SKIP_OVERVIEW);
+    private void updateNonOverviewAnim(LauncherState toState, StateAnimationConfig config) {
+        config.duration = (long) (Math.max(mXRange, mYRange) * 2);
+        config.animFlags = config.animFlags | SKIP_OVERVIEW;
+        mNonOverviewAnim = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(toState, config)
+                .setOnCancelRunnable(this::clearState);
     }
 
     private void setupOverviewAnimators() {
@@ -419,8 +416,10 @@
         if (flingUpToNormal && !mIsHomeScreenVisible) {
             // We are flinging to home while workspace is invisible, run the same staggered
             // animation as from an app.
+            StateAnimationConfig config = new StateAnimationConfig();
             // Update mNonOverviewAnim to do nothing so it doesn't interfere.
-            updateNonOverviewAnim(targetState, new AnimatorSetBuilder(), 0 /* animComponents */);
+            config.animFlags = 0;
+            updateNonOverviewAnim(targetState, config);
             nonOverviewAnim = mNonOverviewAnim.getAnimationPlayer();
 
             new StaggeredWorkspaceAnim(mLauncher, velocity.y, false /* animateOverviewScrim */)
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
index 8de1b3a..9e53959 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/QuickSwitchTouchController.java
@@ -17,18 +17,17 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
 import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
 import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
@@ -41,7 +40,7 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
@@ -108,30 +107,30 @@
 
     @Override
     protected float initCurrentAnimation(int animComponents) {
-        AnimatorSetBuilder animatorSetBuilder = new AnimatorSetBuilder();
-        setupInterpolators(animatorSetBuilder);
-        long accuracy = (long) (getShiftRange() * 2);
-        mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
-                animatorSetBuilder, accuracy, this::clearState, ANIM_ALL_COMPONENTS);
-        mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator -> {
-            updateFullscreenProgress((Float) valueAnimator.getAnimatedValue());
-        });
+        StateAnimationConfig config = new StateAnimationConfig();
+        setupInterpolators(config);
+        config.duration = (long) (getShiftRange() * 2);
+        mCurrentAnimation = mLauncher.getStateManager()
+                .createAnimationToNewWorkspace(mToState, config)
+                .setOnCancelRunnable(this::clearState);
+        mCurrentAnimation.getAnimationPlayer().addUpdateListener(valueAnimator ->
+                updateFullscreenProgress((Float) valueAnimator.getAnimatedValue()));
         return 1 / getShiftRange();
     }
 
-    private void setupInterpolators(AnimatorSetBuilder animatorSetBuilder) {
-        animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_2);
-        animatorSetBuilder.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_2);
+    private void setupInterpolators(StateAnimationConfig stateAnimationConfig) {
+        stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_FADE, DEACCEL_2);
+        stateAnimationConfig.setInterpolator(ANIM_ALL_APPS_FADE, DEACCEL_2);
         if (SysUINavigationMode.getMode(mLauncher) == Mode.NO_BUTTON) {
             // Overview lives to the left of workspace, so translate down later than over
-            animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL_2);
-            animatorSetBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCEL_2);
-            animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_SCALE, ACCEL_2);
-            animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCEL_2);
-            animatorSetBuilder.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
+            stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL_2);
+            stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, ACCEL_2);
+            stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_SCALE, ACCEL_2);
+            stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, ACCEL_2);
+            stateAnimationConfig.setInterpolator(ANIM_OVERVIEW_FADE, INSTANT);
         } else {
-            animatorSetBuilder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, LINEAR);
-            animatorSetBuilder.setInterpolator(ANIM_VERTICAL_PROGRESS, LINEAR);
+            stateAnimationConfig.setInterpolator(ANIM_WORKSPACE_TRANSLATE, LINEAR);
+            stateAnimationConfig.setInterpolator(ANIM_VERTICAL_PROGRESS, LINEAR);
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index cc58fcf..b0125a8 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -214,7 +214,7 @@
         if (mCurrentAnimation != null) {
             mCurrentAnimation.setOnCancelRunnable(null);
         }
-        mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation, maxDuration)
+        mCurrentAnimation = mPendingAnimation.createPlaybackController()
                 .setOnCancelRunnable(this::clearState);
         onUserControlledAnimationCreated(mCurrentAnimation);
         mCurrentAnimation.getTarget().addListener(this);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index 7e17fbf..43c5cb5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -18,9 +18,10 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
-import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -33,14 +34,13 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.R;
 import com.android.launcher3.ShortcutAndWidgetContainer;
 import com.android.launcher3.Workspace;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.DynamicResource;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.plugins.ResourceProvider;
@@ -143,11 +143,11 @@
      * Setup workspace with 0 duration to prepare for our staggered animation.
      */
     private void prepareToAnimate(Launcher launcher) {
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        StateAnimationConfig config = new StateAnimationConfig();
+        config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW;
+        config.duration = 0;
         // setRecentsAttachedToAppWindow() will animate recents out.
-        launcher.getStateManager().createAtomicAnimation(
-                BACKGROUND_APP, NORMAL, builder, ANIM_ALL_COMPONENTS | SKIP_OVERVIEW, 0);
-        builder.build().start();
+        launcher.getStateManager().createAtomicAnimation(BACKGROUND_APP, NORMAL, config).start();
 
         // Stop scrolling so that it doesn't interfere with the translation offscreen.
         launcher.<RecentsView>getOverviewPanel().getScroller().forceFinished(true);
@@ -197,16 +197,12 @@
     }
 
     private void addScrimAnimationForState(Launcher launcher, LauncherState state, long duration) {
-        AnimatorSetBuilder scrimAnimBuilder = new AnimatorSetBuilder();
-        AnimationConfig scrimAnimConfig = new AnimationConfig();
-        scrimAnimConfig.duration = duration;
-        PropertySetter scrimPropertySetter = scrimAnimConfig.getPropertySetter(scrimAnimBuilder);
-        launcher.getWorkspace().getStateTransitionAnimation().setScrim(scrimPropertySetter, state);
-        mAnimators.play(scrimAnimBuilder.build());
-        Animator fadeOverviewScrim = ObjectAnimator.ofFloat(
-                launcher.getDragLayer().getOverviewScrim(), OverviewScrim.SCRIM_PROGRESS,
-                state.getOverviewScrimAlpha(launcher));
-        fadeOverviewScrim.setDuration(duration);
-        mAnimators.play(fadeOverviewScrim);
+        PendingAnimation builder = new PendingAnimation(duration, mAnimators);
+        launcher.getWorkspace().getStateTransitionAnimation().setScrim(builder, state);
+        builder.setFloat(
+                launcher.getDragLayer().getOverviewScrim(),
+                OverviewScrim.SCRIM_PROGRESS,
+                state.getOverviewScrimAlpha(launcher),
+                ACCEL_DEACCEL);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 34d8adf..2f45af1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -1248,7 +1248,7 @@
         if (mPendingAnimation != null) {
             mPendingAnimation.finish(false, Touch.SWIPE);
         }
-        PendingAnimation anim = new PendingAnimation();
+        PendingAnimation anim = new PendingAnimation(duration);
 
         int count = getPageCount();
         if (count == 0) {
@@ -1364,7 +1364,7 @@
         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
-        PendingAnimation anim = new PendingAnimation();
+        PendingAnimation anim = new PendingAnimation(duration);
 
         int count = getTaskViewCount();
         for (int i = 0; i < count; i++) {
@@ -1398,8 +1398,7 @@
     }
 
     private void runDismissAnimation(PendingAnimation pendingAnim) {
-        AnimatorPlaybackController controller =
-                AnimatorPlaybackController.wrap(pendingAnim, DISMISS_TASK_DURATION);
+        AnimatorPlaybackController controller = pendingAnim.createPlaybackController();
         controller.dispatchOnStart();
         controller.setEndAction(() -> pendingAnim.finish(true, Touch.SWIPE));
         controller.getAnimationPlayer().setInterpolator(FAST_OUT_SLOW_IN);
@@ -1724,7 +1723,7 @@
 
         int count = getTaskViewCount();
         if (count == 0) {
-            return new PendingAnimation();
+            return new PendingAnimation(duration);
         }
 
         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
@@ -1763,10 +1762,9 @@
             anim.play(backgroundBlur);
         }
         anim.play(progressAnim);
-        anim.setDuration(duration)
-                .setInterpolator(interpolator);
+        anim.setDuration(duration).setInterpolator(interpolator);
 
-        mPendingAnimation = new PendingAnimation();
+        mPendingAnimation = new PendingAnimation(duration);
         mPendingAnimation.add(anim);
         mPendingAnimation.addEndListener((endState) -> {
             if (endState.isSuccess) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 0e1640e..c94b56c 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -285,8 +285,7 @@
     public AnimatorPlaybackController createLaunchAnimationForRunningTask() {
         final PendingAnimation pendingAnimation = getRecentsView().createTaskLaunchAnimation(
                 this, RECENTS_LAUNCH_DURATION, TOUCH_RESPONSE_INTERPOLATOR);
-        AnimatorPlaybackController currentAnimation =
-                AnimatorPlaybackController.wrap(pendingAnimation, RECENTS_LAUNCH_DURATION);
+        AnimatorPlaybackController currentAnimation = pendingAnimation.createPlaybackController();
         currentAnimation.setEndAction(() -> {
             pendingAnimation.finish(true, Touch.SWIPE);
             launchTask(false);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
index 671aab0..e82a504 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackButtonAlphaHandler.java
@@ -16,19 +16,23 @@
 
 package com.android.launcher3.uioverrides;
 
-import android.animation.ValueAnimator;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.AnimatedFloat.VALUE;
 
 import com.android.launcher3.BaseQuickstepLauncher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SystemUiProxy;
 
 public class BackButtonAlphaHandler implements LauncherStateManager.StateHandler {
 
     private final BaseQuickstepLauncher mLauncher;
+    private final AnimatedFloat mBackAlpha = new AnimatedFloat(this::updateBackAlpha);
 
     public BackButtonAlphaHandler(BaseQuickstepLauncher launcher) {
         mLauncher = launcher;
@@ -38,8 +42,8 @@
     public void setState(LauncherState state) { }
 
     @Override
-    public void setStateWithAnimation(LauncherState toState,
-            AnimatorSetBuilder builder, LauncherStateManager.AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
+            PendingAnimation animation) {
         if (config.onlyPlayAtomicComponent()) {
             return;
         }
@@ -51,17 +55,12 @@
             return;
         }
 
-        float fromAlpha = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
-        float toAlpha = toState.hideBackButton ? 0 : 1;
-        if (Float.compare(fromAlpha, toAlpha) != 0) {
-            ValueAnimator anim = ValueAnimator.ofFloat(fromAlpha, toAlpha);
-            anim.setDuration(config.duration);
-            anim.addUpdateListener(valueAnimator -> {
-                final float alpha = (float) valueAnimator.getAnimatedValue();
-                UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
-                        BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, alpha, false /* animate */);
-            });
-            builder.play(anim);
-        }
+        mBackAlpha.value = SystemUiProxy.INSTANCE.get(mLauncher).getLastBackButtonAlpha();
+        animation.setFloat(mBackAlpha, VALUE, toState.hideBackButton ? 0 : 1, LINEAR);
+    }
+
+    private void updateBackAlpha() {
+        UiThreadHelper.setBackButtonAlphaAsync(mLauncher,
+                BaseQuickstepLauncher.SET_BACK_BUTTON_ALPHA, mBackAlpha.value, false /* animate */);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java b/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
index 022a5f7..513310e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
@@ -26,8 +26,8 @@
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.SurfaceControlCompat;
 import com.android.systemui.shared.system.TransactionCompat;
@@ -133,16 +133,15 @@
     }
 
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
-            LauncherStateManager.AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
+            PendingAnimation animation) {
         if (mSurface == null || config.onlyPlayAtomicComponent()) {
             return;
         }
 
         int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
         if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
-            PropertySetter propertySetter = config.getPropertySetter(builder);
-            propertySetter.setInt(this, BACKGROUND_BLUR, toBackgroundBlurRadius, LINEAR);
+            animation.setInt(this, BACKGROUND_BLUR, toBackgroundBlurRadius, LINEAR);
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 94e67f0..03454f7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -20,17 +20,17 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
-import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_PEEK;
-import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_SCALE;
-import static com.android.launcher3.LauncherStateManager.SKIP_OVERVIEW;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCRIM_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_Y;
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE_IN_OUT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.graphics.Scrim.SCRIM_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
 
 import android.util.FloatProperty;
 import android.view.View;
@@ -41,11 +41,10 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.graphics.OverviewScrim;
+import com.android.launcher3.states.StateAnimationConfig;
 
 /**
  * State handler for recents view. Manages UI changes and animations for recents view based off the
@@ -86,8 +85,8 @@
     }
 
     @Override
-    public final void setStateWithAnimation(@NonNull final LauncherState toState,
-            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
+    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
+            PendingAnimation builder) {
         if (!config.hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_PEEK | PLAY_ATOMIC_OVERVIEW_SCALE)) {
             // The entire recents animation is played atomically.
             return;
@@ -95,25 +94,24 @@
         if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
             return;
         }
-        setStateWithAnimationInternal(toState, builder, config);
+        setStateWithAnimationInternal(toState, config, builder);
     }
 
     /**
      * Core logic for animating the recents view UI.
      *
      * @param toState state to animate to
-     * @param builder animator set builder
      * @param config current animation config
+     * @param setter animator set builder
      */
     void setStateWithAnimationInternal(@NonNull final LauncherState toState,
-            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
-        PropertySetter setter = config.getPropertySetter(builder);
+            @NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
         ScaleAndTranslation scaleAndTranslation = toState.getOverviewScaleAndTranslation(mLauncher);
-        Interpolator scaleInterpolator = builder.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
+        Interpolator scaleInterpolator = config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR);
         setter.setFloat(mRecentsView, SCALE_PROPERTY, scaleAndTranslation.scale, scaleInterpolator);
-        Interpolator translateXInterpolator = builder.getInterpolator(
+        Interpolator translateXInterpolator = config.getInterpolator(
                 ANIM_OVERVIEW_TRANSLATE_X, LINEAR);
-        Interpolator translateYInterpolator = builder.getInterpolator(
+        Interpolator translateYInterpolator = config.getInterpolator(
                 ANIM_OVERVIEW_TRANSLATE_Y, LINEAR);
         float translationX = scaleAndTranslation.translationX;
         if (mRecentsView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
@@ -123,14 +121,14 @@
         setter.setFloat(mRecentsView, VIEW_TRANSLATE_Y, scaleAndTranslation.translationY,
                 translateYInterpolator);
         setter.setFloat(mRecentsView, getContentAlphaProperty(), toState.overviewUi ? 1 : 0,
-                builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+                config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
         OverviewScrim scrim = mLauncher.getDragLayer().getOverviewScrim();
         setter.setFloat(scrim, SCRIM_PROGRESS, toState.getOverviewScrimAlpha(mLauncher),
-                builder.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
+                config.getInterpolator(ANIM_OVERVIEW_SCRIM_FADE, LINEAR));
         if (mActionsView != null) {
             setter.setFloat(mActionsView, VIEW_TRANSLATE_X, translationX, translateXInterpolator);
             setter.setFloat(mActionsView, VIEW_ALPHA, toState.overviewUi ? 1 : 0,
-                    builder.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
+                    config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index 3d0fc56..bef191e 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
@@ -9,7 +9,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationFlags;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index a060d64..cc3fd97 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -19,13 +19,13 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 
 import android.animation.TimeInterpolator;
@@ -37,11 +37,10 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 import com.android.launcher3.touch.AbstractStateChangeTouchController;
 import com.android.launcher3.touch.SingleAxisSwipeDetector;
 import com.android.launcher3.uioverrides.states.OverviewState;
@@ -148,16 +147,16 @@
         return isTouchOverHotseat(mLauncher, ev) ? ContainerType.HOTSEAT : ContainerType.WORKSPACE;
     }
 
-    private AnimatorSetBuilder getNormalToOverviewAnimation() {
+    private StateAnimationConfig getNormalToOverviewAnimation() {
         mAllAppsInterpolatorWrapper.baseInterpolator = LINEAR;
 
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        StateAnimationConfig builder = new StateAnimationConfig();
         builder.setInterpolator(ANIM_VERTICAL_PROGRESS, mAllAppsInterpolatorWrapper);
         return builder;
     }
 
-    public static AnimatorSetBuilder getOverviewToAllAppsAnimation() {
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+    private static StateAnimationConfig getOverviewToAllAppsAnimation() {
+        StateAnimationConfig builder = new StateAnimationConfig();
         builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
                 0, ALL_APPS_CONTENT_FADE_THRESHOLD));
         builder.setInterpolator(ANIM_OVERVIEW_FADE, Interpolators.clampToProgress(DEACCEL,
@@ -165,8 +164,8 @@
         return builder;
     }
 
-    private AnimatorSetBuilder getAllAppsToOverviewAnimation() {
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+    private StateAnimationConfig getAllAppsToOverviewAnimation() {
+        StateAnimationConfig builder = new StateAnimationConfig();
         builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
                 1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
         builder.setInterpolator(ANIM_OVERVIEW_FADE, Interpolators.clampToProgress(ACCEL,
@@ -174,41 +173,42 @@
         return builder;
     }
 
-    private AnimatorSetBuilder getNormalToAllAppsAnimation() {
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+    private StateAnimationConfig getNormalToAllAppsAnimation() {
+        StateAnimationConfig builder = new StateAnimationConfig();
         builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
                 0, ALL_APPS_CONTENT_FADE_THRESHOLD));
         return builder;
     }
 
-    private AnimatorSetBuilder getAllAppsToNormalAnimation() {
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+    private StateAnimationConfig getAllAppsToNormalAnimation() {
+        StateAnimationConfig builder = new StateAnimationConfig();
         builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
                 1 - ALL_APPS_CONTENT_FADE_THRESHOLD, 1));
         return builder;
     }
 
     @Override
-    protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
-            LauncherState toState) {
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+    protected StateAnimationConfig getConfigForStates(
+            LauncherState fromState, LauncherState toState) {
+        final StateAnimationConfig config;
         if (fromState == NORMAL && toState == OVERVIEW) {
-            builder = getNormalToOverviewAnimation();
+            config = getNormalToOverviewAnimation();
         } else if (fromState == OVERVIEW && toState == ALL_APPS) {
-            builder = getOverviewToAllAppsAnimation();
+            config = getOverviewToAllAppsAnimation();
         } else if (fromState == ALL_APPS && toState == OVERVIEW) {
-            builder = getAllAppsToOverviewAnimation();
+            config = getAllAppsToOverviewAnimation();
         } else if (fromState == NORMAL && toState == ALL_APPS) {
-            builder = getNormalToAllAppsAnimation();
+            config = getNormalToAllAppsAnimation();
         } else if (fromState == ALL_APPS && toState == NORMAL) {
-            builder = getAllAppsToNormalAnimation();
+            config = getAllAppsToNormalAnimation();
+        }  else {
+            config = new StateAnimationConfig();
         }
-        return builder;
+        return config;
     }
 
     @Override
-    protected float initCurrentAnimation(@AnimationFlags int animComponents) {
-        animComponents = updateAnimComponentsOnReinit(animComponents);
+    protected float initCurrentAnimation(@AnimationFlags int animFlags) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
 
@@ -217,8 +217,10 @@
 
         float totalShift = endVerticalShift - startVerticalShift;
 
-        final AnimatorSetBuilder builder = totalShift == 0 ? new AnimatorSetBuilder()
-                : getAnimatorSetBuilderForStates(mFromState, mToState);
+        final StateAnimationConfig config = totalShift == 0 ? new StateAnimationConfig()
+                : getConfigForStates(mFromState, mToState);
+        config.animFlags = updateAnimComponentsOnReinit(animFlags);
+        config.duration = maxAccuracy;
 
         cancelPendingAnim();
 
@@ -232,15 +234,15 @@
                 cancelPendingAnim();
                 clearState();
             };
-            mCurrentAnimation = AnimatorPlaybackController.wrap(mPendingAnimation, maxAccuracy)
+            mCurrentAnimation = mPendingAnimation.createPlaybackController()
                     .setOnCancelRunnable(onCancelRunnable);
             mLauncher.getStateManager().setCurrentUserControlledAnimation(mCurrentAnimation);
             totalShift = LayoutUtils.getShelfTrackingDistance(mLauncher,
                     mLauncher.getDeviceProfile());
         } else {
             mCurrentAnimation = mLauncher.getStateManager()
-                    .createAnimationToNewWorkspace(mToState, builder, maxAccuracy, this::clearState,
-                            animComponents);
+                    .createAnimationToNewWorkspace(mToState, config)
+                    .setOnCancelRunnable(this::clearState);
         }
 
         if (totalShift == 0) {
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index af2cdc3..12b5fc1 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
 
-import android.animation.Animator;
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.util.AttributeSet;
@@ -30,11 +29,12 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
 import android.widget.LinearLayout;
 
 import androidx.annotation.IntDef;
-import androidx.annotation.Nullable;
 
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.launcher3.util.TouchController;
@@ -134,9 +134,8 @@
      * Creates a user-controlled animation to hint that the view will be closed if completed.
      * @param distanceToMove The max distance that elements should move from their starting point.
      */
-    public @Nullable Animator createHintCloseAnim(float distanceToMove) {
-        return null;
-    }
+    public void addHintCloseAnim(
+            float distanceToMove, Interpolator interpolator, PendingAnimation target) { }
 
     public abstract void logActionCommand(int command);
 
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 62b8927..df71f16 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -20,11 +20,6 @@
 import static android.view.View.VISIBLE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
 
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_TRANSLATE_X;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_2;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
@@ -32,6 +27,11 @@
 import static com.android.launcher3.anim.Interpolators.clampToProgress;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
 import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
 import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
@@ -46,9 +46,9 @@
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.states.HintState;
 import com.android.launcher3.states.SpringLoadedState;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.uioverrides.states.AllAppsState;
 import com.android.launcher3.uioverrides.states.OverviewState;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -325,13 +325,13 @@
      * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
      */
     public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
-            AnimatorSetBuilder builder) {
+            StateAnimationConfig config) {
         if (this == NORMAL && fromState == OVERVIEW) {
-            builder.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
-            builder.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
-            builder.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
-            builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
-            builder.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
+            config.setInterpolator(ANIM_WORKSPACE_SCALE, DEACCEL);
+            config.setInterpolator(ANIM_WORKSPACE_FADE, ACCEL);
+            config.setInterpolator(ANIM_OVERVIEW_SCALE, clampToProgress(ACCEL, 0, 0.9f));
+            config.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, ACCEL);
+            config.setInterpolator(ANIM_OVERVIEW_FADE, DEACCEL_1_7);
             Workspace workspace = launcher.getWorkspace();
 
             // Start from a higher workspace scale, but only if we're invisible so we don't jump.
@@ -363,7 +363,7 @@
             }
         } else if (this == NORMAL && fromState == OVERVIEW_PEEK) {
             // Keep fully visible until the very end (when overview is offscreen) to make invisible.
-            builder.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
+            config.setInterpolator(ANIM_OVERVIEW_FADE, t -> t < 1 ? 0 : 1);
         }
     }
 
diff --git a/src/com/android/launcher3/LauncherStateManager.java b/src/com/android/launcher3/LauncherStateManager.java
index 04134f2..24d0c41 100644
--- a/src/com/android/launcher3/LauncherStateManager.java
+++ b/src/com/android/launcher3/LauncherStateManager.java
@@ -17,26 +17,23 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
 
 import android.animation.Animator;
+import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.os.Handler;
 import android.os.Looper;
 
-import androidx.annotation.IntDef;
-
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.anim.PropertySetter.AnimatedPropertySetter;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 
 /**
@@ -84,28 +81,7 @@
 
     public static final String TAG = "StateManager";
 
-    // We separate the state animations into "atomic" and "non-atomic" components. The atomic
-    // components may be run atomically - that is, all at once, instead of user-controlled. However,
-    // atomic components are not restricted to this purpose; they can be user-controlled alongside
-    // non atomic components as well. Note that each gesture model has exactly one atomic component,
-    // PLAY_ATOMIC_OVERVIEW_SCALE *or* PLAY_ATOMIC_OVERVIEW_PEEK.
-    @IntDef(flag = true, value = {
-            PLAY_NON_ATOMIC,
-            PLAY_ATOMIC_OVERVIEW_SCALE,
-            PLAY_ATOMIC_OVERVIEW_PEEK,
-            SKIP_OVERVIEW,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface AnimationFlags {}
-    public static final int PLAY_NON_ATOMIC = 1 << 0;
-    public static final int PLAY_ATOMIC_OVERVIEW_SCALE = 1 << 1;
-    public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
-    public static final int SKIP_OVERVIEW = 1 << 3;
-
-    public static final int ANIM_ALL_COMPONENTS = PLAY_NON_ATOMIC | PLAY_ATOMIC_OVERVIEW_SCALE
-            | PLAY_ATOMIC_OVERVIEW_PEEK;
-
-    private final AnimationConfig mConfig = new AnimationConfig();
+    private final AnimationState mConfig = new AnimationState();
     private final Handler mUiHandler;
     private final Launcher mLauncher;
     private final ArrayList<StateListener> mListeners = new ArrayList<>();
@@ -140,7 +116,7 @@
         writer.println(prefix + "\tmCurrentStableState:" + mCurrentStableState);
         writer.println(prefix + "\tmState:" + mState);
         writer.println(prefix + "\tmRestState:" + mRestState);
-        writer.println(prefix + "\tisInTransition:" + (mConfig.mCurrentAnimation != null));
+        writer.println(prefix + "\tisInTransition:" + (mConfig.currentAnimation != null));
     }
 
     public StateHandler[] getStateHandlers() {
@@ -171,7 +147,7 @@
      */
     public boolean isInStableState(LauncherState state) {
         return mState == state && mCurrentStableState == state
-                && (mConfig.mTargetState == null || mConfig.mTargetState == state);
+                && (mConfig.targetState == null || mConfig.targetState == state);
     }
 
     /**
@@ -218,12 +194,12 @@
     }
 
     public void reapplyState(boolean cancelCurrentAnimation) {
-        boolean wasInAnimation = mConfig.mCurrentAnimation != null;
+        boolean wasInAnimation = mConfig.currentAnimation != null;
         if (cancelCurrentAnimation) {
             cancelAllStateElementAnimation();
             cancelAnimation();
         }
-        if (mConfig.mCurrentAnimation == null) {
+        if (mConfig.currentAnimation == null) {
             for (StateHandler handler : getStateHandlers()) {
                 handler.setState(mState);
             }
@@ -237,16 +213,16 @@
             final Runnable onCompleteRunnable) {
         animated &= Utilities.areAnimationsEnabled(mLauncher);
         if (mLauncher.isInState(state)) {
-            if (mConfig.mCurrentAnimation == null) {
+            if (mConfig.currentAnimation == null) {
                 // Run any queued runnable
                 if (onCompleteRunnable != null) {
                     onCompleteRunnable.run();
                 }
                 return;
-            } else if (!mConfig.userControlled && animated && mConfig.mTargetState == state) {
+            } else if (!mConfig.userControlled && animated && mConfig.targetState == state) {
                 // We are running the same animation as requested
                 if (onCompleteRunnable != null) {
-                    mConfig.mCurrentAnimation.addListener(
+                    mConfig.currentAnimation.addListener(
                             AnimationSuccessListener.forRunnable(onCompleteRunnable));
                 }
                 return;
@@ -276,9 +252,9 @@
         if (delay > 0) {
             // Create the animation after the delay as some properties can change between preparing
             // the animation and running the animation.
-            int startChangeId = mConfig.mChangeId;
+            int startChangeId = mConfig.changeId;
             mUiHandler.postDelayed(() -> {
-                if (mConfig.mChangeId == startChangeId) {
+                if (mConfig.changeId == startChangeId) {
                     goToStateAnimated(state, fromState, onCompleteRunnable);
                 }
             }, delay);
@@ -294,11 +270,11 @@
         mConfig.duration = state == NORMAL
                 ? fromState.getTransitionDuration(mLauncher)
                 : state.getTransitionDuration(mLauncher);
-
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
-        prepareForAtomicAnimation(fromState, state, builder);
-        AnimatorSet animation = createAnimationToNewWorkspaceInternal(
-                state, builder, onCompleteRunnable);
+        prepareForAtomicAnimation(fromState, state, mConfig);
+        AnimatorSet animation = createAnimationToNewWorkspaceInternal(state).getAnim();
+        if (onCompleteRunnable != null) {
+            animation.addListener(AnimationSuccessListener.forRunnable(onCompleteRunnable));
+        }
         mUiHandler.post(new StartAnimRunnable(animation));
     }
 
@@ -308,44 +284,22 @@
      * - Setting some start values (e.g. scale) for views that are hidden but about to be shown.
      */
     public void prepareForAtomicAnimation(LauncherState fromState, LauncherState toState,
-            AnimatorSetBuilder builder) {
-        toState.prepareForAtomicAnimation(mLauncher, fromState, builder);
-    }
-
-    public AnimatorSet createAtomicAnimation(LauncherState fromState, LauncherState toState,
-            AnimatorSetBuilder builder, @AnimationFlags int animFlags, long duration) {
-        prepareForAtomicAnimation(fromState, toState, builder);
-        AnimationConfig config = new AnimationConfig();
-        config.mAnimFlags = animFlags;
-        config.duration = duration;
-        for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
-            handler.setStateWithAnimation(toState, builder, config);
-        }
-        return builder.build();
+            StateAnimationConfig config) {
+        toState.prepareForAtomicAnimation(mLauncher, fromState, config);
     }
 
     /**
-     * Creates a {@link AnimatorPlaybackController} that can be used for a controlled
-     * state transition. The UI is force-set to fromState before creating the controller.
-     * @param fromState the initial state for the transition.
-     * @param state the final state for the transition.
-     * @param duration intended duration for normal playback. Use higher duration for better
-     *                accuracy.
+     * Creates an animation representing atomic transitions between the provided states
      */
-    public AnimatorPlaybackController createAnimationToNewWorkspace(
-            LauncherState fromState, LauncherState state, long duration) {
-        // Since we are creating a state animation to a different state, temporarily prevent state
-        // change as part of config reset.
-        LauncherState originalRestState = mRestState;
-        mRestState = state;
-        mConfig.reset();
-        mRestState = originalRestState;
+    public AnimatorSet createAtomicAnimation(
+            LauncherState fromState, LauncherState toState, StateAnimationConfig config) {
+        PendingAnimation builder = new PendingAnimation(config.duration);
+        prepareForAtomicAnimation(fromState, toState, config);
 
-        for (StateHandler handler : getStateHandlers()) {
-            handler.setState(fromState);
+        for (StateHandler handler : mLauncher.getStateManager().getStateHandlers()) {
+            handler.setStateWithAnimation(toState, config, builder);
         }
-
-        return createAnimationToNewWorkspace(state, duration);
+        return builder.getAnim();
     }
 
     /**
@@ -362,32 +316,27 @@
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(
             LauncherState state, long duration, @AnimationFlags int animComponents) {
-        return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
-                animComponents);
+        StateAnimationConfig config = new StateAnimationConfig();
+        config.duration = duration;
+        config.animFlags = animComponents;
+        return createAnimationToNewWorkspace(state, config);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(LauncherState state,
-            AnimatorSetBuilder builder, long duration, Runnable onCancelRunnable,
-            @AnimationFlags int animComponents) {
+            StateAnimationConfig config) {
         mConfig.reset();
-        mConfig.userControlled = true;
-        mConfig.mAnimFlags = animComponents;
-        mConfig.duration = duration;
-        mConfig.playbackController = AnimatorPlaybackController.wrap(
-                createAnimationToNewWorkspaceInternal(state, builder, null), duration)
-                .setOnCancelRunnable(onCancelRunnable);
+        config.copyTo(mConfig);
+        mConfig.playbackController = createAnimationToNewWorkspaceInternal(state)
+                .createPlaybackController();
         return mConfig.playbackController;
     }
 
-    protected AnimatorSet createAnimationToNewWorkspaceInternal(final LauncherState state,
-            AnimatorSetBuilder builder, final Runnable onCompleteRunnable) {
-
+    private PendingAnimation createAnimationToNewWorkspaceInternal(final LauncherState state) {
+        PendingAnimation builder = new PendingAnimation(mConfig.duration);
         for (StateHandler handler : getStateHandlers()) {
-            handler.setStateWithAnimation(state, builder, mConfig);
+            handler.setStateWithAnimation(state, mConfig, builder);
         }
-
-        final AnimatorSet animation = builder.build();
-        animation.addListener(new AnimationSuccessListener() {
+        builder.getAnim().addListener(new AnimationSuccessListener() {
 
             @Override
             public void onAnimationStart(Animator animation) {
@@ -397,15 +346,11 @@
 
             @Override
             public void onAnimationSuccess(Animator animator) {
-                // Run any queued runnables
-                if (onCompleteRunnable != null) {
-                    onCompleteRunnable.run();
-                }
                 onStateTransitionEnd(state);
             }
         });
-        mConfig.setAnimation(animation, state);
-        return mConfig.mCurrentAnimation;
+        mConfig.setAnimation(builder.getAnim(), state);
+        return builder;
     }
 
     private void onStateTransitionStart(LauncherState state) {
@@ -452,7 +397,7 @@
     }
 
     public void moveToRestState() {
-        if (mConfig.mCurrentAnimation != null && mConfig.userControlled) {
+        if (mConfig.currentAnimation != null && mConfig.userControlled) {
             // The user is doing something. Lets not mess it up
             return;
         }
@@ -500,12 +445,12 @@
                     && mConfig.playbackController.getTarget() == childAnim) {
                 clearCurrentAnimation();
                 break;
-            } else if (mConfig.mCurrentAnimation == childAnim) {
+            } else if (mConfig.currentAnimation == childAnim) {
                 clearCurrentAnimation();
                 break;
             }
         }
-        boolean reapplyNeeded = mConfig.mCurrentAnimation != null;
+        boolean reapplyNeeded = mConfig.currentAnimation != null;
         cancelAnimation();
         if (reapplyNeeded) {
             reapplyState();
@@ -557,9 +502,9 @@
     }
 
     private void clearCurrentAnimation() {
-        if (mConfig.mCurrentAnimation != null) {
-            mConfig.mCurrentAnimation.removeListener(mConfig);
-            mConfig.mCurrentAnimation = null;
+        if (mConfig.currentAnimation != null) {
+            mConfig.currentAnimation.removeListener(mConfig);
+            mConfig.currentAnimation = null;
         }
         mConfig.playbackController = null;
     }
@@ -574,54 +519,42 @@
 
         @Override
         public void run() {
-            if (mConfig.mCurrentAnimation != mAnim) {
+            if (mConfig.currentAnimation != mAnim) {
                 return;
             }
             mAnim.start();
         }
     }
 
-    public static class AnimationConfig extends AnimatorListenerAdapter {
-        public long duration;
-        public boolean userControlled;
-        public AnimatorPlaybackController playbackController;
-        private @AnimationFlags int mAnimFlags = ANIM_ALL_COMPONENTS;
-        private PropertySetter mPropertySetter;
+    private static class AnimationState extends StateAnimationConfig implements AnimatorListener {
 
-        private AnimatorSet mCurrentAnimation;
-        private LauncherState mTargetState;
+        private static final StateAnimationConfig DEFAULT = new StateAnimationConfig();
+
+        public AnimatorPlaybackController playbackController;
+        public AnimatorSet currentAnimation;
+        public LauncherState targetState;
+
         // Id to keep track of config changes, to tie an animation with the corresponding request
-        private int mChangeId = 0;
+        public int changeId = 0;
 
         /**
          * Cancels the current animation and resets config variables.
          */
         public void reset() {
-            duration = 0;
-            userControlled = false;
-            mAnimFlags = ANIM_ALL_COMPONENTS;
-            mPropertySetter = null;
-            mTargetState = null;
+            DEFAULT.copyTo(this);
+            targetState = null;
 
             if (playbackController != null) {
                 playbackController.getAnimationPlayer().cancel();
                 playbackController.dispatchOnCancel();
-            } else if (mCurrentAnimation != null) {
-                mCurrentAnimation.setDuration(0);
-                mCurrentAnimation.cancel();
+            } else if (currentAnimation != null) {
+                currentAnimation.setDuration(0);
+                currentAnimation.cancel();
             }
 
-            mCurrentAnimation = null;
+            currentAnimation = null;
             playbackController = null;
-            mChangeId ++;
-        }
-
-        public PropertySetter getPropertySetter(AnimatorSetBuilder builder) {
-            if (mPropertySetter == null) {
-                mPropertySetter = duration == 0 ? NO_ANIM_PROPERTY_SETTER
-                        : new AnimatedPropertySetter(duration, builder);
-            }
-            return mPropertySetter;
+            changeId++;
         }
 
         @Override
@@ -629,51 +562,25 @@
             if (playbackController != null && playbackController.getTarget() == animation) {
                 playbackController = null;
             }
-            if (mCurrentAnimation == animation) {
-                mCurrentAnimation = null;
+            if (currentAnimation == animation) {
+                currentAnimation = null;
             }
         }
 
         public void setAnimation(AnimatorSet animation, LauncherState targetState) {
-            mCurrentAnimation = animation;
-            mTargetState = targetState;
-            mCurrentAnimation.addListener(this);
+            currentAnimation = animation;
+            this.targetState = targetState;
+            currentAnimation.addListener(this);
         }
 
-        /**
-         * @return Whether Overview is scaling as part of this animation. If this is the only
-         * component (i.e. NON_ATOMIC_COMPONENT isn't included), then this scaling is happening
-         * atomically, rather than being part of a normal state animation. StateHandlers can use
-         * this to designate part of their animation that should scale with Overview.
-         */
-        public boolean playAtomicOverviewScaleComponent() {
-            return hasAnimationFlag(PLAY_ATOMIC_OVERVIEW_SCALE);
-        }
+        @Override
+        public void onAnimationStart(Animator animator) { }
 
-        /**
-         * @return Whether this animation will play atomically at the same time as a different,
-         * user-controlled state transition. StateHandlers, which contribute to both animations, can
-         * use this to avoid animating the same properties in both animations, since they'd conflict
-         * with one another.
-         */
-        public boolean onlyPlayAtomicComponent() {
-            return getAnimComponents() == PLAY_ATOMIC_OVERVIEW_SCALE
-                    || getAnimComponents() == PLAY_ATOMIC_OVERVIEW_PEEK;
-        }
+        @Override
+        public void onAnimationCancel(Animator animator) { }
 
-        /**
-         * Returns true if the config and any of the provided component flags
-         */
-        public boolean hasAnimationFlag(@AnimationFlags int a) {
-            return (mAnimFlags & a) != 0;
-        }
-
-        /**
-         * @return Only the flags that determine which animation components to play.
-         */
-        public @AnimationFlags int getAnimComponents() {
-            return mAnimFlags & ANIM_ALL_COMPONENTS;
-        }
+        @Override
+        public void onAnimationRepeat(Animator animator) { }
     }
 
     public interface StateHandler {
@@ -686,8 +593,8 @@
         /**
          * Sets the UI to {@param state} by animating any changes.
          */
-        void setStateWithAnimation(LauncherState toState,
-                AnimatorSetBuilder builder, AnimationConfig config);
+        void setStateWithAnimation(
+                LauncherState toState, StateAnimationConfig config, PendingAnimation animation);
     }
 
     public interface StateListener {
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fc1a074..ead6018 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -60,11 +60,10 @@
 import android.widget.Toast;
 
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
 import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dot.FolderDotInfo;
 import com.android.launcher3.dragndrop.DragController;
@@ -83,6 +82,7 @@
 import com.android.launcher3.pageindicators.WorkspacePageIndicator;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.touch.WorkspaceTouchListener;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -1383,10 +1383,10 @@
      * Sets the current workspace {@link LauncherState}, then animates the UI
      */
     @Override
-    public void setStateWithAnimation(LauncherState toState,
-            AnimatorSetBuilder builder, AnimationConfig config) {
+    public void setStateWithAnimation(
+            LauncherState toState, StateAnimationConfig config, PendingAnimation animation) {
         StateTransitionListener listener = new StateTransitionListener(toState);
-        mStateTransitionAnimation.setStateWithAnimation(toState, builder, config);
+        mStateTransitionAnimation.setStateWithAnimation(toState, config, animation);
 
         // Invalidate the pages now, so that we have the visible pages before the
         // animation is started
@@ -1399,7 +1399,7 @@
         stepAnimator.addUpdateListener(listener);
         stepAnimator.setDuration(config.duration);
         stepAnimator.addListener(listener);
-        builder.play(stepAnimator);
+        animation.add(stepAnimator);
     }
 
     public WorkspaceStateTransitionAnimation getStateTransitionAnimation() {
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 6653426..c521c34 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -22,27 +22,27 @@
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_HOTSEAT_TRANSLATE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_WORKSPACE_TRANSLATE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.ZOOM_OUT;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
 import static com.android.launcher3.graphics.WorkspaceAndHotseatScrim.SCRIM_PROGRESS;
 import static com.android.launcher3.graphics.WorkspaceAndHotseatScrim.SYSUI_PROGRESS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_HOTSEAT_TRANSLATE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_WORKSPACE_TRANSLATE;
 
 import android.view.View;
 import android.view.animation.Interpolator;
 
 import com.android.launcher3.LauncherState.PageAlphaProvider;
 import com.android.launcher3.LauncherState.ScaleAndTranslation;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.allapps.AllAppsContainerView;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
 import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
+import com.android.launcher3.states.StateAnimationConfig;
 
 /**
  * Manages the animations between each of the workspace states.
@@ -60,13 +60,15 @@
     }
 
     public void setState(LauncherState toState) {
-        setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER, new AnimatorSetBuilder(),
-                new AnimationConfig());
+        setWorkspaceProperty(toState, NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig());
     }
 
-    public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
-            AnimationConfig config) {
-        setWorkspaceProperty(toState, config.getPropertySetter(builder), builder, config);
+    /**
+     * @see com.android.launcher3.LauncherStateManager.StateHandler#setStateWithAnimation
+     */
+    public void setStateWithAnimation(
+            LauncherState toState, StateAnimationConfig config, PendingAnimation animation) {
+        setWorkspaceProperty(toState, animation, config);
     }
 
     public float getFinalScale() {
@@ -77,7 +79,7 @@
      * Starts a transition animation for the workspace.
      */
     private void setWorkspaceProperty(LauncherState state, PropertySetter propertySetter,
-            AnimatorSetBuilder builder, AnimationConfig config) {
+            StateAnimationConfig config) {
         ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
         ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(
                 mLauncher);
@@ -87,11 +89,11 @@
         final int childCount = mWorkspace.getChildCount();
         for (int i = 0; i < childCount; i++) {
             applyChildState(state, (CellLayout) mWorkspace.getChildAt(i), i, pageAlphaProvider,
-                    propertySetter, builder, config);
+                    propertySetter, config);
         }
 
         int elements = state.getVisibleElements(mLauncher);
-        Interpolator fadeInterpolator = builder.getInterpolator(ANIM_WORKSPACE_FADE,
+        Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
                 pageAlphaProvider.interpolator);
         boolean playAtomicComponent = config.playAtomicOverviewScaleComponent();
         Hotseat hotseat = mWorkspace.getHotseat();
@@ -99,7 +101,7 @@
         AllAppsContainerView qsbScaleView = mLauncher.getAppsView();
         View qsbView = qsbScaleView.getSearchView();
         if (playAtomicComponent) {
-            Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
+            Interpolator scaleInterpolator = config.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
             propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
 
             if (!hotseat.getRotationMode().isTransposed) {
@@ -107,7 +109,7 @@
                 setPivotToScaleWithWorkspace(qsbScaleView);
             }
             float hotseatScale = hotseatScaleAndTranslation.scale;
-            Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
+            Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
                     scaleInterpolator);
             propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
                     hotseatScaleInterpolator);
@@ -127,13 +129,13 @@
 
         Interpolator translationInterpolator = !playAtomicComponent
                 ? LINEAR
-                : builder.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
+                : config.getInterpolator(ANIM_WORKSPACE_TRANSLATE, ZOOM_OUT);
         propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_X,
                 scaleAndTranslation.translationX, translationInterpolator);
         propertySetter.setFloat(mWorkspace, VIEW_TRANSLATE_Y,
                 scaleAndTranslation.translationY, translationInterpolator);
 
-        Interpolator hotseatTranslationInterpolator = builder.getInterpolator(
+        Interpolator hotseatTranslationInterpolator = config.getInterpolator(
                 ANIM_HOTSEAT_TRANSLATE, translationInterpolator);
         propertySetter.setFloat(hotseat, VIEW_TRANSLATE_Y,
                 hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
@@ -166,12 +168,12 @@
 
     public void applyChildState(LauncherState state, CellLayout cl, int childIndex) {
         applyChildState(state, cl, childIndex, state.getWorkspacePageAlphaProvider(mLauncher),
-                NO_ANIM_PROPERTY_SETTER, new AnimatorSetBuilder(), new AnimationConfig());
+                NO_ANIM_PROPERTY_SETTER, new StateAnimationConfig());
     }
 
     private void applyChildState(LauncherState state, CellLayout cl, int childIndex,
             PageAlphaProvider pageAlphaProvider, PropertySetter propertySetter,
-            AnimatorSetBuilder builder, AnimationConfig config) {
+            StateAnimationConfig config) {
         float pageAlpha = pageAlphaProvider.getPageAlpha(childIndex);
         int drawableAlpha = Math.round(pageAlpha * (state.hasWorkspacePageBackground ? 255 : 0));
 
@@ -181,7 +183,7 @@
                     DRAWABLE_ALPHA, drawableAlpha, ZOOM_OUT);
         }
         if (config.playAtomicOverviewScaleComponent()) {
-            Interpolator fadeInterpolator = builder.getInterpolator(ANIM_WORKSPACE_FADE,
+            Interpolator fadeInterpolator = config.getInterpolator(ANIM_WORKSPACE_FADE,
                     pageAlphaProvider.interpolator);
             propertySetter.setFloat(cl.getShortcutsAndWidgets(), VIEW_ALPHA,
                     pageAlpha, fadeInterpolator);
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 744f4eb..7600f52 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -5,13 +5,13 @@
 import static com.android.launcher3.LauncherState.APPS_VIEW_ITEM_MASK;
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.LauncherState.VERTICAL_SWIPE_INDICATOR;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_ALL_APPS_HEADER_FADE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_OVERVIEW_SCALE;
-import static com.android.launcher3.anim.AnimatorSetBuilder.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_HEADER_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
 
 import android.animation.Animator;
@@ -24,12 +24,12 @@
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
 import com.android.launcher3.LauncherStateManager.StateHandler;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PropertySetter;
+import com.android.launcher3.states.StateAnimationConfig;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.views.ScrimView;
 
@@ -112,7 +112,7 @@
      * @param progress value between 0 and 1, 0 shows all apps and 1 shows workspace
      *
      * @see #setState(LauncherState)
-     * @see #setStateWithAnimation(LauncherState, AnimatorSetBuilder, AnimationConfig)
+     * @see #setStateWithAnimation(LauncherState, StateAnimationConfig, PendingAnimation)
      */
     public void setProgress(float progress) {
         mProgress = progress;
@@ -143,7 +143,7 @@
     @Override
     public void setState(LauncherState state) {
         setProgress(state.getVerticalProgress(mLauncher));
-        setAlphas(state, null, new AnimatorSetBuilder());
+        setAlphas(state, new StateAnimationConfig(), NO_ANIM_PROPERTY_SETTER);
         onProgressAnimationEnd();
     }
 
@@ -153,7 +153,7 @@
      */
     @Override
     public void setStateWithAnimation(LauncherState toState,
-            AnimatorSetBuilder builder, AnimationConfig config) {
+            StateAnimationConfig config, PendingAnimation builder) {
         float targetProgress = toState.getVerticalProgress(mLauncher);
         if (Float.compare(mProgress, targetProgress) == 0) {
             setAlphas(toState, config, builder);
@@ -168,14 +168,14 @@
         }
 
         Interpolator interpolator = config.userControlled ? LINEAR : toState == OVERVIEW
-                ? builder.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
+                ? config.getInterpolator(ANIM_OVERVIEW_SCALE, FAST_OUT_SLOW_IN)
                 : FAST_OUT_SLOW_IN;
+
         Animator anim = createSpringAnimation(mProgress, targetProgress);
         anim.setDuration(config.duration);
-        anim.setInterpolator(builder.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
+        anim.setInterpolator(config.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
         anim.addListener(getProgressAnimatorListener());
-
-        builder.play(anim);
+        builder.add(anim);
 
         setAlphas(toState, config, builder);
     }
@@ -184,21 +184,18 @@
         return ObjectAnimator.ofFloat(this, ALL_APPS_PROGRESS, progressValues);
     }
 
-    private void setAlphas(LauncherState toState, AnimationConfig config,
-            AnimatorSetBuilder builder) {
-        setAlphas(toState.getVisibleElements(mLauncher), config, builder);
-    }
-
-    public void setAlphas(int visibleElements, AnimationConfig config, AnimatorSetBuilder builder) {
-        PropertySetter setter = config == null ? NO_ANIM_PROPERTY_SETTER
-                : config.getPropertySetter(builder);
+    /**
+     * Updates the property for the provided state
+     */
+    public void setAlphas(LauncherState state, StateAnimationConfig config, PropertySetter setter) {
+        int visibleElements = state.getVisibleElements(mLauncher);
         boolean hasHeaderExtra = (visibleElements & ALL_APPS_HEADER_EXTRA) != 0;
         boolean hasAllAppsContent = (visibleElements & ALL_APPS_CONTENT) != 0;
 
         boolean hasAnyVisibleItem = (visibleElements & APPS_VIEW_ITEM_MASK) != 0;
 
-        Interpolator allAppsFade = builder.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
-        Interpolator headerFade = builder.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
+        Interpolator allAppsFade = config.getInterpolator(ANIM_ALL_APPS_FADE, LINEAR);
+        Interpolator headerFade = config.getInterpolator(ANIM_ALL_APPS_HEADER_FADE, allAppsFade);
         setter.setViewAlpha(mAppsView.getContentView(), hasAllAppsContent ? 1 : 0, allAppsFade);
         setter.setViewAlpha(mAppsView.getScrollBar(), hasAllAppsContent ? 1 : 0, allAppsFade);
         mAppsView.getFloatingHeaderView().setContentVisibility(hasHeaderExtra, hasAllAppsContent,
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 1fc21fd..958c863 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -64,13 +64,6 @@
         return new AnimatorPlaybackController(anim, duration, childAnims);
     }
 
-    public static AnimatorPlaybackController wrap(PendingAnimation anim, long duration) {
-        /**
-         * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed.
-         */
-        return new AnimatorPlaybackController(anim.anim, duration, anim.animHolders);
-    }
-
     private static final FloatProperty<ValueAnimator> CURRENT_PLAY_TIME =
             new FloatProperty<ValueAnimator>("current-play-time") {
                 @Override
@@ -96,8 +89,8 @@
     protected boolean mTargetCancelled = false;
     protected Runnable mOnCancelRunnable;
 
-    private AnimatorPlaybackController(
-            AnimatorSet anim, long duration, ArrayList<Holder> childAnims) {
+    /** package private */
+    AnimatorPlaybackController(AnimatorSet anim, long duration, ArrayList<Holder> childAnims) {
         mAnim = anim;
         mDuration = duration;
 
diff --git a/src/com/android/launcher3/anim/AnimatorSetBuilder.java b/src/com/android/launcher3/anim/AnimatorSetBuilder.java
deleted file mode 100644
index d814b19..0000000
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 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 android.animation.Animator;
-import android.animation.AnimatorSet;
-import android.util.SparseArray;
-import android.view.animation.Interpolator;
-
-import java.util.ArrayList;
-
-/**
- * Utility class for building animator set
- */
-public class AnimatorSetBuilder {
-
-    public static final int ANIM_VERTICAL_PROGRESS = 0;
-    public static final int ANIM_WORKSPACE_SCALE = 1;
-    public static final int ANIM_WORKSPACE_TRANSLATE = 2;
-    public static final int ANIM_WORKSPACE_FADE = 3;
-    public static final int ANIM_HOTSEAT_SCALE = 4;
-    public static final int ANIM_HOTSEAT_TRANSLATE = 5;
-    public static final int ANIM_OVERVIEW_SCALE = 6;
-    public static final int ANIM_OVERVIEW_TRANSLATE_X = 7;
-    public static final int ANIM_OVERVIEW_TRANSLATE_Y = 8;
-    public static final int ANIM_OVERVIEW_FADE = 9;
-    public static final int ANIM_ALL_APPS_FADE = 10;
-    public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
-    public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
-
-    protected final ArrayList<Animator> mAnims = new ArrayList<>();
-
-    private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
-
-    public void play(Animator anim) {
-        mAnims.add(anim);
-    }
-
-    public AnimatorSet build() {
-        AnimatorSet anim = new AnimatorSet();
-        anim.playTogether(mAnims);
-        return anim;
-    }
-
-    public Interpolator getInterpolator(int animId, Interpolator fallback) {
-        return mInterpolators.get(animId, fallback);
-    }
-
-    public void setInterpolator(int animId, Interpolator interpolator) {
-        mInterpolators.put(animId, interpolator);
-    }
-}
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 562d160..9a25c47 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -19,9 +19,12 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
-import android.annotation.TargetApi;
-import android.os.Build;
+import android.animation.ValueAnimator;
+import android.util.FloatProperty;
+import android.util.IntProperty;
+import android.view.View;
 
 import com.android.launcher3.anim.AnimatorPlaybackController.Holder;
 
@@ -37,14 +40,24 @@
  *
  * TODO: Find a better name
  */
-@TargetApi(Build.VERSION_CODES.O)
-public class PendingAnimation {
+public class PendingAnimation implements PropertySetter {
 
     private final ArrayList<Consumer<EndState>> mEndListeners = new ArrayList<>();
 
-    /** package private **/
-    final AnimatorSet anim = new AnimatorSet();
-    final ArrayList<Holder> animHolders = new ArrayList<>();
+    private final ArrayList<Holder> mAnimHolders = new ArrayList<>();
+    private final AnimatorSet mAnim;
+    private final long mDuration;
+
+    private ValueAnimator mProgressAnimator;
+
+    public PendingAnimation(long  duration) {
+        this(duration, new AnimatorSet());
+    }
+
+    public PendingAnimation(long  duration, AnimatorSet targetSet) {
+        mDuration = duration;
+        mAnim = targetSet;
+    }
 
     /**
      * Utility method to sent an interpolator on an animation and add it to the list
@@ -63,8 +76,8 @@
     }
 
     public void add(Animator a, SpringProperty springProperty) {
-        anim.play(a);
-        addAnimationHoldersRecur(a, springProperty, animHolders);
+        mAnim.play(a);
+        addAnimationHoldersRecur(a, springProperty, mAnimHolders);
     }
 
     public void finish(boolean isSuccess, int logAction) {
@@ -74,6 +87,67 @@
         mEndListeners.clear();
     }
 
+    @Override
+    public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
+        if (view == null || view.getAlpha() == alpha) {
+            return;
+        }
+        ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
+        anim.addListener(new AlphaUpdateListener(view));
+        anim.setDuration(mDuration).setInterpolator(interpolator);
+        add(anim);
+    }
+
+    @Override
+    public <T> void setFloat(T target, FloatProperty<T> property, float value,
+            TimeInterpolator interpolator) {
+        if (property.get(target) == value) {
+            return;
+        }
+        Animator anim = ObjectAnimator.ofFloat(target, property, value);
+        anim.setDuration(mDuration).setInterpolator(interpolator);
+        add(anim);
+    }
+
+    @Override
+    public <T> void setInt(T target, IntProperty<T> property, int value,
+            TimeInterpolator interpolator) {
+        if (property.get(target) == value) {
+            return;
+        }
+        Animator anim = ObjectAnimator.ofInt(target, property, value);
+        anim.setDuration(mDuration).setInterpolator(interpolator);
+        add(anim);
+    }
+
+    /**
+     * Adds a callback to be run on every frame of the animation
+     */
+    public void addOnFrameCallback(Runnable runnable) {
+        if (mProgressAnimator == null) {
+            mProgressAnimator = ValueAnimator.ofFloat(0, 1).setDuration(mDuration);
+            add(mProgressAnimator);
+        }
+
+        mProgressAnimator.addUpdateListener(anim -> runnable.run());
+    }
+
+    public AnimatorSet getAnim() {
+        return mAnim;
+    }
+
+    /**
+     * Creates a controller for this animation
+     */
+    public AnimatorPlaybackController createPlaybackController() {
+        return new AnimatorPlaybackController(mAnim, mDuration, mAnimHolders);
+    }
+
+    /**
+     * Add a listener of receiving the end state.
+     * Note that the listeners are called as a result of calling {@link #finish(boolean, int)}
+     * and not automatically
+     */
     public void addEndListener(Consumer<EndState> listener) {
         mEndListeners.add(listener);
     }
diff --git a/src/com/android/launcher3/anim/PropertySetter.java b/src/com/android/launcher3/anim/PropertySetter.java
index 0b2eb48..2ce620b 100644
--- a/src/com/android/launcher3/anim/PropertySetter.java
+++ b/src/com/android/launcher3/anim/PropertySetter.java
@@ -16,8 +16,6 @@
 
 package com.android.launcher3.anim;
 
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
 import android.util.FloatProperty;
 import android.util.IntProperty;
@@ -26,68 +24,35 @@
 /**
  * Utility class for setting a property with or without animation
  */
-public class PropertySetter {
+public interface PropertySetter {
 
-    public static final PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter();
+    PropertySetter NO_ANIM_PROPERTY_SETTER = new PropertySetter() { };
 
-    public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
+    /**
+     * Sets the view alpha using the provided interpolator.
+     * Unlike {@link #setFloat}, this also updates the visibility of the view as alpha changes
+     * between zero and non-zero.
+     */
+    default void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
         if (view != null) {
             view.setAlpha(alpha);
             AlphaUpdateListener.updateVisibility(view);
         }
     }
 
-    public <T> void setFloat(T target, FloatProperty<T> property, float value,
+    /**
+     * Updates the float property of the target using the provided interpolator
+     */
+    default <T> void setFloat(T target, FloatProperty<T> property, float value,
             TimeInterpolator interpolator) {
         property.setValue(target, value);
     }
 
-    public <T> void setInt(T target, IntProperty<T> property, int value,
+    /**
+     * Updates the int property of the target using the provided interpolator
+     */
+    default <T> void setInt(T target, IntProperty<T> property, int value,
             TimeInterpolator interpolator) {
         property.setValue(target, value);
     }
-
-    public static class AnimatedPropertySetter extends PropertySetter {
-
-        private final long mDuration;
-        private final AnimatorSetBuilder mStateAnimator;
-
-        public AnimatedPropertySetter(long duration, AnimatorSetBuilder builder) {
-            mDuration = duration;
-            mStateAnimator = builder;
-        }
-
-        @Override
-        public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
-            if (view == null || view.getAlpha() == alpha) {
-                return;
-            }
-            ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.ALPHA, alpha);
-            anim.addListener(new AlphaUpdateListener(view));
-            anim.setDuration(mDuration).setInterpolator(interpolator);
-            mStateAnimator.play(anim);
-        }
-
-        @Override
-        public <T> void setFloat(T target, FloatProperty<T> property, float value,
-                TimeInterpolator interpolator) {
-            if (property.get(target) == value) {
-                return;
-            }
-            Animator anim = ObjectAnimator.ofFloat(target, property, value);
-            anim.setDuration(mDuration).setInterpolator(interpolator);
-            mStateAnimator.play(anim);
-        }
-
-        @Override
-        public <T> void setInt(T target, IntProperty<T> property, int value,
-                TimeInterpolator interpolator) {
-            if (property.get(target) == value) {
-                return;
-            }
-            Animator anim = ObjectAnimator.ofInt(target, property, value);
-            anim.setDuration(mDuration).setInterpolator(interpolator);
-            mStateAnimator.play(anim);
-        }
-    }
 }
diff --git a/src/com/android/launcher3/states/StateAnimationConfig.java b/src/com/android/launcher3/states/StateAnimationConfig.java
new file mode 100644
index 0000000..82cde64
--- /dev/null
+++ b/src/com/android/launcher3/states/StateAnimationConfig.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2020 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.states;
+
+import android.view.animation.Interpolator;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utility class for building animator set
+ */
+public class StateAnimationConfig {
+
+    // We separate the state animations into "atomic" and "non-atomic" components. The atomic
+    // components may be run atomically - that is, all at once, instead of user-controlled. However,
+    // atomic components are not restricted to this purpose; they can be user-controlled alongside
+    // non atomic components as well. Note that each gesture model has exactly one atomic component,
+    // PLAY_ATOMIC_OVERVIEW_SCALE *or* PLAY_ATOMIC_OVERVIEW_PEEK.
+    @IntDef(flag = true, value = {
+            PLAY_NON_ATOMIC,
+            PLAY_ATOMIC_OVERVIEW_SCALE,
+            PLAY_ATOMIC_OVERVIEW_PEEK,
+            SKIP_OVERVIEW,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AnimationFlags {}
+    public static final int PLAY_NON_ATOMIC = 1 << 0;
+    public static final int PLAY_ATOMIC_OVERVIEW_SCALE = 1 << 1;
+    public static final int PLAY_ATOMIC_OVERVIEW_PEEK = 1 << 2;
+    public static final int SKIP_OVERVIEW = 1 << 3;
+
+    public long duration;
+    public boolean userControlled;
+    public @AnimationFlags int animFlags = ANIM_ALL_COMPONENTS;
+
+    public static final int ANIM_ALL_COMPONENTS = PLAY_NON_ATOMIC | PLAY_ATOMIC_OVERVIEW_SCALE
+            | PLAY_ATOMIC_OVERVIEW_PEEK;
+
+    // Various types of animation state transition
+    @IntDef(value = {
+            ANIM_VERTICAL_PROGRESS,
+            ANIM_WORKSPACE_SCALE,
+            ANIM_WORKSPACE_TRANSLATE,
+            ANIM_WORKSPACE_FADE,
+            ANIM_HOTSEAT_SCALE,
+            ANIM_HOTSEAT_TRANSLATE,
+            ANIM_OVERVIEW_SCALE,
+            ANIM_OVERVIEW_TRANSLATE_X,
+            ANIM_OVERVIEW_TRANSLATE_Y,
+            ANIM_OVERVIEW_FADE,
+            ANIM_ALL_APPS_FADE,
+            ANIM_OVERVIEW_SCRIM_FADE,
+            ANIM_ALL_APPS_HEADER_FADE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AnimType {}
+    public static final int ANIM_VERTICAL_PROGRESS = 0;
+    public static final int ANIM_WORKSPACE_SCALE = 1;
+    public static final int ANIM_WORKSPACE_TRANSLATE = 2;
+    public static final int ANIM_WORKSPACE_FADE = 3;
+    public static final int ANIM_HOTSEAT_SCALE = 4;
+    public static final int ANIM_HOTSEAT_TRANSLATE = 5;
+    public static final int ANIM_OVERVIEW_SCALE = 6;
+    public static final int ANIM_OVERVIEW_TRANSLATE_X = 7;
+    public static final int ANIM_OVERVIEW_TRANSLATE_Y = 8;
+    public static final int ANIM_OVERVIEW_FADE = 9;
+    public static final int ANIM_ALL_APPS_FADE = 10;
+    public static final int ANIM_OVERVIEW_SCRIM_FADE = 11;
+    public static final int ANIM_ALL_APPS_HEADER_FADE = 12; // e.g. predictions
+
+    private static final int ANIM_TYPES_COUNT = 13;
+
+    private final Interpolator[] mInterpolators = new Interpolator[ANIM_TYPES_COUNT];
+
+    public StateAnimationConfig() { }
+
+    /**
+     * Copies the config to target
+     */
+    public void copyTo(StateAnimationConfig target) {
+        target.duration = duration;
+        target.animFlags = animFlags;
+        target.userControlled = userControlled;
+        for (int i = 0; i < ANIM_TYPES_COUNT; i++) {
+            target.mInterpolators[i] = mInterpolators[i];
+        }
+    }
+
+    /**
+     * Returns the interpolator set for animId or fallback if nothing is set
+     *
+     * @see #setInterpolator(int, Interpolator)
+     */
+    public Interpolator getInterpolator(@AnimType int animId, Interpolator fallback) {
+        return mInterpolators[animId] == null ? fallback : mInterpolators[animId];
+    }
+
+    /**
+     * Sets an interpolator for a given animation type
+     */
+    public void setInterpolator(@AnimType int animId, Interpolator interpolator) {
+        mInterpolators[animId] = interpolator;
+    }
+
+    /**
+     * @return Whether Overview is scaling as part of this animation. If this is the only
+     * component (i.e. NON_ATOMIC_COMPONENT isn't included), then this scaling is happening
+     * atomically, rather than being part of a normal state animation. StateHandlers can use
+     * this to designate part of their animation that should scale with Overview.
+     */
+    public boolean playAtomicOverviewScaleComponent() {
+        return hasAnimationFlag(StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE);
+    }
+
+    /**
+     * @return Whether this animation will play atomically at the same time as a different,
+     * user-controlled state transition. StateHandlers, which contribute to both animations, can
+     * use this to avoid animating the same properties in both animations, since they'd conflict
+     * with one another.
+     */
+    public boolean onlyPlayAtomicComponent() {
+        return getAnimComponents() == StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE
+                || getAnimComponents() == StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_PEEK;
+    }
+
+    /**
+     * Returns true if the config and any of the provided component flags
+     */
+    public boolean hasAnimationFlag(@AnimationFlags int a) {
+        return (animFlags & a) != 0;
+    }
+
+    /**
+     * @return Only the flags that determine which animation components to play.
+     */
+    public @AnimationFlags int getAnimComponents() {
+        return animFlags & StateAnimationConfig.ANIM_ALL_COMPONENTS;
+    }
+}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index c3664c3..1d14a8f 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -19,11 +19,11 @@
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.LauncherStateManager.ANIM_ALL_COMPONENTS;
-import static com.android.launcher3.LauncherStateManager.PLAY_ATOMIC_OVERVIEW_SCALE;
-import static com.android.launcher3.LauncherStateManager.PLAY_NON_ATOMIC;
 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
 import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_COMPONENTS;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_ATOMIC_OVERVIEW_SCALE;
+import static com.android.launcher3.states.StateAnimationConfig.PLAY_NON_ATOMIC;
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.animation.Animator;
@@ -37,12 +37,12 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherAnimUtils;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationFlags;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
-import com.android.launcher3.anim.AnimatorSetBuilder;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.states.StateAnimationConfig;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Direction;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -358,14 +358,18 @@
 
     private AnimatorSet createAtomicAnimForState(LauncherState fromState, LauncherState targetState,
             long duration) {
-        AnimatorSetBuilder builder = getAnimatorSetBuilderForStates(fromState, targetState);
-        return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, builder,
-                PLAY_ATOMIC_OVERVIEW_SCALE, duration);
+        StateAnimationConfig config = getConfigForStates(fromState, targetState);
+        config.animFlags = PLAY_ATOMIC_OVERVIEW_SCALE;
+        config.duration = duration;
+        return mLauncher.getStateManager().createAtomicAnimation(fromState, targetState, config);
     }
 
-    protected AnimatorSetBuilder getAnimatorSetBuilderForStates(LauncherState fromState,
-            LauncherState toState) {
-        return new AnimatorSetBuilder();
+    /**
+     * Returns animation config for state transition between provided states
+     */
+    protected StateAnimationConfig getConfigForStates(
+            LauncherState fromState, LauncherState toState) {
+        return new StateAnimationConfig();
     }
 
     @Override
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 8d5f33d..4a202b6 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -23,7 +23,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationFlags;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
diff --git a/src/com/android/launcher3/widget/WidgetsBottomSheet.java b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
index a7078a2..8d1a3b0 100644
--- a/src/com/android/launcher3/widget/WidgetsBottomSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsBottomSheet.java
@@ -16,8 +16,8 @@
 
 package com.android.launcher3.widget;
 
-import android.animation.Animator;
-import android.animation.ObjectAnimator;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
@@ -28,16 +28,15 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.animation.Interpolator;
 import android.widget.TextView;
 
-import androidx.annotation.Nullable;
-
 import com.android.launcher3.Insettable;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.R;
 import com.android.launcher3.ResourceUtils;
-import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.model.WidgetItem;
 import com.android.launcher3.util.PackageUserKey;
 
@@ -156,7 +155,7 @@
         setupNavBarColor();
         mOpenCloseAnimator.setValues(
                 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
-        mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mOpenCloseAnimator.setInterpolator(FAST_OUT_SLOW_IN);
         mOpenCloseAnimator.start();
     }
 
@@ -191,9 +190,9 @@
                 mIsOpen ? R.string.widgets_list : R.string.widgets_list_closed));
     }
 
-    @Nullable
     @Override
-    public Animator createHintCloseAnim(float distanceToMove) {
-        return ObjectAnimator.ofInt(this, PADDING_BOTTOM, (int) (distanceToMove + mInsets.bottom));
+    public void addHintCloseAnim(
+            float distanceToMove, Interpolator interpolator, PendingAnimation target) {
+        target.setInt(this, PADDING_BOTTOM, (int) (distanceToMove + mInsets.bottom), interpolator);
     }
 }
diff --git a/src/com/android/launcher3/widget/WidgetsFullSheet.java b/src/com/android/launcher3/widget/WidgetsFullSheet.java
index 2a102d2..b07a4f4 100644
--- a/src/com/android/launcher3/widget/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/WidgetsFullSheet.java
@@ -15,12 +15,11 @@
  */
 package com.android.launcher3.widget;
 
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.content.Context;
 import android.graphics.Rect;
@@ -30,8 +29,8 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 
-import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.Insettable;
@@ -39,6 +38,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherAppWidgetHost.ProviderChangedListener;
 import com.android.launcher3.R;
+import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 import com.android.launcher3.views.TopRoundedCornerView;
@@ -242,13 +242,11 @@
         return mAdapter.getItemCount();
     }
 
-    @Nullable
     @Override
-    public Animator createHintCloseAnim(float distanceToMove) {
-        AnimatorSet anim = new AnimatorSet();
-        anim.play(ObjectAnimator.ofFloat(mRecyclerView, TRANSLATION_Y, -distanceToMove));
-        anim.play(ObjectAnimator.ofFloat(mRecyclerView, ALPHA, 0.5f));
-        return anim;
+    public void addHintCloseAnim(
+            float distanceToMove, Interpolator interpolator, PendingAnimation target) {
+        target.setFloat(mRecyclerView, VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
+        target.setViewAlpha(mRecyclerView, 0.5f, interpolator);
     }
 
     @Override
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java b/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java
index 232bad3..75f99a9 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java
@@ -19,10 +19,12 @@
 
 import android.util.IntProperty;
 import android.view.View;
+
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.states.StateAnimationConfig;
 
 /**
  * Controls the blur, for the Launcher surface only.
@@ -52,6 +54,6 @@
     public void setState(LauncherState toState) {}
 
     @Override
-    public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
-            LauncherStateManager.AnimationConfig config) {}
+    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
+            PendingAnimation animation) { }
 }