Merge "Show IME suggestions while tapping on Folder EditText" into ub-launcher3-master
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..6fc03b1 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) {
@@ -116,8 +120,8 @@
         mPeekAnim.start();
         VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
 
-        mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
-                peekDuration, 0);
+        mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(isPaused ? 0 : 1)
+                .setDuration(peekDuration).start();
     }
 
     /**
@@ -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..1b3610a 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
@@ -81,6 +81,9 @@
 
     private boolean canInterceptTouch() {
         if (mCurrentAnimation != null) {
+            mCurrentAnimation.forceFinishIfCloseToEnd();
+        }
+        if (mCurrentAnimation != null) {
             // If we are already animating from a previous state, we can intercept.
             return true;
         }
@@ -126,6 +129,7 @@
 
                 for (int i = 0; i < mRecentsView.getTaskViewCount(); i++) {
                     TaskView view = mRecentsView.getTaskViewAt(i);
+
                     if (mRecentsView.isTaskViewVisible(view) && mActivity.getDragLayer()
                             .isEventOverView(view, ev)) {
                         // Disable swiping up and down if the task overlay is modal.
@@ -214,7 +218,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/AppToOverviewAnimationProvider.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
index e1ff4f4..aaf7619 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -48,7 +48,7 @@
  *
  * @param <T> activity that contains the overview
  */
-final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> implements
+final class AppToOverviewAnimationProvider<T extends BaseDraggingActivity> extends
         RemoteAnimationProvider {
 
     private static final long RECENTS_LAUNCH_DURATION = 250;
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
index 71580ca..4abb86a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackActivityInterface.java
@@ -213,7 +213,7 @@
     @Override
     public Rect getOverviewWindowBounds(Rect homeBounds, RemoteAnimationTargetCompat target) {
         // TODO: Remove this once b/77875376 is fixed
-        return target.sourceContainerBounds;
+        return target.screenSpaceBounds;
     }
 
     @Override
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
index fc50660..c6b719a 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/OverviewCommandHelper.java
@@ -33,6 +33,7 @@
 import com.android.launcher3.logging.UserEventDispatcher;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.ActivityInitListener;
+import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -187,7 +188,15 @@
             // Otherwise, start overview.
             mListener = mActivityInterface.createActivityInitListener(this::onActivityReady);
             mListener.registerAndStartActivity(mOverviewComponentObserver.getOverviewIntent(),
-                    this::createWindowAnimation, mContext, MAIN_EXECUTOR.getHandler(),
+                    new RemoteAnimationProvider() {
+                        @Override
+                        public AnimatorSet createWindowAnimation(
+                                RemoteAnimationTargetCompat[] appTargets,
+                                RemoteAnimationTargetCompat[] wallpaperTargets) {
+                            return RecentsActivityCommand.this.createWindowAnimation(appTargets,
+                                    wallpaperTargets);
+                        }
+                    }, mContext, MAIN_EXECUTOR.getHandler(),
                     mAnimationProvider.getRecentsLaunchDuration());
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 681ce02..d9e9cc7 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -50,6 +50,7 @@
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -119,7 +120,7 @@
 
     private void updateSourceStack(RemoteAnimationTargetCompat target) {
         mSourceInsets.set(target.contentInsets);
-        mSourceStackBounds.set(target.sourceContainerBounds);
+        mSourceStackBounds.set(target.screenSpaceBounds);
 
         // TODO: Should sourceContainerBounds already have this offset?
         mSourceStackBounds.offsetTo(target.position.x, target.position.y);
@@ -199,9 +200,17 @@
         for (int i = 0; i < params.mTargetSet.unfilteredApps.length; i++) {
             RemoteAnimationTargetCompat app = params.mTargetSet.unfilteredApps[i];
             SurfaceParams.Builder builder = new SurfaceParams.Builder(app.leash);
-            mTmpMatrix.setTranslate(app.position.x, app.position.y);
+            if (app.localBounds != null) {
+                mTmpMatrix.setTranslate(0, 0);
+                if (app.activityType == ACTIVITY_TYPE_HOME && app.mode == MODE_CLOSING) {
+                    mTmpMatrix.setTranslate(app.localBounds.left, app.localBounds.top);
+                }
+            } else {
+                mTmpMatrix.setTranslate(app.position.x, app.position.y);
+            }
+
             Rect crop = mTmpRect;
-            crop.set(app.sourceContainerBounds);
+            crop.set(app.screenSpaceBounds);
             crop.offsetTo(0, 0);
             float alpha;
             float cornerRadius = 0f;
@@ -211,7 +220,11 @@
                 alpha = mTaskAlphaCallback.getAlpha(app, params.mTargetAlpha);
                 if (app.activityType != RemoteAnimationTargetCompat.ACTIVITY_TYPE_HOME) {
                     mTmpMatrix.setRectToRect(mSourceRect, mCurrentRect, ScaleToFit.FILL);
-                    mTmpMatrix.postTranslate(app.position.x, app.position.y);
+                    if (app.localBounds != null) {
+                        mTmpMatrix.postTranslate(app.localBounds.left, app.localBounds.top);
+                    } else {
+                        mTmpMatrix.postTranslate(app.position.x, app.position.y);
+                    }
                     mCurrentClipRectF.roundOut(crop);
                     if (mSupportsRoundedCornersOnWindows) {
                         if (params.mCornerRadius > -1) {
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..9cf45b3 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;
@@ -126,6 +126,8 @@
             addScrimAnimationForState(launcher, NORMAL, ALPHA_DURATION_MS);
         }
 
+        mAnimators.play(launcher.getDragLayer().getScrim().createSysuiMultiplierAnim(0f, 1f)
+                .setDuration(ALPHA_DURATION_MS));
         mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
@@ -143,16 +145,20 @@
      * 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);
     }
 
+    public AnimatorSet getAnimators() {
+        return mAnimators;
+    }
+
     /**
      * Starts the animation.
      */
@@ -197,16 +203,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 b5e6af4..e15ac46 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
@@ -68,7 +68,6 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
-import android.view.OrientationEventListener;
 import android.view.View;
 import android.view.ViewDebug;
 import android.view.ViewGroup;
@@ -88,7 +87,6 @@
 import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.anim.PendingAnimation;
 import com.android.launcher3.anim.PendingAnimation.EndState;
@@ -96,7 +94,6 @@
 import com.android.launcher3.anim.SpringProperty;
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.states.RotationHelper;
 import com.android.launcher3.touch.PagedOrientationHandler.CurveProperties;
@@ -1165,7 +1162,7 @@
     }
 
     private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) {
-        anim.add(ObjectAnimator.ofFloat(taskView, ALPHA, 0).setDuration(duration), ACCEL_2);
+        anim.setViewAlpha(taskView, 0, ACCEL_2);
         FloatProperty<View> secondaryViewTranslate =
             mOrientationHandler.getSecondaryViewTranslate();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -1195,7 +1192,7 @@
         if (mPendingAnimation != null) {
             mPendingAnimation.finish(false, Touch.SWIPE);
         }
-        PendingAnimation anim = new PendingAnimation();
+        PendingAnimation anim = new PendingAnimation(duration);
 
         int count = getPageCount();
         if (count == 0) {
@@ -1257,9 +1254,7 @@
         }
 
         if (needsCurveUpdates) {
-            ValueAnimator va = ValueAnimator.ofFloat(0, 1);
-            va.addUpdateListener((a) -> updateCurveProperties());
-            anim.add(va);
+            anim.addOnFrameCallback(this::updateCurveProperties);
         }
 
         // Add a tiny bit of translation Z, so that it draws on top of other views
@@ -1279,6 +1274,7 @@
                 }
             }
 
+            @SuppressWarnings("WrongCall")
             private void onEnd(EndState endState) {
                 if (endState.isSuccess) {
                     if (shouldRemoveTask) {
@@ -1290,15 +1286,18 @@
                             pageToSnapTo == (getTaskViewCount() - 1)) {
                         pageToSnapTo -= 1;
                     }
-                    removeView(taskView);
+                    removeViewInLayout(taskView);
 
                     if (getTaskViewCount() == 0) {
-                        removeView(mClearAllButton);
+                        removeViewInLayout(mClearAllButton);
                         hideActionsView();
                         startHome();
                     } else {
                         snapToPageImmediately(pageToSnapTo);
                     }
+                    // Update the layout synchronously so that the position of next view is
+                    // immediately available.
+                    onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
                 }
                 resetTaskVisuals();
                 mPendingAnimation = null;
@@ -1311,7 +1310,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++) {
@@ -1345,8 +1344,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);
@@ -1549,6 +1547,7 @@
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
+
         updateEmptyStateUi(changed);
 
         // Set the pivot points to match the task preview center
@@ -1671,7 +1670,7 @@
 
         int count = getTaskViewCount();
         if (count == 0) {
-            return new PendingAnimation();
+            return new PendingAnimation(duration);
         }
 
         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
@@ -1710,10 +1709,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/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 809543a..a7d00c5 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -52,10 +52,12 @@
 import com.android.quickstep.SysUINavigationMode.Mode;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
 import com.android.quickstep.SystemUiProxy;
+import com.android.quickstep.util.RemoteAnimationProvider;
 import com.android.quickstep.util.RemoteFadeOutAnimationListener;
 import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.quickstep.views.RecentsView;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.stream.Stream;
 
@@ -262,17 +264,21 @@
     public void useFadeOutAnimationForLauncherStart(CancellationSignal signal) {
         QuickstepAppTransitionManagerImpl appTransitionManager =
                 (QuickstepAppTransitionManagerImpl) getAppTransitionManager();
-        appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> {
+        appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
+            @Override
+            public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+                    RemoteAnimationTargetCompat[] wallpaperTargets) {
 
-            // On the first call clear the reference.
-            signal.cancel();
+                // On the first call clear the reference.
+                signal.cancel();
 
-            ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
-            fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
-                    wallpaperTargets));
-            AnimatorSet anim = new AnimatorSet();
-            anim.play(fadeAnimation);
-            return anim;
+                ValueAnimator fadeAnimation = ValueAnimator.ofFloat(1, 0);
+                fadeAnimation.addUpdateListener(new RemoteFadeOutAnimationListener(appTargets,
+                        wallpaperTargets));
+                AnimatorSet anim = new AnimatorSet();
+                anim.play(fadeAnimation);
+                return anim;
+            }
         }, signal);
     }
 
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 31c1acf..2cb23f1 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -34,7 +34,8 @@
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 @TargetApi(Build.VERSION_CODES.P)
-public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat {
+public abstract class LauncherAnimationRunner implements RemoteAnimationRunnerCompat,
+        WrappedAnimationRunnerImpl {
 
     private final Handler mHandler;
     private final boolean mStartAtFrontOfQueue;
@@ -49,6 +50,10 @@
         mStartAtFrontOfQueue = startAtFrontOfQueue;
     }
 
+    public Handler getHandler() {
+        return mHandler;
+    }
+
     // Called only in R+ platform
     @BinderThread
     public void onAnimationStart(RemoteAnimationTargetCompat[] appTargets,
diff --git a/quickstep/src/com/android/launcher3/LauncherInitListener.java b/quickstep/src/com/android/launcher3/LauncherInitListener.java
index 96340b2..fbd7a8a 100644
--- a/quickstep/src/com/android/launcher3/LauncherInitListener.java
+++ b/quickstep/src/com/android/launcher3/LauncherInitListener.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3;
 
+import android.animation.AnimatorSet;
 import android.annotation.TargetApi;
 import android.content.Context;
 import android.content.Intent;
@@ -25,6 +26,7 @@
 import com.android.launcher3.util.ActivityTracker;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 
 import java.util.function.BiPredicate;
 
@@ -51,17 +53,21 @@
             // Set a one-time animation provider. After the first call, this will get cleared.
             // TODO: Probably also check the intended target id.
             CancellationSignal cancellationSignal = new CancellationSignal();
-            appTransitionManager.setRemoteAnimationProvider((appTargets, wallpaperTargets) -> {
+            appTransitionManager.setRemoteAnimationProvider(new RemoteAnimationProvider() {
+                @Override
+                public AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+                        RemoteAnimationTargetCompat[] wallpaperTargets) {
 
-                // On the first call clear the reference.
-                cancellationSignal.cancel();
-                RemoteAnimationProvider provider = mRemoteAnimationProvider;
-                mRemoteAnimationProvider = null;
+                    // On the first call clear the reference.
+                    cancellationSignal.cancel();
+                    RemoteAnimationProvider provider = mRemoteAnimationProvider;
+                    mRemoteAnimationProvider = null;
 
-                if (provider != null && launcher.getStateManager().getState().overviewUi) {
-                    return provider.createWindowAnimation(appTargets, wallpaperTargets);
+                    if (provider != null && launcher.getStateManager().getState().overviewUi) {
+                        return provider.createWindowAnimation(appTargets, wallpaperTargets);
+                    }
+                    return null;
                 }
-                return null;
             }, cancellationSignal);
         }
         launcher.deferOverlayCallbacksUntilNextResumeOrStop();
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index f691359..1b39242 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static android.util.TypedValue.COMPLEX_UNIT_DIP;
+
 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS;
 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
@@ -29,6 +31,7 @@
 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
 import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS;
 import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
@@ -49,6 +52,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Matrix;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
@@ -57,6 +61,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Pair;
+import android.util.TypedValue;
 import android.view.View;
 
 import androidx.annotation.NonNull;
@@ -69,12 +74,14 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.util.DynamicResource;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.RemoteAnimationTargets;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.util.RemoteAnimationProvider;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
 import com.android.systemui.shared.system.ActivityCompat;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -86,8 +93,6 @@
 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
 import com.android.systemui.shared.system.WindowManagerWrapper;
 
-import java.lang.ref.WeakReference;
-
 /**
  * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from
  * home and/or all-apps.  Not used for 3p launchers.
@@ -157,6 +162,7 @@
     // Strong refs to runners which are cleared when the launcher activity is destroyed
     private WrappedAnimationRunnerImpl mWallpaperOpenRunner;
     private WrappedAnimationRunnerImpl mAppLaunchRunner;
+    private WrappedAnimationRunnerImpl mKeyguardGoingAwayRunner;
 
     private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
         @Override
@@ -299,8 +305,12 @@
         if (mLauncher.isInMultiWindowMode()) {
             for (RemoteAnimationTargetCompat target : appTargets) {
                 if (target.mode == MODE_OPENING) {
-                    bounds.set(target.sourceContainerBounds);
-                    bounds.offsetTo(target.position.x, target.position.y);
+                    bounds.set(target.screenSpaceBounds);
+                    if (target.localBounds != null) {
+                        bounds.set(target.localBounds);
+                    } else {
+                        bounds.offsetTo(target.position.x, target.position.y);
+                    }
                     return bounds;
                 }
             }
@@ -460,6 +470,7 @@
         RectF targetBounds = new RectF(windowTargetBounds);
         RectF currentBounds = new RectF();
         RectF temp = new RectF();
+        Point tmpPos = new Point();
 
         AnimatorSet animatorSet = new AnimatorSet();
         ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1);
@@ -547,6 +558,13 @@
                 for (int i = appTargets.length - 1; i >= 0; i--) {
                     RemoteAnimationTargetCompat target = appTargets[i];
                     SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
+
+                    tmpPos.set(target.position.x, target.position.y);
+                    if (target.localBounds != null) {
+                        final Rect localBounds = target.localBounds;
+                        tmpPos.set(target.localBounds.left, target.localBounds.top);
+                    }
+
                     if (target.mode == MODE_OPENING) {
                         matrix.setScale(scale, scale);
                         matrix.postTranslate(transX0, transY0);
@@ -563,9 +581,9 @@
                                 .withAlpha(1f - mIconAlpha.value)
                                 .withCornerRadius(mWindowRadius.value);
                     } else {
-                        matrix.setTranslate(target.position.x, target.position.y);
+                        matrix.setTranslate(tmpPos.x, tmpPos.y);
                         builder.withMatrix(matrix)
-                                .withWindowCrop(target.sourceContainerBounds)
+                                .withWindowCrop(target.screenSpaceBounds)
                                 .withAlpha(1f);
                     }
                     builder.withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING));
@@ -612,6 +630,17 @@
                             new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner,
                                     false /* startAtFrontOfQueue */),
                             CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+
+            if (KEYGUARD_ANIMATION.get()) {
+                mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */);
+                definition.addRemoteAnimation(
+                        WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                        new RemoteAnimationAdapterCompat(
+                                new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner,
+                                        true /* startAtFrontOfQueue */),
+                                CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */));
+            }
+
             new ActivityCompat(mLauncher).registerRemoteAnimations(definition);
         }
     }
@@ -628,6 +657,7 @@
             // definition so we don't have to wait for the system gc
             mWallpaperOpenRunner = null;
             mAppLaunchRunner = null;
+            mKeyguardGoingAwayRunner = null;
         }
     }
 
@@ -662,7 +692,7 @@
                     RemoteAnimationTargetCompat target = appTargets[i];
                     params[i] = new SurfaceParams.Builder(target.leash)
                             .withAlpha(1f)
-                            .withWindowCrop(target.sourceContainerBounds)
+                            .withWindowCrop(target.screenSpaceBounds)
                             .withLayer(RemoteAnimationProvider.getLayer(target, MODE_OPENING))
                             .withCornerRadius(cornerRadius)
                             .build();
@@ -681,6 +711,7 @@
         SyncRtSurfaceTransactionApplierCompat surfaceApplier =
                 new SyncRtSurfaceTransactionApplierCompat(mDragLayer);
         Matrix matrix = new Matrix();
+        Point tmpPos = new Point();
         ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1);
         int duration = CLOSING_TRANSITION_DURATION_MS;
         float windowCornerRadius = mDeviceProfile.isMultiWindowMode
@@ -697,22 +728,28 @@
                 for (int i = appTargets.length - 1; i >= 0; i--) {
                     RemoteAnimationTargetCompat target = appTargets[i];
                     SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash);
+
+                    tmpPos.set(target.position.x, target.position.y);
+                    if (target.localBounds != null) {
+                        tmpPos.set(target.localBounds.left, target.localBounds.top);
+                    }
+
                     if (target.mode == MODE_CLOSING) {
                         matrix.setScale(mScale.value, mScale.value,
-                                target.sourceContainerBounds.centerX(),
-                                target.sourceContainerBounds.centerY());
+                                target.screenSpaceBounds.centerX(),
+                                target.screenSpaceBounds.centerY());
                         matrix.postTranslate(0, mDy.value);
-                        matrix.postTranslate(target.position.x, target.position.y);
+                        matrix.postTranslate(tmpPos.x, tmpPos.y);
                         builder.withMatrix(matrix)
                                 .withAlpha(mAlpha.value)
                                 .withCornerRadius(windowCornerRadius);
                     } else {
-                        matrix.setTranslate(target.position.x, target.position.y);
+                        matrix.setTranslate(tmpPos.x, tmpPos.y);
                         builder.withMatrix(matrix)
                                 .withAlpha(1f);
                     }
                     params[i] = builder
-                            .withWindowCrop(target.sourceContainerBounds)
+                            .withWindowCrop(target.screenSpaceBounds)
                             .withLayer(RemoteAnimationProvider.getLayer(target, MODE_CLOSING))
                             .build();
                 }
@@ -785,52 +822,6 @@
     }
 
     /**
-     * Used with WrappedLauncherAnimationRunner as an interface for the runner to call back to the
-     * implementation.
-     */
-    protected interface WrappedAnimationRunnerImpl {
-        Handler getHandler();
-        void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
-                RemoteAnimationTargetCompat[] wallpaperTargets,
-                LauncherAnimationRunner.AnimationResult result);
-    }
-
-    /**
-     * This class is needed to wrap any animation runner that is a part of the
-     * RemoteAnimationDefinition:
-     * - Launcher creates a new instance of the LauncherAppTransitionManagerImpl whenever it is
-     *   created, which in turn registers a new definition
-     * - When the definition is registered, window manager retains a strong binder reference to the
-     *   runner passed in
-     * - If the Launcher activity is recreated, the new definition registered will replace the old
-     *   reference in the system's activity record, but until the system server is GC'd, the binder
-     *   reference will still exist, which references the runner in the Launcher process, which
-     *   references the (old) Launcher activity through this class
-     *
-     * Instead we make the runner provided to the definition static only holding a weak reference to
-     * the runner implementation.  When this animation manager is destroyed, we remove the Launcher
-     * reference to the runner, leaving only the weak ref from the runner.
-     */
-    protected static class WrappedLauncherAnimationRunner<R extends WrappedAnimationRunnerImpl>
-            extends LauncherAnimationRunner {
-        private WeakReference<R> mImpl;
-
-        public WrappedLauncherAnimationRunner(R animationRunnerImpl, boolean startAtFrontOfQueue) {
-            super(animationRunnerImpl.getHandler(), startAtFrontOfQueue);
-            mImpl = new WeakReference<>(animationRunnerImpl);
-        }
-
-        @Override
-        public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
-                RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
-            R animationRunnerImpl = mImpl.get();
-            if (animationRunnerImpl != null) {
-                animationRunnerImpl.onCreateAnimation(appTargets, wallpaperTargets, result);
-            }
-        }
-    }
-
-    /**
      * Remote animation runner for animation from the app to Launcher, including recents.
      */
     protected class WallpaperOpenLauncherAnimationRunner implements WrappedAnimationRunnerImpl {
@@ -896,18 +887,14 @@
                         || mLauncher.isForceInvisible()) {
                     // Only register the content animation for cancellation when state changes
                     mLauncher.getStateManager().setCurrentAnimation(anim);
+
                     if (mFromUnlock) {
-                        Pair<AnimatorSet, Runnable> contentAnimator =
-                                getLauncherContentAnimator(false /* isAppOpening */,
-                                        new float[] {mContentTransY, 0});
-                        contentAnimator.first.setStartDelay(0);
-                        anim.play(contentAnimator.first);
-                        anim.addListener(new AnimatorListenerAdapter() {
-                            @Override
-                            public void onAnimationEnd(Animator animation) {
-                                contentAnimator.second.run();
-                            }
-                        });
+                        float velocityDpPerS = DynamicResource.provider(mLauncher)
+                                .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
+                        float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP,
+                                velocityDpPerS, mLauncher.getResources().getDisplayMetrics());
+                        anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false)
+                                .getAnimators());
                     } else {
                         createLauncherResumeAnimation(anim);
                     }
diff --git a/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
new file mode 100644
index 0000000..da2aee4
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/WrappedAnimationRunnerImpl.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import android.os.Handler;
+
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+/**
+ * Used with WrappedLauncherAnimationRunner as an interface for the runner to call back to the
+ * implementation.
+ */
+public interface WrappedAnimationRunnerImpl {
+    Handler getHandler();
+    void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets,
+            LauncherAnimationRunner.AnimationResult result);
+}
diff --git a/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
new file mode 100644
index 0000000..1753b62
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/WrappedLauncherAnimationRunner.java
@@ -0,0 +1,56 @@
+/*
+ * 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;
+
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This class is needed to wrap any animation runner that is a part of the
+ * RemoteAnimationDefinition:
+ * - Launcher creates a new instance of the LauncherAppTransitionManagerImpl whenever it is
+ *   created, which in turn registers a new definition
+ * - When the definition is registered, window manager retains a strong binder reference to the
+ *   runner passed in
+ * - If the Launcher activity is recreated, the new definition registered will replace the old
+ *   reference in the system's activity record, but until the system server is GC'd, the binder
+ *   reference will still exist, which references the runner in the Launcher process, which
+ *   references the (old) Launcher activity through this class
+ *
+ * Instead we make the runner provided to the definition static only holding a weak reference to
+ * the runner implementation.  When this animation manager is destroyed, we remove the Launcher
+ * reference to the runner, leaving only the weak ref from the runner.
+ */
+public class WrappedLauncherAnimationRunner<R extends WrappedAnimationRunnerImpl>
+        extends LauncherAnimationRunner {
+    private WeakReference<R> mImpl;
+
+    public WrappedLauncherAnimationRunner(R animationRunnerImpl, boolean startAtFrontOfQueue) {
+        super(animationRunnerImpl.getHandler(), startAtFrontOfQueue);
+        mImpl = new WeakReference<>(animationRunnerImpl);
+    }
+
+    @Override
+    public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets,
+            RemoteAnimationTargetCompat[] wallpaperTargets, AnimationResult result) {
+        R animationRunnerImpl = mImpl.get();
+        if (animationRunnerImpl != null) {
+            animationRunnerImpl.onCreateAnimation(appTargets, wallpaperTargets, result);
+        }
+    }
+}
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/PreviewSurfaceRenderer.java b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
new file mode 100644
index 0000000..548223a
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
@@ -0,0 +1,45 @@
+/*
+ * 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.uioverrides;
+
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.Size;
+import android.view.View;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
+import com.android.systemui.shared.system.SurfaceViewRequestReceiver;
+
+/** Render preview using surface view. */
+public class PreviewSurfaceRenderer {
+
+    /** Handle a received surface view request. */
+    public static void render(Context context, Bundle bundle) {
+        final String gridName = bundle.getString("name");
+        bundle.remove("name");
+        final InvariantDeviceProfile idp = new InvariantDeviceProfile(context, gridName);
+
+        MAIN_EXECUTOR.execute(() -> {
+            View view = new LauncherPreviewRenderer(context, idp).getRenderedView();
+            new SurfaceViewRequestReceiver().onReceive(context, bundle, view,
+                    new Size(view.getMeasuredWidth(), view.getMeasuredHeight()));
+        });
+    }
+}
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/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
index 6520c4f..21b97ec 100644
--- a/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
+++ b/quickstep/src/com/android/quickstep/util/RemoteAnimationProvider.java
@@ -21,21 +21,22 @@
 import android.os.Handler;
 
 import com.android.launcher3.LauncherAnimationRunner;
+import com.android.launcher3.WrappedLauncherAnimationRunner;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
 import com.android.systemui.shared.system.TransactionCompat;
 
-@FunctionalInterface
-public interface RemoteAnimationProvider {
+public abstract class RemoteAnimationProvider {
 
+    LauncherAnimationRunner mAnimationRunner;
     static final int Z_BOOST_BASE = 800570000;
 
-    AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
+    public abstract AnimatorSet createWindowAnimation(RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets);
 
-    default ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
-        LauncherAnimationRunner runner = new LauncherAnimationRunner(handler,
+    ActivityOptions toActivityOptions(Handler handler, long duration, Context context) {
+        mAnimationRunner = new LauncherAnimationRunner(handler,
                 false /* startAtFrontOfQueue */) {
 
             @Override
@@ -44,8 +45,11 @@
                 result.setAnimation(createWindowAnimation(appTargets, wallpaperTargets), context);
             }
         };
+        final LauncherAnimationRunner wrapper = new WrappedLauncherAnimationRunner(
+                mAnimationRunner, false /* startAtFrontOfQueue */);
+
         return ActivityOptionsCompat.makeRemoteAnimation(
-                new RemoteAnimationAdapterCompat(runner, duration, 0));
+                new RemoteAnimationAdapterCompat(wrapper, duration, 0));
     }
 
     /**
@@ -63,7 +67,7 @@
         }
     }
 
-    static int getLayer(RemoteAnimationTargetCompat target, int boostModeTarget) {
+    public static int getLayer(RemoteAnimationTargetCompat target, int boostModeTarget) {
         return target.mode == boostModeTarget
                 ? Z_BOOST_BASE + target.prefixOrderIndex
                 : target.prefixOrderIndex;
@@ -72,7 +76,7 @@
     /**
      * @return the target with the lowest opaque layer for a certain app animation, or null.
      */
-    static RemoteAnimationTargetCompat findLowestOpaqueLayerTarget(
+    public static RemoteAnimationTargetCompat findLowestOpaqueLayerTarget(
             RemoteAnimationTargetCompat[] appTargets, int mode) {
         int lowestLayer = Integer.MAX_VALUE;
         int lowestLayerIndex = -1;
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index 4ac815e..0afe4a8 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -166,12 +166,9 @@
             return false;
         }
 
-        setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
-                overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
-        setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
-                overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
-        setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
-                overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+        Log.d(TAG, "setActiveOverlay: " + overlayPackage + "...");
+        UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+                "cmd overlay enable-exclusive " + overlayPackage);
 
         if (currentSysUiNavigationMode() != expectedMode) {
             final CountDownLatch latch = new CountDownLatch(1);
@@ -210,14 +207,6 @@
         return true;
     }
 
-    private static void setOverlayPackageEnabled(String overlayPackage, boolean enable)
-            throws Exception {
-        Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
-        final String action = enable ? "enable" : "disable";
-        UiDevice.getInstance(getInstrumentation()).executeShellCommand(
-                "cmd overlay " + action + " " + overlayPackage);
-    }
-
     private static boolean packageExists(String packageName) {
         try {
             PackageManager pm = getInstrumentation().getContext().getPackageManager();
diff --git a/res/values/config.xml b/res/values/config.xml
index df0f233..6c239bd 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -142,6 +142,7 @@
 
     <item name="staggered_damping_ratio" type="dimen" format="float">0.7</item>
     <item name="staggered_stiffness" type="dimen" format="float">150</item>
+    <dimen name="unlock_staggered_velocity_dp_per_s">3dp</dimen>
 
     <!-- Swipe up to home related -->
     <dimen name="swipe_up_fling_min_visible_change">18dp</dimen>
@@ -170,6 +171,7 @@
 
         <item>@dimen/staggered_damping_ratio</item>
         <item>@dimen/staggered_stiffness</item>
+        <item>@dimen/unlock_staggered_velocity_dp_per_s</item>
 
         <item>@dimen/swipe_up_fling_min_visible_change</item>
         <item>@dimen/swipe_up_y_overshoot</item>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9d0fb56..7748b3a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -208,7 +208,7 @@
     <string name="notification_dots_service_title">Show notification dots</string>
 
     <!-- Label for the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=40] -->
-    <string name="auto_add_shortcuts_label">Add icon to Home screen</string>
+    <string name="auto_add_shortcuts_label">Add app icons to Home screen</string>
     <!-- Text description of the setting that allows the automatic placement of launcher shortcuts for applications and games installed on the device [CHAR LIMIT=NONE] -->
     <string name="auto_add_shortcuts_description">For new apps</string>
 
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
new file mode 100644
index 0000000..8f58d8b
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.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.model;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+import static com.android.launcher3.LauncherSettings.Favorites.TMP_CONTENT_URI;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
+import static com.android.launcher3.util.LauncherModelHelper.DESKTOP;
+import static com.android.launcher3.util.LauncherModelHelper.HOTSEAT;
+import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
+import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Point;
+import android.os.Process;
+
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.LauncherModelHelper;
+import com.android.launcher3.util.LauncherRoboTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.HashSet;
+
+/** Unit tests for {@link GridSizeMigrationTaskV2} */
+@RunWith(LauncherRoboTestRunner.class)
+public class GridSizeMigrationTaskV2Test {
+
+    private LauncherModelHelper mModelHelper;
+    private Context mContext;
+    private SQLiteDatabase mDb;
+
+    private HashSet<String> mValidPackages;
+    private InvariantDeviceProfile mIdp;
+
+    @Before
+    public void setUp() {
+        mModelHelper = new LauncherModelHelper();
+        mContext = RuntimeEnvironment.application;
+        mDb = mModelHelper.provider.getDb();
+
+        mValidPackages = new HashSet<>();
+        mValidPackages.add(TEST_PACKAGE);
+        mIdp = InvariantDeviceProfile.INSTANCE.get(mContext);
+
+        long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
+                Process.myUserHandle());
+        dropTable(mDb, LauncherSettings.Favorites.TMP_TABLE);
+        LauncherSettings.Favorites.addTableToDb(mDb, userSerial, false,
+                LauncherSettings.Favorites.TMP_TABLE);
+    }
+
+    @Test
+    public void testMigration() {
+        final String testPackage1 = "com.android.launcher3.validpackage1";
+        final String testPackage2 = "com.android.launcher3.validpackage2";
+        final String testPackage3 = "com.android.launcher3.validpackage3";
+        final String testPackage4 = "com.android.launcher3.validpackage4";
+        final String testPackage5 = "com.android.launcher3.validpackage5";
+        final String testPackage7 = "com.android.launcher3.validpackage7";
+
+        mValidPackages.add(testPackage1);
+        mValidPackages.add(testPackage2);
+        mValidPackages.add(testPackage3);
+        mValidPackages.add(testPackage4);
+        mValidPackages.add(testPackage5);
+        mValidPackages.add(testPackage7);
+
+        int[] srcHotseatItems = {
+                mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
+                mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
+                -1,
+                mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
+                mModelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI),
+        };
+        mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage5, 5, TMP_CONTENT_URI);
+
+        int[] destHotseatItems = {
+                -1,
+                mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2),
+                -1,
+        };
+        mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage7);
+
+        mIdp.numHotseatIcons = 3;
+        mIdp.numColumns = 3;
+        mIdp.numRows = 3;
+        GridSizeMigrationTaskV2.DbReader srcReader = new GridSizeMigrationTaskV2.DbReader(mDb,
+                LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages, 5);
+        GridSizeMigrationTaskV2.DbReader destReader = new GridSizeMigrationTaskV2.DbReader(mDb,
+                LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages, 3);
+        GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
+                destReader, 3, new Point(mIdp.numColumns, mIdp.numRows));
+        task.migrate();
+
+        Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
+                "container=" + CONTAINER_HOTSEAT, null, null, null);
+        assertEquals(c.getCount(), 3);
+        int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
+        int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
+        c.moveToNext();
+        assertEquals(c.getInt(screenIndex), 1);
+        assertTrue(c.getString(intentIndex).contains(testPackage2));
+        c.moveToNext();
+        assertEquals(c.getInt(screenIndex), 0);
+        assertTrue(c.getString(intentIndex).contains(testPackage1));
+        c.moveToNext();
+        assertEquals(c.getInt(screenIndex), 2);
+        assertTrue(c.getString(intentIndex).contains(testPackage3));
+        c.close();
+
+        c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
+                new String[]{LauncherSettings.Favorites.CELLX, LauncherSettings.Favorites.CELLY,
+                        LauncherSettings.Favorites.INTENT},
+                "container=" + CONTAINER_DESKTOP, null, null, null);
+        assertEquals(c.getCount(), 2);
+        intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
+        int cellXIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLX);
+        int cellYIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLY);
+
+        c.moveToNext();
+        assertTrue(c.getString(intentIndex).contains(testPackage7));
+        c.moveToNext();
+        assertTrue(c.getString(intentIndex).contains(testPackage5));
+        assertEquals(c.getInt(cellXIndex), 0);
+        assertEquals(c.getInt(cellYIndex), 2);
+    }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
index e133cf2..20b1453 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -230,7 +230,21 @@
     }
 
     public int addItem(int type, int screen, int container, int x, int y) {
-        return addItem(type, screen, container, x, y, mDefaultProfileId);
+        return addItem(type, screen, container, x, y, mDefaultProfileId, TEST_PACKAGE);
+    }
+
+    public int addItem(int type, int screen, int container, int x, int y, long profileId) {
+        return addItem(type, screen, container, x, y, profileId, TEST_PACKAGE);
+    }
+
+    public int addItem(int type, int screen, int container, int x, int y, String packageName) {
+        return addItem(type, screen, container, x, y, mDefaultProfileId, packageName);
+    }
+
+    public int addItem(int type, int screen, int container, int x, int y, String packageName,
+            int id, Uri contentUri) {
+        addItem(type, screen, container, x, y, mDefaultProfileId, packageName, id, contentUri);
+        return id;
     }
 
     /**
@@ -238,11 +252,19 @@
      * @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
      *             folder (where the type represents the number of items in the folder).
      */
-    public int addItem(int type, int screen, int container, int x, int y, long profileId) {
+    public int addItem(int type, int screen, int container, int x, int y, long profileId,
+            String packageName) {
         Context context = RuntimeEnvironment.application;
         int id = LauncherSettings.Settings.call(context.getContentResolver(),
                 LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
                 .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+        addItem(type, screen, container, x, y, profileId, packageName, id, CONTENT_URI);
+        return id;
+    }
+
+    public void addItem(int type, int screen, int container, int x, int y, long profileId,
+            String packageName, int id, Uri contentUri) {
+        Context context = RuntimeEnvironment.application;
 
         ContentValues values = new ContentValues();
         values.put(LauncherSettings.Favorites._ID, id);
@@ -257,7 +279,7 @@
         if (type == APP_ICON || type == SHORTCUT) {
             values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
             values.put(LauncherSettings.Favorites.INTENT,
-                    new Intent(Intent.ACTION_MAIN).setPackage(TEST_PACKAGE).toUri(0));
+                    new Intent(Intent.ACTION_MAIN).setPackage(packageName).toUri(0));
         } else {
             values.put(LauncherSettings.Favorites.ITEM_TYPE,
                     LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
@@ -267,8 +289,7 @@
             }
         }
 
-        context.getContentResolver().insert(CONTENT_URI, values);
-        return id;
+        context.getContentResolver().insert(contentUri, values);
     }
 
     public int[][][] createGrid(int[][][] typeArray) {
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/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 697048a..a699c32 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
+import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
@@ -164,8 +165,11 @@
             return false;
         }
 
-        mOpenHelper.close();
+        DatabaseHelper oldHelper = mOpenHelper;
         mOpenHelper = new DatabaseHelper(getContext());
+        copyTable(oldHelper.getReadableDatabase(), Favorites.TABLE_NAME,
+                mOpenHelper.getWritableDatabase(), Favorites.TMP_TABLE, getContext());
+        oldHelper.close();
         return true;
     }
 
diff --git a/src/com/android/launcher3/LauncherSettings.java b/src/com/android/launcher3/LauncherSettings.java
index 216c221..f516446 100644
--- a/src/com/android/launcher3/LauncherSettings.java
+++ b/src/com/android/launcher3/LauncherSettings.java
@@ -103,6 +103,11 @@
         public static final String PREVIEW_TABLE_NAME = "favorites_preview";
 
         /**
+         * Temporary table used specifically for multi-db grid migrations
+         */
+        public static final String TMP_TABLE = "favorites_tmp";
+
+        /**
          * The content:// style URL for "favorites" table
          */
         public static final Uri CONTENT_URI = Uri.parse("content://"
@@ -115,6 +120,12 @@
                 + LauncherProvider.AUTHORITY + "/" + PREVIEW_TABLE_NAME);
 
         /**
+         * The content:// style URL for "favorites_tmp" table
+         */
+        public static final Uri TMP_CONTENT_URI = Uri.parse("content://"
+                + LauncherProvider.AUTHORITY + "/" + TMP_TABLE);
+
+        /**
          * The content:// style URL for a given row, identified by its id.
          *
          * @param id The row id.
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..f12789a 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
@@ -84,6 +77,9 @@
                 }
             };
 
+    // Progress factor after which an animation is considered almost completed.
+    private static final float ANIMATION_COMPLETE_THRESHOLD = 0.95f;
+
     private final ValueAnimator mAnimationPlayer;
     private final long mDuration;
 
@@ -96,8 +92,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;
 
@@ -217,6 +213,16 @@
     }
 
     /**
+     * Tries to finish the running animation if it is close to completion.
+     */
+    public void forceFinishIfCloseToEnd() {
+        if (mAnimationPlayer.isRunning()
+                && mAnimationPlayer.getAnimatedFraction() > ANIMATION_COMPLETE_THRESHOLD) {
+            mAnimationPlayer.end();
+        }
+    }
+
+    /**
      * Pauses the currently playing animation.
      */
     public void pause() {
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..a95a5e1 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,22 +40,28 @@
  *
  * 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
      */
-    public void add(Animator anim, TimeInterpolator interpolator) {
-        add(anim, interpolator, SpringProperty.DEFAULT);
-    }
-
     public void add(Animator anim, TimeInterpolator interpolator, SpringProperty springProperty) {
         anim.setInterpolator(interpolator);
         add(anim, springProperty);
@@ -63,8 +72,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 +83,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/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index af769c8..0237b50 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -83,6 +83,9 @@
     public static final BooleanFlag UNSTABLE_SPRINGS = getDebugFlag(
             "UNSTABLE_SPRINGS", false, "Enable unstable springs for quickstep animations");
 
+    public static final BooleanFlag KEYGUARD_ANIMATION = getDebugFlag(
+            "KEYGUARD_ANIMATION", false, "Enable animation for keyguard going away on wallpaper");
+
     public static final BooleanFlag ADAPTIVE_ICON_WINDOW_ANIM = getDebugFlag(
             "ADAPTIVE_ICON_WINDOW_ANIM", true, "Use adaptive icons for window animations.");
 
@@ -103,8 +106,8 @@
             "APP_SEARCH_IMPROVEMENTS", true,
             "Adds localized title and keyword search and ranking");
 
-    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = new DeviceFlag(
-            "ENABLE_PREDICTION_DISMISS", true, "Allow option to dimiss apps from predicted list");
+    public static final BooleanFlag ENABLE_PREDICTION_DISMISS = getDebugFlag(
+            "ENABLE_PREDICTION_DISMISS", false, "Allow option to dimiss apps from predicted list");
 
     public static final BooleanFlag ENABLE_QUICK_CAPTURE_GESTURE = getDebugFlag(
             "ENABLE_QUICK_CAPTURE_GESTURE", true, "Swipe from right to left to quick capture");
@@ -117,11 +120,11 @@
             "ASSISTANT_GIVES_LAUNCHER_FOCUS", false,
             "Allow Launcher to handle nav bar gestures while Assistant is running over it");
 
-    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
+    public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = getDebugFlag(
             "ENABLE_HYBRID_HOTSEAT", false, "Fill gaps in hotseat with predicted apps");
 
-    public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = new DeviceFlag(
-            "HOTSEAT_MIGRATE_NEW_PAGE", true,
+    public static final BooleanFlag HOTSEAT_MIGRATE_NEW_PAGE = getDebugFlag(
+            "HOTSEAT_MIGRATE_NEW_PAGE", false,
             "Migrates hotseat to a new workspace page instead of same page");
 
     public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
@@ -131,13 +134,18 @@
             "MULTI_DB_GRID_MIRATION_ALGO", false, "Use the multi-db grid migration algorithm");
 
     public static final BooleanFlag ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER = getDebugFlag(
-            "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true,
-            "Show launcher preview in grid picker");
+            "ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER", true, "Show launcher preview in grid picker");
+
+    public static final BooleanFlag USE_SURFACE_VIEW_FOR_GRID_PREVIEW = getDebugFlag(
+            "USE_SURFACE_VIEW_FOR_GRID_PREVIEW", false, "Use surface view for grid preview");
 
     public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
             "ENABLE_OVERVIEW_ACTIONS", true, "Show app actions instead of the shelf in Overview."
             + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
 
+    public static final BooleanFlag ENABLE_SELECT_MODE = getDebugFlag(
+            "ENABLE_SELECT_MODE", true, "Show Select Mode button in Overview Actions");
+
     public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
             "ENABLE_DATABASE_RESTORE", true,
             "Enable database restore when new restore session is created");
diff --git a/src/com/android/launcher3/graphics/GridOptionsProvider.java b/src/com/android/launcher3/graphics/GridOptionsProvider.java
index 71b4366..607aba9 100644
--- a/src/com/android/launcher3/graphics/GridOptionsProvider.java
+++ b/src/com/android/launcher3/graphics/GridOptionsProvider.java
@@ -1,5 +1,6 @@
 package com.android.launcher3.graphics;
 
+import static com.android.launcher3.config.FeatureFlags.USE_SURFACE_VIEW_FOR_GRID_PREVIEW;
 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.content.ContentProvider;
@@ -19,6 +20,7 @@
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.InvariantDeviceProfile.GridOption;
 import com.android.launcher3.R;
+import com.android.launcher3.uioverrides.PreviewSurfaceRenderer;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -62,6 +64,11 @@
     private static final String KEY_PREVIEW = "preview";
     private static final String MIME_TYPE_PNG = "image/png";
 
+    private static final String METHOD_GET_PREVIEW = "get_preview";
+    private static final String METADATA_KEY_PREVIEW_VERSION = "preview_version";
+
+
+
     public static final PipeDataWriter<Future<Bitmap>> BITMAP_WRITER =
             new PipeDataWriter<Future<Bitmap>>() {
                 @Override
@@ -98,6 +105,10 @@
                     .add(KEY_IS_DEFAULT, idp.numColumns == gridOption.numColumns
                             && idp.numRows == gridOption.numRows);
         }
+        Bundle metadata = new Bundle();
+        metadata.putString(METADATA_KEY_PREVIEW_VERSION,
+                USE_SURFACE_VIEW_FOR_GRID_PREVIEW.get() ? "V2" : "V1");
+        cursor.setExtras(metadata);
         return cursor;
     }
 
@@ -188,4 +199,14 @@
             throw new FileNotFoundException(e.getMessage());
         }
     }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras)  {
+        if (!METHOD_GET_PREVIEW.equals(method)) {
+            return null;
+        }
+
+        PreviewSurfaceRenderer.render(getContext(), extras);
+        return null;
+    }
 }
diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
index 02e98e8..5bc6610 100644
--- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
+++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java
@@ -262,6 +262,13 @@
         });
     }
 
+    /** Populate preview and render it. */
+    public View getRenderedView() {
+        MainThreadRenderer renderer = new MainThreadRenderer(mContext);
+        renderer.populate();
+        return renderer.mRootView;
+    }
+
     private class MainThreadRenderer extends ContextThemeWrapper
             implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 {
 
@@ -388,7 +395,7 @@
             }
         }
 
-        private void renderScreenShot(Canvas canvas) {
+        private void populate() {
             if (ENABLE_LAUNCHER_PREVIEW_IN_GRID_PICKER.get()) {
                 boolean needsToMigrate = needsToMigrate(mContext, mIdp);
                 boolean success = false;
@@ -499,7 +506,10 @@
             measureView(mRootView, mDp.widthPx, mDp.heightPx);
             // Additional measure for views which use auto text size API
             measureView(mRootView, mDp.widthPx, mDp.heightPx);
+        }
 
+        private void renderScreenShot(Canvas canvas) {
+            populate();
             mRootView.draw(canvas);
             dispatchVisibilityAggregated(mRootView, false);
         }
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 83349bc..2c7f891 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.ACTION_SCREEN_OFF;
 import static android.content.Intent.ACTION_USER_PRESENT;
 
+import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
 import android.animation.ObjectAnimator;
@@ -81,6 +82,10 @@
                 }
             };
 
+    /**
+     * Receiver used to get a signal that the user unlocked their device.
+     * @see KEYGUARD_ANIMATION For proper signal.
+     */
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -164,8 +169,10 @@
                 mSysUiAnimMultiplier = 0;
                 reapplySysUiAlphaNoInvalidate();
 
-                animateToSysuiMultiplier(1, 600,
-                        mLauncher.getWindow().getTransitionBackgroundFadeDuration());
+                ObjectAnimator oa = createSysuiMultiplierAnim(1);
+                oa.setDuration(600);
+                oa.setStartDelay(mLauncher.getWindow().getTransitionBackgroundFadeDuration());
+                oa.start();
                 mAnimateScrimOnNextDraw = false;
             }
 
@@ -178,13 +185,13 @@
         }
     }
 
-    public void animateToSysuiMultiplier(float toMultiplier, long duration,
-            long startDelay) {
-        ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, toMultiplier);
+    /**
+     * @return an ObjectAnimator that controls the fade in/out of the sys ui scrim.
+     */
+    public ObjectAnimator createSysuiMultiplierAnim(float... values) {
+        ObjectAnimator anim = ObjectAnimator.ofFloat(this, SYSUI_ANIM_MULTIPLIER, values);
         anim.setAutoCancel(true);
-        anim.setDuration(duration);
-        anim.setStartDelay(startDelay);
-        anim.start();
+        return anim;
     }
 
     public void onInsetsChanged(Rect insets) {
@@ -197,7 +204,7 @@
     public void onViewAttachedToWindow(View view) {
         super.onViewAttachedToWindow(view);
 
-        if (mTopScrim != null) {
+        if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
             IntentFilter filter = new IntentFilter(ACTION_SCREEN_OFF);
             filter.addAction(ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
             mRoot.getContext().registerReceiver(mReceiver, filter);
@@ -207,7 +214,7 @@
     @Override
     public void onViewDetachedFromWindow(View view) {
         super.onViewDetachedFromWindow(view);
-        if (mTopScrim != null) {
+        if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
             mRoot.getContext().unregisterReceiver(mReceiver);
         }
     }
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 3770d17..89077ee 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -143,7 +143,7 @@
             fillIntentInfo(itemTarget, intent, userHandle);
         }
         LauncherEvent event = newLauncherEvent(action,  targets);
-        ItemInfo info = (ItemInfo) v.getTag();
+        ItemInfo info = v == null ? null : (ItemInfo) v.getTag();
         if (info != null && Utilities.IS_DEBUG_DEVICE && FeatureFlags.ENABLE_HYBRID_HOTSEAT.get()) {
             FileLog.d(TAG, "appLaunch: packageName:" + info.getTargetComponent().getPackageName()
                     + ",isWorkApp:" + (info.user != null && !Process.myUserHandle().equals(
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
index 3ba740d..a084600 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTask.java
@@ -936,8 +936,8 @@
 
             boolean dbChanged = false;
             if (migrateForPreview) {
-                copyTable(transaction.getDb(), Favorites.TABLE_NAME, Favorites.PREVIEW_TABLE_NAME,
-                        context);
+                copyTable(transaction.getDb(), Favorites.TABLE_NAME, transaction.getDb(),
+                        Favorites.PREVIEW_TABLE_NAME, context);
             }
 
             GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(),
@@ -950,10 +950,11 @@
 
             HashSet<String> validPackages = getValidPackages(context);
             // Hotseat.
-            if (srcHotseatCount != idp.numHotseatIcons) {
-                // Migrate hotseat.
-                dbChanged = new GridSizeMigrationTask(context, transaction.getDb(), validPackages,
-                        migrateForPreview, srcHotseatCount, idp.numHotseatIcons).migrateHotseat();
+            if (srcHotseatCount != idp.numHotseatIcons
+                    && new GridSizeMigrationTask(context, transaction.getDb(), validPackages,
+                            migrateForPreview, srcHotseatCount,
+                            idp.numHotseatIcons).migrateHotseat()) {
+                dbChanged = true;
             }
 
             // Grid size
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index 197b29c..0bdccfa 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -16,9 +16,46 @@
 
 package com.android.launcher3.model;
 
+import static com.android.launcher3.Utilities.getPointString;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
+
+import android.content.ComponentName;
+import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.graphics.Point;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
 
 import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.graphics.LauncherPreviewRenderer;
+import com.android.launcher3.pm.InstallSessionHelper;
+import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.widget.WidgetManagerHelper;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@@ -26,14 +63,63 @@
  */
 public class GridSizeMigrationTaskV2 {
 
-    private GridSizeMigrationTaskV2(Context context) {
+    public static final String KEY_MIGRATION_SRC_WORKSPACE_SIZE = "migration_src_workspace_size";
+    public static final String KEY_MIGRATION_SRC_HOTSEAT_COUNT = "migration_src_hotseat_count";
 
+    private static final String TAG = "GridSizeMigrationTaskV2";
+    private static final boolean DEBUG = true;
+
+    private final Context mContext;
+    private final SQLiteDatabase mDb;
+    private final DbReader mSrcReader;
+    private final DbReader mDestReader;
+
+    private final List<DbEntry> mHotseatItems;
+    private final List<DbEntry> mWorkspaceItems;
+
+    private final List<DbEntry> mHotseatDiff;
+    private final List<DbEntry> mWorkspaceDiff;
+
+    private final int mDestHotseatSize;
+    private final int mTrgX, mTrgY;
+
+    @VisibleForTesting
+    protected GridSizeMigrationTaskV2(Context context, SQLiteDatabase db, DbReader srcReader,
+            DbReader destReader, int destHotseatSize, Point targetSize) {
+        mContext = context;
+        mDb = db;
+        mSrcReader = srcReader;
+        mDestReader = destReader;
+
+        mHotseatItems = destReader.loadHotseatEntries();
+        mWorkspaceItems = destReader.loadAllWorkspaceEntries();
+
+        mHotseatDiff = calcDiff(mSrcReader.loadHotseatEntries(), mHotseatItems);
+        mWorkspaceDiff = calcDiff(mSrcReader.loadAllWorkspaceEntries(), mWorkspaceItems);
+        mDestHotseatSize = destHotseatSize;
+
+        mTrgX = targetSize.x;
+        mTrgY = targetSize.y;
+    }
+
+    /**
+     * Check given a new IDP, if migration is necessary.
+     */
+    public static boolean needsToMigrate(Context context, InvariantDeviceProfile idp) {
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
+
+        return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
+                || idp.numHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+                idp.numHotseatIcons);
     }
 
     /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
     public static boolean migrateGridIfNeeded(Context context) {
-        // To be implemented.
-        return true;
+        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
+            return true;
+        }
+        return migrateGridIfNeeded(context, null);
     }
 
     /**
@@ -43,7 +129,608 @@
      * @return false if the migration failed.
      */
     public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
-        // To be implemented.
+        boolean migrateForPreview = idp != null;
+        if (!migrateForPreview) {
+            idp = LauncherAppState.getIDP(context);
+        }
+
+        if (!needsToMigrate(context, idp)) {
+            return true;
+        }
+
+        SharedPreferences prefs = Utilities.getPrefs(context);
+        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
+
+        if (gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
+                && idp.numHotseatIcons == prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
+                idp.numHotseatIcons)) {
+            // Skip if workspace and hotseat sizes have not changed.
+            return true;
+        }
+
+        HashSet<String> validPackages = getValidPackages(context);
+        int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons);
+
+        if (!LauncherSettings.Settings.call(
+                context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER).getBoolean(
+                LauncherSettings.Settings.EXTRA_VALUE)) {
+            return false;
+        }
+
+        long migrationStartTime = System.currentTimeMillis();
+        try (SQLiteTransaction t = (SQLiteTransaction) LauncherSettings.Settings.call(
+                context.getContentResolver(),
+                LauncherSettings.Settings.METHOD_NEW_TRANSACTION).getBinder(
+                LauncherSettings.Settings.EXTRA_VALUE)) {
+
+            DbReader srcReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TMP_TABLE,
+                    context, validPackages, srcHotseatCount);
+            DbReader destReader = new DbReader(t.getDb(), LauncherSettings.Favorites.TABLE_NAME,
+                    context, validPackages, idp.numHotseatIcons);
+
+            Point targetSize = new Point(idp.numColumns, idp.numRows);
+            GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(context, t.getDb(),
+                    srcReader, destReader, idp.numHotseatIcons, targetSize);
+            task.migrate();
+
+            dropTable(t.getDb(), LauncherSettings.Favorites.TMP_TABLE);
+
+            t.commit();
+            return true;
+        } catch (Exception e) {
+            Log.e(TAG, "Error during grid migration", e);
+
+            return false;
+        } finally {
+            Log.v(TAG, "Workspace migration completed in "
+                    + (System.currentTimeMillis() - migrationStartTime));
+
+            // Save current configuration, so that the migration does not run again.
+            prefs.edit()
+                    .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
+                    .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numHotseatIcons)
+                    .apply();
+        }
+    }
+
+    @VisibleForTesting
+    protected boolean migrate() {
+        if (mHotseatDiff.isEmpty() && mWorkspaceDiff.isEmpty()) {
+            return false;
+        }
+
+        // Migrate hotseat
+        HotseatPlacementSolution hotseatSolution = new HotseatPlacementSolution(mDb, mSrcReader,
+                mContext, mDestHotseatSize, mHotseatItems, mHotseatDiff);
+        hotseatSolution.find();
+
+        // Sort the items by the reading order.
+        Collections.sort(mWorkspaceDiff);
+
+        // Migrate workspace.
+        for (int screenId = 0; screenId <= mDestReader.mLastScreenId; screenId++) {
+            if (DEBUG) {
+                Log.d(TAG, "Migrating " + screenId);
+            }
+            List<DbEntry> entries = mDestReader.loadWorkspaceEntries(screenId);
+            GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
+                    mContext, entries, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+            workspaceSolution.find();
+            if (mWorkspaceDiff.isEmpty()) {
+                break;
+            }
+        }
+
+        int screenId = mDestReader.mLastScreenId + 1;
+        while (!mWorkspaceDiff.isEmpty()) {
+            GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
+                    mContext, new ArrayList<>(), screenId, mTrgX, mTrgY, mWorkspaceDiff);
+            workspaceSolution.find();
+            screenId++;
+        }
         return true;
     }
+
+    /** Return what's in the src but not in the dest */
+    private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
+        Set<String> destSet = dest.parallelStream().map(DbEntry::getIntentStr).collect(
+                Collectors.toSet());
+        List<DbEntry> diff = new ArrayList<>();
+        for (DbEntry entry : src) {
+            if (!destSet.contains(entry.mIntent)) {
+                diff.add(entry);
+            }
+        }
+        return diff;
+    }
+
+    private static void insertEntryInDb(SQLiteDatabase db, Context context,
+            ArrayList<DbEntry> entriesFromSrcDb, DbEntry entry) {
+        int id = -1;
+        switch (entry.itemType) {
+            case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+            case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+            case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
+                for (DbEntry e : entriesFromSrcDb) {
+                    if (TextUtils.equals(e.mIntent, entry.mIntent)) {
+                        id = e.id;
+                    }
+                }
+
+                break;
+            }
+            case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
+                for (DbEntry e : entriesFromSrcDb) {
+                    if (e.mFolderItems.size() == entry.mFolderItems.size()
+                            && e.mFolderItems.containsAll(entry.mFolderItems)) {
+                        id = e.id;
+                    }
+                }
+                break;
+            }
+            case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+            case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: {
+                for (DbEntry e : entriesFromSrcDb) {
+                    if (TextUtils.equals(e.mProvider, entry.mProvider)) {
+                        id = e.id;
+                        break;
+                    }
+                }
+                break;
+            }
+            default:
+                return;
+        }
+
+        Cursor c = db.query(LauncherSettings.Favorites.TMP_TABLE, null,
+                LauncherSettings.Favorites._ID + " = '" + id + "'", null, null, null, null);
+
+        while (c.moveToNext()) {
+            ContentValues values = new ContentValues();
+            DatabaseUtils.cursorRowToContentValues(c, values);
+            entry.updateContentValues(values);
+            values.put(LauncherSettings.Favorites._ID,
+                    LauncherSettings.Settings.call(context.getContentResolver(),
+                            LauncherSettings.Settings.METHOD_NEW_ITEM_ID).getInt(
+                            LauncherSettings.Settings.EXTRA_VALUE));
+            db.insert(LauncherSettings.Favorites.TABLE_NAME, null, values);
+        }
+        c.close();
+    }
+
+    private static void removeEntryFromDb(SQLiteDatabase db, IntArray entryId) {
+        db.delete(LauncherSettings.Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
+                LauncherSettings.Favorites._ID, entryId), null);
+    }
+
+    private static HashSet<String> getValidPackages(Context context) {
+        // Initialize list of valid packages. This contain all the packages which are already on
+        // the device and packages which are being installed. Any item which doesn't belong to
+        // this set is removed.
+        // Since the loader removes such items anyway, removing these items here doesn't cause
+        // any extra data loss and gives us more free space on the grid for better migration.
+        HashSet<String> validPackages = new HashSet<>();
+        for (PackageInfo info : context.getPackageManager()
+                .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
+            validPackages.add(info.packageName);
+        }
+        InstallSessionHelper.INSTANCE.get(context)
+                .getActiveSessions().keySet()
+                .forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName));
+        return validPackages;
+    }
+
+    protected static class GridPlacementSolution {
+
+        private final SQLiteDatabase mDb;
+        private final DbReader mSrcReader;
+        private final Context mContext;
+        private final GridOccupancy mOccupied;
+        private final int mScreenId;
+        private final int mTrgX;
+        private final int mTrgY;
+        private final List<DbEntry> mItemsToPlace;
+
+        private int mNextStartX;
+        private int mNextStartY;
+
+        GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context,
+                List<DbEntry> placedWorkspaceItems, int screenId, int trgX,
+                int trgY, List<DbEntry> itemsToPlace) {
+            mDb = db;
+            mSrcReader = srcReader;
+            mContext = context;
+            mOccupied = new GridOccupancy(trgX, trgY);
+            mScreenId = screenId;
+            mTrgX = trgX;
+            mTrgY = trgY;
+            mNextStartX = 0;
+            mNextStartY = mTrgY - 1;
+            for (DbEntry entry : placedWorkspaceItems) {
+                mOccupied.markCells(entry, true);
+            }
+            mItemsToPlace = itemsToPlace;
+        }
+
+        public void find() {
+            Iterator<DbEntry> iterator = mItemsToPlace.iterator();
+            while (iterator.hasNext()) {
+                final DbEntry entry = iterator.next();
+                if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
+                    iterator.remove();
+                    continue;
+                }
+                if (findPlacement(entry)) {
+                    insertEntryInDb(mDb, mContext, mSrcReader.mWorkspaceEntries, entry);
+                    iterator.remove();
+                }
+            }
+        }
+
+        private boolean findPlacement(DbEntry entry) {
+            for (int y = mNextStartY; y >= 0; y--) {
+                for (int x = mNextStartX; x < mTrgX; x++) {
+                    boolean fits = mOccupied.isRegionVacant(x, y, entry.spanX, entry.spanY);
+                    boolean minFits = mOccupied.isRegionVacant(x, y, entry.minSpanX,
+                            entry.minSpanY);
+                    if (minFits) {
+                        entry.spanX = entry.minSpanX;
+                        entry.spanY = entry.minSpanY;
+                    }
+                    if (fits || minFits) {
+                        entry.screenId = mScreenId;
+                        entry.cellX = x;
+                        entry.cellY = y;
+                        mOccupied.markCells(entry, true);
+                        mNextStartX = x + entry.spanX;
+                        mNextStartY = y;
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    protected static class HotseatPlacementSolution {
+
+        private final SQLiteDatabase mDb;
+        private final DbReader mSrcReader;
+        private final Context mContext;
+        private final HotseatOccupancy mOccupied;
+        private final List<DbEntry> mItemsToPlace;
+
+        HotseatPlacementSolution(SQLiteDatabase db, DbReader srcReader, Context context,
+                int hotseatSize, List<DbEntry> placedHotseatItems, List<DbEntry> itemsToPlace) {
+            mDb = db;
+            mSrcReader = srcReader;
+            mContext = context;
+            mOccupied = new HotseatOccupancy(hotseatSize);
+            for (DbEntry entry : placedHotseatItems) {
+                mOccupied.markCells(entry, true);
+            }
+            mItemsToPlace = itemsToPlace;
+        }
+
+        public void find() {
+            for (int i = 0; i < mOccupied.mCells.length; i++) {
+                if (!mOccupied.mCells[i] && !mItemsToPlace.isEmpty()) {
+                    DbEntry entry = mItemsToPlace.remove(0);
+                    entry.screenId = i;
+                    // These values does not affect the item position, but we should set them
+                    // to something other than -1.
+                    entry.cellX = i;
+                    entry.cellY = 0;
+                    insertEntryInDb(mDb, mContext, mSrcReader.mHotseatEntries, entry);
+                    mOccupied.markCells(entry, true);
+                }
+            }
+        }
+
+        private class HotseatOccupancy {
+
+            private final boolean[] mCells;
+
+            private HotseatOccupancy(int hotseatSize) {
+                mCells = new boolean[hotseatSize];
+            }
+
+            private void markCells(ItemInfo item, boolean value) {
+                mCells[item.screenId] = value;
+            }
+        }
+    }
+
+    protected static class DbReader {
+
+        private final SQLiteDatabase mDb;
+        private final String mTableName;
+        private final Context mContext;
+        private final HashSet<String> mValidPackages;
+        private final int mHotseatSize;
+        private int mLastScreenId = -1;
+
+        private final ArrayList<DbEntry> mHotseatEntries = new ArrayList<>();
+        private final ArrayList<DbEntry> mWorkspaceEntries = new ArrayList<>();
+
+        DbReader(SQLiteDatabase db, String tableName, Context context,
+                HashSet<String> validPackages, int hotseatSize) {
+            mDb = db;
+            mTableName = tableName;
+            mContext = context;
+            mValidPackages = validPackages;
+            mHotseatSize = hotseatSize;
+        }
+
+        protected ArrayList<DbEntry> loadHotseatEntries() {
+            Cursor c = queryWorkspace(
+                    new String[]{
+                            LauncherSettings.Favorites._ID,                  // 0
+                            LauncherSettings.Favorites.ITEM_TYPE,            // 1
+                            LauncherSettings.Favorites.INTENT,               // 2
+                            LauncherSettings.Favorites.SCREEN},              // 3
+                    LauncherSettings.Favorites.CONTAINER + " = "
+                            + LauncherSettings.Favorites.CONTAINER_HOTSEAT);
+
+            final int indexId = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+            final int indexItemType = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+            final int indexIntent = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+            final int indexScreen = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+
+            IntArray entriesToRemove = new IntArray();
+            while (c.moveToNext()) {
+                DbEntry entry = new DbEntry();
+                entry.id = c.getInt(indexId);
+                entry.itemType = c.getInt(indexItemType);
+                entry.screenId = c.getInt(indexScreen);
+
+                if (entry.screenId >= mHotseatSize) {
+                    entriesToRemove.add(entry.id);
+                    continue;
+                }
+
+                try {
+                    // calculate weight
+                    switch (entry.itemType) {
+                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
+                            entry.mIntent = c.getString(indexIntent);
+                            verifyIntent(c.getString(indexIntent));
+                            break;
+                        }
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
+                            int total = getFolderItemsCount(entry);
+                            if (total == 0) {
+                                throw new Exception("Folder is empty");
+                            }
+                            break;
+                        }
+                        default:
+                            throw new Exception("Invalid item type");
+                    }
+                } catch (Exception e) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Removing item " + entry.id, e);
+                    }
+                    entriesToRemove.add(entry.id);
+                    continue;
+                }
+                mHotseatEntries.add(entry);
+            }
+            removeEntryFromDb(mDb, entriesToRemove);
+            c.close();
+            return mHotseatEntries;
+        }
+
+        protected ArrayList<DbEntry> loadAllWorkspaceEntries() {
+            Cursor c = queryWorkspace(
+                    new String[]{
+                            LauncherSettings.Favorites._ID,                  // 0
+                            LauncherSettings.Favorites.ITEM_TYPE,            // 1
+                            LauncherSettings.Favorites.SCREEN,               // 2
+                            LauncherSettings.Favorites.CELLX,                // 3
+                            LauncherSettings.Favorites.CELLY,                // 4
+                            LauncherSettings.Favorites.SPANX,                // 5
+                            LauncherSettings.Favorites.SPANY,                // 6
+                            LauncherSettings.Favorites.INTENT,               // 7
+                            LauncherSettings.Favorites.APPWIDGET_PROVIDER,   // 8
+                            LauncherSettings.Favorites.APPWIDGET_ID},        // 9
+                        LauncherSettings.Favorites.CONTAINER + " = "
+                            + LauncherSettings.Favorites.CONTAINER_DESKTOP);
+            return loadWorkspaceEntries(c);
+        }
+
+        protected ArrayList<DbEntry> loadWorkspaceEntries(int screen) {
+            Cursor c = queryWorkspace(
+                    new String[]{
+                            LauncherSettings.Favorites._ID,                  // 0
+                            LauncherSettings.Favorites.ITEM_TYPE,            // 1
+                            LauncherSettings.Favorites.SCREEN,               // 2
+                            LauncherSettings.Favorites.CELLX,                // 3
+                            LauncherSettings.Favorites.CELLY,                // 4
+                            LauncherSettings.Favorites.SPANX,                // 5
+                            LauncherSettings.Favorites.SPANY,                // 6
+                            LauncherSettings.Favorites.INTENT,               // 7
+                            LauncherSettings.Favorites.APPWIDGET_PROVIDER,   // 8
+                            LauncherSettings.Favorites.APPWIDGET_ID},        // 9
+                    LauncherSettings.Favorites.CONTAINER + " = "
+                            + LauncherSettings.Favorites.CONTAINER_DESKTOP
+                            + " AND " + LauncherSettings.Favorites.SCREEN + " = " + screen);
+            return loadWorkspaceEntries(c);
+        }
+
+        private ArrayList<DbEntry> loadWorkspaceEntries(Cursor c) {
+            final int indexId = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
+            final int indexItemType = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE);
+            final int indexScreen = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN);
+            final int indexCellX = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX);
+            final int indexCellY = c.getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY);
+            final int indexSpanX = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANX);
+            final int indexSpanY = c.getColumnIndexOrThrow(LauncherSettings.Favorites.SPANY);
+            final int indexIntent = c.getColumnIndexOrThrow(LauncherSettings.Favorites.INTENT);
+            final int indexAppWidgetProvider = c.getColumnIndexOrThrow(
+                    LauncherSettings.Favorites.APPWIDGET_PROVIDER);
+            final int indexAppWidgetId = c.getColumnIndexOrThrow(
+                    LauncherSettings.Favorites.APPWIDGET_ID);
+
+            IntArray entriesToRemove = new IntArray();
+            WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(mContext);
+            while (c.moveToNext()) {
+                DbEntry entry = new DbEntry();
+                entry.id = c.getInt(indexId);
+                entry.itemType = c.getInt(indexItemType);
+                entry.screenId = c.getInt(indexScreen);
+                mLastScreenId = Math.max(mLastScreenId, entry.screenId);
+                entry.cellX = c.getInt(indexCellX);
+                entry.cellY = c.getInt(indexCellY);
+                entry.spanX = c.getInt(indexSpanX);
+                entry.spanY = c.getInt(indexSpanY);
+
+                try {
+                    // calculate weight
+                    switch (entry.itemType) {
+                        case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
+                        case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: {
+                            entry.mIntent = c.getString(indexIntent);
+                            verifyIntent(entry.mIntent);
+                            break;
+                        }
+                        case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
+                            entry.mProvider = c.getString(indexAppWidgetProvider);
+                            ComponentName cn = ComponentName.unflattenFromString(entry.mProvider);
+                            verifyPackage(cn.getPackageName());
+
+                            int widgetId = c.getInt(indexAppWidgetId);
+                            LauncherAppWidgetProviderInfo pInfo =
+                                    widgetManagerHelper.getLauncherAppWidgetInfo(widgetId);
+                            Point spans = null;
+                            if (pInfo != null) {
+                                spans = pInfo.getMinSpans();
+                            }
+                            if (spans != null) {
+                                entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
+                                entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY;
+                            } else {
+                                // Assume that the widget be resized down to 2x2
+                                entry.minSpanX = entry.minSpanY = 2;
+                            }
+
+                            break;
+                        }
+                        case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
+                            int total = getFolderItemsCount(entry);
+                            if (total == 0) {
+                                throw new Exception("Folder is empty");
+                            }
+                            break;
+                        }
+                        default:
+                            throw new Exception("Invalid item type");
+                    }
+                } catch (Exception e) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Removing item " + entry.id, e);
+                    }
+                    entriesToRemove.add(entry.id);
+                    continue;
+                }
+                mWorkspaceEntries.add(entry);
+            }
+            removeEntryFromDb(mDb, entriesToRemove);
+            c.close();
+            return mWorkspaceEntries;
+        }
+
+        private int getFolderItemsCount(DbEntry entry) {
+            Cursor c = queryWorkspace(
+                    new String[]{LauncherSettings.Favorites._ID, LauncherSettings.Favorites.INTENT},
+                    LauncherSettings.Favorites.CONTAINER + " = " + entry.id);
+
+            int total = 0;
+            while (c.moveToNext()) {
+                try {
+                    String intent = c.getString(1);
+                    verifyIntent(intent);
+                    total++;
+                    entry.mFolderItems.add(intent);
+                } catch (Exception e) {
+                    removeEntryFromDb(mDb, IntArray.wrap(c.getInt(0)));
+                }
+            }
+            c.close();
+            return total;
+        }
+
+        private Cursor queryWorkspace(String[] columns, String where) {
+            return mDb.query(mTableName, columns, where, null, null, null, null);
+        }
+
+        /** Verifies if the mIntent should be restored. */
+        private void verifyIntent(String intentStr)
+                throws Exception {
+            Intent intent = Intent.parseUri(intentStr, 0);
+            if (intent.getComponent() != null) {
+                verifyPackage(intent.getComponent().getPackageName());
+            } else if (intent.getPackage() != null) {
+                // Only verify package if the component was null.
+                verifyPackage(intent.getPackage());
+            }
+        }
+
+        /** Verifies if the package should be restored */
+        private void verifyPackage(String packageName)
+                throws Exception {
+            if (!mValidPackages.contains(packageName)) {
+                // TODO(b/151468819): Handle promise app icon restoration during grid migration.
+                throw new Exception("Package not available");
+            }
+        }
+    }
+
+    protected static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
+
+        private String mIntent;
+        private String mProvider;
+        private Set<String> mFolderItems = new HashSet<>();
+
+        /** Comparator according to the reading order */
+        @Override
+        public int compareTo(DbEntry another) {
+            if (screenId != another.screenId) {
+                return Integer.compare(screenId, another.screenId);
+            }
+            if (cellY != another.cellY) {
+                return -Integer.compare(cellY, another.cellY);
+            }
+            return Integer.compare(cellX, another.cellX);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            DbEntry entry = (DbEntry) o;
+            return Objects.equals(mIntent, entry.mIntent);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mIntent);
+        }
+
+        public void updateContentValues(ContentValues values) {
+            values.put(LauncherSettings.Favorites.SCREEN, screenId);
+            values.put(LauncherSettings.Favorites.CELLX, cellX);
+            values.put(LauncherSettings.Favorites.CELLY, cellY);
+            values.put(LauncherSettings.Favorites.SPANX, spanX);
+            values.put(LauncherSettings.Favorites.SPANY, spanY);
+        }
+
+        public String getIntentStr() {
+            return mIntent;
+        }
+    }
 }
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index f7ecc3f..dacea84 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -118,13 +118,20 @@
         db.execSQL("DROP TABLE IF EXISTS " + tableName);
     }
 
-    /** Copy from table to the to table. */
-    public static void copyTable(SQLiteDatabase db, String from, String to, Context context) {
+    /** Copy fromTable in fromDb to toTable in toDb. */
+    public static void copyTable(SQLiteDatabase fromDb, String fromTable, SQLiteDatabase toDb,
+            String toTable, Context context) {
         long userSerial = UserCache.INSTANCE.get(context).getSerialNumberForUser(
                 Process.myUserHandle());
-        dropTable(db, to);
-        Favorites.addTableToDb(db, userSerial, false, to);
-        db.execSQL("INSERT INTO " + to + " SELECT * FROM " + from);
+        dropTable(toDb, toTable);
+        Favorites.addTableToDb(toDb, userSerial, false, toTable);
+        if (fromDb != toDb) {
+            toDb.execSQL("ATTACH DATABASE '" + fromDb.getPath() + "' AS from_db");
+            toDb.execSQL(
+                    "INSERT INTO " + toTable + " SELECT * FROM from_db." + fromTable);
+        } else {
+            toDb.execSQL("INSERT INTO " + toTable + " SELECT * FROM " + fromTable);
+        }
     }
 
     /**
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..cbc5257 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
@@ -531,7 +535,7 @@
             // case the user started interacting with it before the animation finished.
             mLauncher.getStateManager().goToState(targetState, false /* animated */);
         }
-        mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
+        mLauncher.getDragLayer().getScrim().createSysuiMultiplierAnim(1f).setDuration(0).start();
     }
 
     private void logReachedState(int logAction, LauncherState targetState) {
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) { }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java b/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
new file mode 100644
index 0000000..4913cad
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
@@ -0,0 +1,27 @@
+/*
+ * 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.uioverrides;
+
+import android.content.Context;
+import android.os.Bundle;
+
+/** Render preview using surface view. */
+public class PreviewSurfaceRenderer {
+
+    /** Handle a received surface view request. */
+    public static void render(Context context, Bundle bundle) { }
+}