Merge "Adds back gesture recognition to Sandbox." into ub-launcher3-master
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 6d105ac..c7a0253 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -46,7 +46,9 @@
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
-
+    <!-- TODO(b/150802536): Enabled only for ENABLE_FIXED_ROTATION_TRANSFORM feature flag -->
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+    
     <!--
     Permissions required for read/write access to the workspace data. These permission name
     should not conflict with that defined in other apps, as such an app should embed its package
diff --git a/quickstep/AndroidManifest.xml b/quickstep/AndroidManifest.xml
index d360613..1d0b045 100644
--- a/quickstep/AndroidManifest.xml
+++ b/quickstep/AndroidManifest.xml
@@ -26,6 +26,7 @@
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
+    
     <application
         android:backupAgent="com.android.launcher3.LauncherBackupAgent"
         android:fullBackupOnly="true"
diff --git a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
index b0f2b4b..980bb5a 100644
--- a/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
+++ b/quickstep/recents_ui_overrides/res/layout/arrow_toast.xml
@@ -34,6 +34,7 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_weight="1"
+            android:gravity="center"
             android:layout_gravity="center_vertical"
             android:textColor="@android:color/white"
             android:textSize="16sp"/>
diff --git a/quickstep/recents_ui_overrides/res/values/config.xml b/quickstep/recents_ui_overrides/res/values/config.xml
index 527eec6..120e034 100644
--- a/quickstep/recents_ui_overrides/res/values/config.xml
+++ b/quickstep/recents_ui_overrides/res/values/config.xml
@@ -14,8 +14,5 @@
      limitations under the License.
 -->
 <resources>
-    <integer name="app_background_blur_radius">150</integer>
-    <integer name="allapps_background_blur_radius">90</integer>
-    <integer name="overview_background_blur_radius">50</integer>
-    <integer name="folder_background_blur_radius_adjustment">20</integer>
+    <integer name="max_depth_blur_radius">150</integer>
 </resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java
new file mode 100644
index 0000000..a5ea523
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/ArrowTipView.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 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.content.Context;
+import android.graphics.CornerPathEffect;
+import android.graphics.Paint;
+import android.graphics.drawable.ShapeDrawable;
+import android.os.Handler;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.core.content.ContextCompat;
+
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.graphics.TriangleShape;
+
+/**
+ * A base class for arrow tip view in launcher
+ */
+public class ArrowTipView extends AbstractFloatingView {
+
+    private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
+    private static final long SHOW_DELAY_MS = 200;
+    private static final long SHOW_DURATION_MS = 300;
+    private static final long HIDE_DURATION_MS = 100;
+
+    protected final Launcher mLauncher;
+    private final Handler mHandler = new Handler();
+    private Runnable mOnClosed;
+
+    public ArrowTipView(Context context) {
+        super(context, null, 0);
+        mLauncher = Launcher.getLauncher(context);
+        init(context);
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            close(true);
+        }
+        return false;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (mIsOpen) {
+            if (animate) {
+                animate().alpha(0f)
+                        .withLayer()
+                        .setStartDelay(0)
+                        .setDuration(HIDE_DURATION_MS)
+                        .setInterpolator(Interpolators.ACCEL)
+                        .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
+                        .start();
+            } else {
+                animate().cancel();
+                mLauncher.getDragLayer().removeView(this);
+            }
+            if (mOnClosed != null) mOnClosed.run();
+            mIsOpen = false;
+        }
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_ON_BOARD_POPUP) != 0;
+    }
+
+    private void init(Context context) {
+        inflate(context, R.layout.arrow_toast, this);
+        setOrientation(LinearLayout.VERTICAL);
+        View dismissButton = findViewById(R.id.dismiss);
+        dismissButton.setOnClickListener(view -> {
+            handleClose(true);
+        });
+
+        View arrowView = findViewById(R.id.arrow);
+        ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
+        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
+                arrowLp.width, arrowLp.height, false));
+        Paint arrowPaint = arrowDrawable.getPaint();
+        TypedValue typedValue = new TypedValue();
+        context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
+        arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
+        // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
+        arrowPaint.setPathEffect(new CornerPathEffect(
+                context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
+        arrowView.setBackground(arrowDrawable);
+
+        mIsOpen = true;
+
+        mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Show Tip with specified string and Y location
+     */
+    public ArrowTipView show(String text, int top) {
+        ((TextView) findViewById(R.id.text)).setText(text);
+        mLauncher.getDragLayer().addView(this);
+
+        DragLayer.LayoutParams params = (DragLayer.LayoutParams) getLayoutParams();
+        params.gravity = Gravity.CENTER_HORIZONTAL;
+        params.leftMargin = mLauncher.getDeviceProfile().workspacePadding.left;
+        params.rightMargin = mLauncher.getDeviceProfile().workspacePadding.right;
+        post(() -> setY(top - getHeight()));
+        setAlpha(0);
+        animate()
+                .alpha(1f)
+                .withLayer()
+                .setStartDelay(SHOW_DELAY_MS)
+                .setDuration(SHOW_DURATION_MS)
+                .setInterpolator(Interpolators.DEACCEL)
+                .start();
+        return this;
+    }
+
+    /**
+     * Register a callback fired when toast is hidden
+     */
+    public ArrowTipView setOnClosedCallback(Runnable runnable) {
+        mOnClosed = runnable;
+        return this;
+    }
+}
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 7bc656a..0019ecb 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;
-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;
@@ -92,7 +91,7 @@
         AppWindowAnimationHelper helper =
             new AppWindowAnimationHelper(recentsView.getPagedViewOrientedState(), mLauncher);
         Animator recentsAnimator = getRecentsWindowAnimator(taskView, skipLauncherChanges,
-                appTargets, wallpaperTargets, mLauncher.getBackgroundBlurController(), helper);
+                appTargets, wallpaperTargets, mLauncher.getDepthController(), helper);
         anim.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
 
         Animator childStateAnimation = null;
@@ -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, ATOMIC_DURATION_FROM_PAUSED_TO_OVERVIEW);
+                        stateManager.getCurrentStableState(), OVERVIEW, config);
             }
 
             default:
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
index 0ae7435..b3bb850 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/AllAppsTipView.java
@@ -16,133 +16,30 @@
 
 package com.android.launcher3.appprediction;
 
+import static com.android.launcher3.AbstractFloatingView.TYPE_DISCOVERY_BOUNCE;
+import static com.android.launcher3.AbstractFloatingView.TYPE_ON_BOARD_POPUP;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.quickstep.logging.UserEventDispatcherExtension.ALL_APPS_PREDICTION_TIPS;
 
-import android.content.Context;
-import android.graphics.CornerPathEffect;
-import android.graphics.Paint;
-import android.graphics.drawable.ShapeDrawable;
-import android.os.Handler;
 import android.os.UserManager;
-import android.util.AttributeSet;
-import android.util.TypedValue;
-import android.view.Gravity;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.core.content.ContextCompat;
 
 import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.ArrowTipView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.FloatingHeaderView;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.graphics.TriangleShape;
 import com.android.systemui.shared.system.LauncherEventUtil;
 
 /**
- * All apps tip view aligned just above prediction apps, shown to users that enter all apps for the
+ * ArrowTip helper aligned just above prediction apps, shown to users that enter all apps for the
  * first time.
  */
-public class AllAppsTipView extends AbstractFloatingView {
+public class AllAppsTipView {
 
     private static final String ALL_APPS_TIP_SEEN = "launcher.all_apps_tip_seen";
-    private static final long AUTO_CLOSE_TIMEOUT_MILLIS = 10 * 1000;
-    private static final long SHOW_DELAY_MS = 200;
-    private static final long SHOW_DURATION_MS = 300;
-    private static final long HIDE_DURATION_MS = 100;
-
-    private final Launcher mLauncher;
-    private final Handler mHandler = new Handler();
-
-    private AllAppsTipView(Context context, AttributeSet attrs) {
-        this(context, attrs, 0);
-    }
-
-    private AllAppsTipView(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-        setOrientation(LinearLayout.VERTICAL);
-
-        mLauncher = Launcher.getLauncher(context);
-
-        init(context);
-    }
-
-    @Override
-    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
-        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            close(true);
-        }
-        return false;
-    }
-
-    @Override
-    protected void handleClose(boolean animate) {
-        if (mIsOpen) {
-            if (animate) {
-                animate().alpha(0f)
-                        .withLayer()
-                        .setStartDelay(0)
-                        .setDuration(HIDE_DURATION_MS)
-                        .setInterpolator(Interpolators.ACCEL)
-                        .withEndAction(() -> mLauncher.getDragLayer().removeView(this))
-                        .start();
-            } else {
-                animate().cancel();
-                mLauncher.getDragLayer().removeView(this);
-            }
-            mLauncher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
-            mIsOpen = false;
-        }
-    }
-
-    @Override
-    public void logActionCommand(int command) {
-    }
-
-    @Override
-    protected boolean isOfType(int type) {
-        return (type & TYPE_ON_BOARD_POPUP) != 0;
-    }
-
-    private void init(Context context) {
-        inflate(context, R.layout.arrow_toast, this);
-
-        TextView textView = findViewById(R.id.text);
-        textView.setText(R.string.all_apps_prediction_tip);
-
-        View dismissButton = findViewById(R.id.dismiss);
-        dismissButton.setOnClickListener(view -> {
-            mLauncher.getUserEventDispatcher().logActionTip(
-                    LauncherEventUtil.DISMISS, ALL_APPS_PREDICTION_TIPS);
-            handleClose(true);
-        });
-
-        View arrowView = findViewById(R.id.arrow);
-        ViewGroup.LayoutParams arrowLp = arrowView.getLayoutParams();
-        ShapeDrawable arrowDrawable = new ShapeDrawable(TriangleShape.create(
-                arrowLp.width, arrowLp.height, false));
-        Paint arrowPaint = arrowDrawable.getPaint();
-        TypedValue typedValue = new TypedValue();
-        context.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
-        arrowPaint.setColor(ContextCompat.getColor(getContext(), typedValue.resourceId));
-        // The corner path effect won't be reflected in the shadow, but shouldn't be noticeable.
-        arrowPaint.setPathEffect(new CornerPathEffect(
-                context.getResources().getDimension(R.dimen.arrow_toast_corner_radius)));
-        arrowView.setBackground(arrowDrawable);
-
-        mIsOpen = true;
-
-        mHandler.postDelayed(() -> handleClose(true), AUTO_CLOSE_TIMEOUT_MILLIS);
-    }
 
     private static boolean showAllAppsTipIfNecessary(Launcher launcher) {
         FloatingHeaderView floatingHeaderView = launcher.getAppsView().getFloatingHeaderView();
@@ -156,28 +53,15 @@
             return false;
         }
 
-        AllAppsTipView allAppsTipView = new AllAppsTipView(launcher.getAppsView().getContext(),
-            null);
-        launcher.getDragLayer().addView(allAppsTipView);
+        int[] coords = new int[2];
+        floatingHeaderView.findFixedRowByType(PredictionRowView.class).getLocationOnScreen(coords);
+        ArrowTipView arrowTipView = new ArrowTipView(launcher).setOnClosedCallback(() -> {
+            launcher.getSharedPrefs().edit().putBoolean(ALL_APPS_TIP_SEEN, true).apply();
+            launcher.getUserEventDispatcher().logActionTip(LauncherEventUtil.DISMISS,
+                    ALL_APPS_PREDICTION_TIPS);
+        });
+        arrowTipView.show(launcher.getString(R.string.all_apps_prediction_tip), coords[1]);
 
-        DragLayer.LayoutParams params = (DragLayer.LayoutParams) allAppsTipView.getLayoutParams();
-        params.gravity = Gravity.CENTER_HORIZONTAL;
-
-        int top = floatingHeaderView.findFixedRowByType(PredictionRowView.class).getTop();
-        allAppsTipView.setY(top - launcher.getResources().getDimensionPixelSize(
-                R.dimen.all_apps_tip_bottom_margin));
-
-        allAppsTipView.setAlpha(0);
-        allAppsTipView.animate()
-                .alpha(1f)
-                .withLayer()
-                .setStartDelay(SHOW_DELAY_MS)
-                .setDuration(SHOW_DURATION_MS)
-                .setInterpolator(Interpolators.DEACCEL)
-                .start();
-
-        launcher.getUserEventDispatcher().logActionTip(
-                LauncherEventUtil.VISIBLE, ALL_APPS_PREDICTION_TIPS);
         return true;
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index a07cd1d..da58817 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -23,23 +23,28 @@
 import android.content.res.Configuration;
 import android.os.Build;
 import android.view.View;
-import android.view.ViewGroup;
 
 import androidx.core.app.NotificationCompat;
 
 import com.android.launcher3.CellLayout;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.Hotseat;
+import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.WorkspaceLayoutManager;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.uioverrides.QuickstepLauncher;
 import com.android.launcher3.util.ActivityTracker;
+import com.android.launcher3.util.GridOccupancy;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.Themes;
 
+import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -52,48 +57,179 @@
     private static final int ONBOARDING_NOTIFICATION_ID = 7641;
 
     private final Launcher mLauncher;
+    private final NotificationManager mNotificationManager;
+    private final Notification mNotification;
     private List<WorkspaceItemInfo> mPredictedApps;
     private HotseatEduDialog mActiveDialog;
 
-    private final NotificationManager mNotificationManager;
-    private final Notification mNotification;
+    private ArrayList<ItemInfo> mNewItems = new ArrayList<>();
+    private IntArray mNewScreens = null;
+    private Runnable mOnOnboardingComplete;
 
-    HotseatEduController(Launcher launcher) {
+    HotseatEduController(Launcher launcher, Runnable runnable) {
         mLauncher = launcher;
+        mOnOnboardingComplete = runnable;
         mNotificationManager = mLauncher.getSystemService(NotificationManager.class);
         createNotificationChannel();
         mNotification = createNotification();
     }
 
-    boolean migrate() {
-        Workspace workspace = mLauncher.getWorkspace();
-        CellLayout firstScreen = workspace.getScreenWithId(WorkspaceLayoutManager.FIRST_SCREEN_ID);
-        int toPage = Workspace.FIRST_SCREEN_ID;
-        int toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
-        if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
-            toPage = workspace.getScreenIdForPageIndex(workspace.getPageCount());
-            toRow = 0;
-        } else if (!firstScreen.makeSpaceForHotseatMigration(true)) {
-            return false;
+    /**
+     * Checks what type of migration should be used and migrates hotseat
+     */
+    void migrate() {
+        if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
+            migrateToFolder();
+        } else {
+            migrateHotseatWhole();
         }
-        ViewGroup hotseatVG = mLauncher.getHotseat().getShortcutsAndWidgets();
-        for (int i = 0; i < hotseatVG.getChildCount(); i++) {
-            View child = hotseatVG.getChildAt(i);
+    }
+
+    /**
+     * This migration places all non folder items in the hotseat into a folder and then moves
+     * all folders in the hotseat to a workspace page that has enough empty spots.
+     *
+     * @return pageId that has accepted the items.
+     */
+    private int migrateToFolder() {
+        ArrayDeque<FolderInfo> folders = new ArrayDeque<>();
+
+        ArrayList<WorkspaceItemInfo> putIntoFolder = new ArrayList<>();
+
+        //separate folders and items that can get in folders
+        for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+            View view = mLauncher.getHotseat().getChildAt(i, 0);
+            if (view == null) continue;
+            ItemInfo info = (ItemInfo) view.getTag();
+            if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+                folders.add((FolderInfo) info);
+            } else if (info instanceof WorkspaceItemInfo) {
+                putIntoFolder.add((WorkspaceItemInfo) info);
+            }
+        }
+
+        // create a temp folder and add non folder items to it
+        if (!putIntoFolder.isEmpty()) {
+            ItemInfo firstItem = putIntoFolder.get(0);
+            FolderInfo folderInfo = new FolderInfo();
+            folderInfo.title = "";
+            mLauncher.getModelWriter().addItemToDatabase(folderInfo, firstItem.container,
+                    firstItem.screenId, firstItem.cellX, firstItem.cellY);
+            folderInfo.contents.addAll(putIntoFolder);
+            for (int i = 0; i < folderInfo.contents.size(); i++) {
+                ItemInfo item = folderInfo.contents.get(i);
+                item.rank = i;
+                mLauncher.getModelWriter().moveItemInDatabase(item, folderInfo.id, 0,
+                        item.cellX, item.cellY);
+            }
+            folders.add(folderInfo);
+        }
+        mNewItems.addAll(folders);
+
+        return placeFoldersInWorkspace(folders);
+    }
+
+    private int placeFoldersInWorkspace(ArrayDeque<FolderInfo> folders) {
+        if (folders.isEmpty()) return 0;
+
+        Workspace workspace = mLauncher.getWorkspace();
+        InvariantDeviceProfile idp = mLauncher.getDeviceProfile().inv;
+
+        GridOccupancy[] occupancyList = new GridOccupancy[workspace.getChildCount()];
+        for (int i = 0; i < occupancyList.length; i++) {
+            occupancyList[i] = ((CellLayout) workspace.getChildAt(i)).cloneGridOccupancy();
+        }
+        //scan every screen to find available spots to place folders
+        int occupancyIndex = 0;
+        int[] itemXY = new int[2];
+        while (occupancyIndex < occupancyList.length && !folders.isEmpty()) {
+            GridOccupancy occupancy = occupancyList[occupancyIndex];
+            if (occupancy.findVacantCell(itemXY, 1, 1)) {
+                FolderInfo info = folders.poll();
+                mLauncher.getModelWriter().moveItemInDatabase(info,
+                        LauncherSettings.Favorites.CONTAINER_DESKTOP,
+                        workspace.getScreenIdForPageIndex(occupancyIndex), itemXY[0], itemXY[1]);
+                occupancy.markCells(info, true);
+            } else {
+                occupancyIndex++;
+            }
+        }
+        if (folders.isEmpty()) return workspace.getScreenIdForPageIndex(occupancyIndex);
+        int screenId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+                LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+        // if all screens are full and we still have folders left, put those on a new page
+        FolderInfo folderInfo;
+        int col = 0;
+        while ((folderInfo = folders.poll()) != null) {
+            mLauncher.getModelWriter().moveItemInDatabase(folderInfo,
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, col++,
+                    idp.numRows - 1);
+        }
+        mNewScreens = IntArray.wrap(screenId);
+        return workspace.getPageCount();
+    }
+
+    /**
+     * This migration option attempts to move the entire hotseat up to the first workspace that
+     * has space to host items. If no such page is found, it moves items to a new page.
+     *
+     * @return pageId where items are migrated
+     */
+    private int migrateHotseatWhole() {
+        Workspace workspace = mLauncher.getWorkspace();
+        Hotseat hotseatVG = mLauncher.getHotseat();
+
+        int pageId = -1;
+        int toRow = 0;
+        for (int i = 0; i < workspace.getPageCount(); i++) {
+            CellLayout target = workspace.getScreenWithId(workspace.getScreenIdForPageIndex(i));
+            if (target.makeSpaceForHotseatMigration(true)) {
+                toRow = mLauncher.getDeviceProfile().inv.numRows - 1;
+                pageId = i;
+                break;
+            }
+        }
+        if (pageId == -1) {
+            pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
+                    LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
+                    .getInt(LauncherSettings.Settings.EXTRA_VALUE);
+        }
+        for (int i = 0; i < mLauncher.getDeviceProfile().inv.numHotseatIcons; i++) {
+            View child = hotseatVG.getChildAt(i, 0);
+            if (child == null || child.getTag() == null) continue;
             ItemInfo tag = (ItemInfo) child.getTag();
             mLauncher.getModelWriter().moveItemInDatabase(tag,
-                    LauncherSettings.Favorites.CONTAINER_DESKTOP, toPage, tag.screenId, toRow);
+                    LauncherSettings.Favorites.CONTAINER_DESKTOP, pageId, i, toRow);
+            mNewItems.add(tag);
         }
-        return true;
+        return pageId;
     }
 
+
     void removeNotification() {
         mNotificationManager.cancel(ONBOARDING_NOTIFICATION_ID);
     }
 
     void finishOnboarding() {
-        mLauncher.getModel().rebindCallbacks();
+        mLauncher.getHotseat().removeAllViewsInLayout();
+        if (!mNewItems.isEmpty()) {
+            int lastPage = mNewItems.get(mNewItems.size() - 1).screenId;
+            ArrayList<ItemInfo> animated = new ArrayList<>();
+            ArrayList<ItemInfo> nonAnimated = new ArrayList<>();
+
+            for (ItemInfo info : mNewItems) {
+                if (info.screenId == lastPage) {
+                    animated.add(info);
+                } else {
+                    nonAnimated.add(info);
+                }
+            }
+            mLauncher.bindAppsAdded(mNewScreens, nonAnimated, animated);
+        }
+        mOnOnboardingComplete.run();
+        destroy();
         mLauncher.getSharedPrefs().edit().putBoolean(KEY_HOTSEAT_EDU_SEEN, true).apply();
-        removeNotification();
     }
 
     void setPredictedApps(List<WorkspaceItemInfo> predictedApps) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
index 538b7f3..bcce168 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduDialog.java
@@ -29,13 +29,13 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.launcher3.ArrowTipView;
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.WorkspaceLayoutManager;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
@@ -54,23 +54,15 @@
     private static final int DEFAULT_CLOSE_DURATION = 200;
     protected static final int FINAL_SCRIM_BG_COLOR = 0x88000000;
 
-    // We don't migrate if user has more than SAME_PAGE_MAX_ROWS rows of item in their screen
-    private static final int SAME_PAGE_MAX_ROWS = 2;
 
-    private static final int MIGRATE_SAME_PAGE = 0;
-    private static final int MIGRATE_NEW_PAGE = 1;
-    private static final int MIGRATE_NO_MIGRATE = 2;
-
+    // we use this value to keep track of migration logs as we experiment with different migrations
+    private static final int MIGRATION_EXPERIMENT_IDENTIFIER = 1;
 
     private final Rect mInsets = new Rect();
     private View mHotseatWrapper;
     private CellLayout mSampleHotseat;
-    private TextView mEduHeading;
-    private TextView mEduContent;
     private Button mDismissBtn;
 
-    private int mMigrationMode = MIGRATE_SAME_PAGE;
-
     public void setHotseatEduController(HotseatEduController hotseatEduController) {
         mHotseatEduController = hotseatEduController;
     }
@@ -93,8 +85,6 @@
         super.onFinishInflate();
         mHotseatWrapper = findViewById(R.id.hotseat_wrapper);
         mSampleHotseat = findViewById(R.id.sample_prediction);
-        mEduHeading = findViewById(R.id.hotseat_edu_heading);
-        mEduContent = findViewById(R.id.hotseat_edu_content);
 
         DeviceProfile grid = mLauncher.getDeviceProfile();
         Rect padding = grid.getHotseatLayoutPadding();
@@ -109,25 +99,30 @@
         mDismissBtn = findViewById(R.id.no_thanks);
         mDismissBtn.setOnClickListener(this::onDismiss);
 
+        // update ui to reflect which migration method is going to be used
+        if (FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()) {
+            ((TextView) findViewById(R.id.hotseat_edu_content)).setText(
+                    R.string.hotseat_edu_message_migrate_alt);
+        }
     }
 
     private void onAccept(View v) {
-        if (mMigrationMode == MIGRATE_NO_MIGRATE || !mHotseatEduController.migrate()) {
-            onDismiss(v);
-            return;
-        }
+        mHotseatEduController.migrate();
         handleClose(true);
         mHotseatEduController.finishOnboarding();
-        logUserAction(true);
-        int toastStringRes = mMigrationMode == MIGRATE_SAME_PAGE
+        //TODO: pass actual page index here.
+        // Temporarily we're passing 1 for folder migration and 2 for page migration
+        logUserAction(true, FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get() ? 1 : 2);
+        int toastStringRes = !FeatureFlags.HOTSEAT_MIGRATE_TO_FOLDER.get()
                 ? R.string.hotseat_items_migrated : R.string.hotseat_items_migrated_alt;
         Toast.makeText(mLauncher, toastStringRes, Toast.LENGTH_LONG).show();
     }
 
     private void onDismiss(View v) {
-        Toast.makeText(getContext(), R.string.hotseat_no_migration, Toast.LENGTH_LONG).show();
+        int top = mLauncher.getHotseat().getTop();
+        new ArrowTipView(mLauncher).show(mLauncher.getString(R.string.hotseat_no_migration), top);
         mHotseatEduController.finishOnboarding();
-        logUserAction(false);
+        logUserAction(false, -1);
         handleClose(true);
     }
 
@@ -159,7 +154,7 @@
                 mLauncher.getDeviceProfile().hotseatBarSizePx + insets.bottom;
     }
 
-    private void logUserAction(boolean migrated) {
+    private void logUserAction(boolean migrated, int pageIndex) {
         LauncherLogProto.Action action = new LauncherLogProto.Action();
         LauncherLogProto.Target target = new LauncherLogProto.Target();
         action.type = LauncherLogProto.Action.Type.TOUCH;
@@ -168,8 +163,9 @@
         target.tipType = LauncherLogProto.TipType.HYBRID_HOTSEAT;
         target.controlType = migrated ? LauncherLogProto.ControlType.HYBRID_HOTSEAT_ACCEPTED
                 : HYBRID_HOTSEAT_CANCELED;
+        target.rank = MIGRATION_EXPERIMENT_IDENTIFIER;
         // encoding migration type on pageIndex
-        target.pageIndex = mMigrationMode;
+        target.pageIndex = pageIndex;
         LauncherLogProto.LauncherEvent event = newLauncherEvent(action, target);
         UserEventDispatcher.newInstance(getContext()).dispatchUserEvent(event, null);
     }
@@ -222,28 +218,6 @@
         }
     }
 
-    @Override
-    protected void attachToContainer() {
-        super.attachToContainer();
-        if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) {
-            mEduContent.setText(R.string.hotseat_edu_message_migrate_alt);
-            mMigrationMode = MIGRATE_NEW_PAGE;
-            return;
-        }
-        CellLayout page = mLauncher.getWorkspace().getScreenWithId(
-                WorkspaceLayoutManager.FIRST_SCREEN_ID);
-
-        int maxItemsOnPage = SAME_PAGE_MAX_ROWS * mLauncher.getDeviceProfile().inv.numColumns
-                + (FeatureFlags.QSB_ON_FIRST_SCREEN ? 1 : 0);
-        if (page.getShortcutsAndWidgets().getChildCount() > maxItemsOnPage
-                || !page.makeSpaceForHotseatMigration(false)) {
-            mMigrationMode = MIGRATE_NO_MIGRATE;
-            mEduContent.setText(R.string.hotseat_edu_message_no_migrate);
-            mEduHeading.setText(R.string.hotseat_edu_title_no_migrate);
-            mDismissBtn.setVisibility(GONE);
-        }
-    }
-
     /**
      * Opens User education dialog with a list of suggested apps
      */
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index 2cdcd20..d82e9f0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -94,7 +94,6 @@
     private static final String BUNDLE_KEY_WORKSPACE = "workspace_apps";
 
     private static final String PREDICTION_CLIENT = "hotseat";
-
     private DropTarget.DragObject mDragObject;
     private int mHotSeatItemsCount;
     private int mPredictedSpotsCount = 0;
@@ -115,6 +114,7 @@
 
     private List<PredictedAppIcon.PredictedIconOutlineDrawing> mOutlineDrawings = new ArrayList<>();
 
+
     private final View.OnLongClickListener mPredictionLongClickListener = v -> {
         if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
         if (mLauncher.getWorkspace().isSwitchingState()) return false;
@@ -276,12 +276,10 @@
                         .build());
         mAppPredictor.registerPredictionUpdates(mLauncher.getMainExecutor(),
                 this::setPredictedApps);
+        setPauseUIUpdate(false);
 
         if (!isReady()) {
-            if (mHotseatEduController != null) {
-                mHotseatEduController.destroy();
-            }
-            mHotseatEduController = new HotseatEduController(mLauncher);
+            mHotseatEduController = new HotseatEduController(mLauncher, this::createPredictor);
         }
         mAppPredictor.requestPredictionUpdate();
     }
@@ -327,7 +325,7 @@
             mComponentKeyMappers.add(new ComponentKeyMapper(key, mDynamicItemCache));
         }
         predictionLog.append("]");
-        FileLog.d(TAG, predictionLog.toString());
+        if (false) FileLog.d(TAG, predictionLog.toString());
         updateDependencies();
         if (isReady()) {
             fillGapsWithPrediction();
@@ -488,7 +486,6 @@
         }
     }
 
-
     @Override
     public void onDragEnd() {
         if (mDragObject == null) {
@@ -564,7 +561,8 @@
     }
 
     @Override
-    public void reapplyItemInfo(ItemInfoWithIcon info) {}
+    public void reapplyItemInfo(ItemInfoWithIcon info) {
+    }
 
     @Override
     public void onDropCompleted(View target, DropTarget.DragObject d, boolean success) {
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 b5d8424..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,9 +28,10 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager.AnimationConfig;
-import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.anim.AnimationSuccessListener;
+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;
@@ -60,29 +60,22 @@
     }
 
     @Override
-    void setStateWithAnimationInternal(@NonNull final LauncherState toState,
-            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
-        super.setStateWithAnimationInternal(toState, builder, config);
-
-        if (!toState.overviewUi) {
-            builder.addOnFinishRunnable(mRecentsView::resetTaskVisuals);
-        }
+    void setStateWithAnimationInternal(@NonNull LauncherState toState,
+            @NonNull StateAnimationConfig config, @NonNull PendingAnimation builder) {
+        super.setStateWithAnimationInternal(toState, config, builder);
 
         if (toState.overviewUi) {
-            ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1);
-            updateAnim.addUpdateListener(valueAnimator -> {
-                // While animating into recents, update the visible task data as needed
-                mRecentsView.loadVisibleTaskData();
-            });
-            updateAnim.setDuration(config.duration);
-            builder.play(updateAnim);
+            // While animating into recents, update the visible task data as needed
+            builder.addOnFrameCallback(mRecentsView::loadVisibleTaskData);
             mRecentsView.updateEmptyMessage();
+        } else {
+            builder.getAnim().addListener(
+                    AnimationSuccessListener.forRunnable(mRecentsView::resetTaskVisuals));
         }
 
-        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/BackgroundAppState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 5bac964..de3fce1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -19,7 +19,6 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsTransitionController;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.quickstep.util.LayoutUtils;
@@ -107,7 +106,7 @@
     }
 
     @Override
-    public int getBackgroundBlurRadius(Context context) {
-        return context.getResources().getInteger(R.integer.app_background_blur_radius);
+    public float getDepth(Context context) {
+        return 1f;
     }
 }
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..024872f 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;
@@ -207,8 +207,8 @@
     }
 
     @Override
-    public int getBackgroundBlurRadius(Context context) {
-        return context.getResources().getInteger(R.integer.overview_background_blur_radius);
+    public float getDepth(Context context) {
+        return 1f;
     }
 
     @Override
@@ -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 9cbe11a..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,15 +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.ATOMIC_OVERVIEW_PEEK_COMPONENT;
-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,8 +45,9 @@
 import com.android.launcher3.LauncherAppTransitionManagerImpl;
 import com.android.launcher3.LauncherState;
 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;
@@ -103,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(), ATOMIC_OVERVIEW_PEEK_COMPONENT, 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) {
@@ -114,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();
     }
 
     /**
@@ -128,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);
@@ -150,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(
@@ -163,7 +169,7 @@
                     1));
             return builder;
         }
-        return super.getAnimatorSetBuilderForStates(fromState, toState);
+        return super.getConfigForStates(fromState, toState);
     }
 
     @Override
@@ -209,9 +215,11 @@
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
                 if (mCancelled) {
-                    mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(mFromState,
-                            mToState, new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT,
-                            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;
@@ -237,11 +245,14 @@
     }
 
     @Override
-    protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+    @AnimationFlags
+    protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
         if (handlingOverviewAnim()) {
             // We don't want the state transition to all apps to animate overview,
             // as that will cause a jump after our atomic animation.
-            builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
+            return animComponents | SKIP_OVERVIEW;
+        } else {
+            return animComponents;
         }
     }
 
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 7bae211..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.ATOMIC_OVERVIEW_PEEK_COMPONENT;
 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,
-                        ATOMIC_OVERVIEW_PEEK_COMPONENT, 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 52625dc..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;
 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.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
 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;
-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.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);
+        updateNonOverviewAnim(QUICK_SWITCH, nonOverviewBuilder);
         mNonOverviewAnim.dispatchOnStart();
 
         if (mRecentsView.getTaskViewCount() == 0) {
@@ -230,12 +226,12 @@
     }
 
     /** Create state animation to control non-overview components. */
-    private void updateNonOverviewAnim(LauncherState toState, AnimatorSetBuilder builder,
-            @LauncherStateManager.AnimationComponents int animComponents) {
-        builder.addFlag(FLAG_DONT_ANIMATE_OVERVIEW);
-        long accuracy = (long) (Math.max(mXRange, mYRange) * 2);
-        mNonOverviewAnim = mLauncher.getStateManager().createAnimationToNewWorkspace(toState,
-                builder, accuracy, this::clearState, animComponents);
+    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() {
@@ -420,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 d5b221d..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,17 +17,17 @@
 
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.QUICK_SWITCH;
-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;
@@ -39,9 +39,8 @@
 
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
 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, LauncherStateManager.ANIM_ALL);
-        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..ce7fa0d 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/AppToOverviewAnimationProvider.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
+import static com.android.launcher3.uioverrides.DepthController.DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
@@ -34,7 +34,7 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.anim.AnimationSuccessListener;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.DepthController;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.AppWindowAnimationHelper.TransformParams;
 import com.android.quickstep.util.RemoteAnimationProvider;
@@ -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;
@@ -105,10 +105,10 @@
             mRecentsView.setRunningTaskIconScaledDown(true);
         }
 
-        BackgroundBlurController blurController = mActivityInterface.getBackgroundBlurController();
-        if (blurController != null) {
+        DepthController depthController = mActivityInterface.getDepthController();
+        if (depthController != null) {
             // Update the surface to be the lowest closing app surface
-            blurController.setSurfaceToLauncher(mRecentsView);
+            depthController.setSurfaceToLauncher(mRecentsView);
         }
 
         AnimatorSet anim = new AnimatorSet();
@@ -124,7 +124,7 @@
         if (mActivity == null) {
             Log.e(TAG, "Animation created, before activity");
             anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
-                    .with(createBackgroundBlurAnimator(blurController));
+                    .with(createDepthAnimator(depthController));
             return anim;
         }
 
@@ -136,7 +136,7 @@
         if (runningTaskTarget == null) {
             Log.e(TAG, "No closing app");
             anim.play(ValueAnimator.ofInt(0, 1).setDuration(RECENTS_LAUNCH_DURATION))
-                    .with(createBackgroundBlurAnimator(blurController));
+                    .with(createDepthAnimator(depthController));
             return anim;
         }
 
@@ -184,7 +184,7 @@
             });
         }
         anim.play(valueAnimator)
-                .with(createBackgroundBlurAnimator(blurController));
+                .with(createDepthAnimator(depthController));
         return anim;
     }
 
@@ -197,14 +197,14 @@
         return RECENTS_LAUNCH_DURATION;
     }
 
-    private Animator createBackgroundBlurAnimator(BackgroundBlurController blurController) {
-        if (blurController == null) {
+    private Animator createDepthAnimator(DepthController depthController) {
+        if (depthController == null) {
             // Dummy animation
             return ValueAnimator.ofInt(0);
         }
-        return ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
-                BACKGROUND_APP.getBackgroundBlurRadius(mActivity),
-                OVERVIEW.getBackgroundBlurRadius(mActivity))
+        return ObjectAnimator.ofFloat(depthController, DEPTH,
+                BACKGROUND_APP.getDepth(mActivity),
+                OVERVIEW.getDepth(mActivity))
                 .setDuration(RECENTS_LAUNCH_DURATION);
     }
 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
index 9f19bb3..7786a8f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/BaseSwipeUpHandler.java
@@ -79,6 +79,7 @@
     private static final String TAG = "BaseSwipeUpHandler";
     protected static final Rect TEMP_RECT = new Rect();
 
+    public static final float MIN_PROGRESS_FOR_OVERVIEW = 0.7f;
     private static final Interpolator PULLBACK_INTERPOLATOR = DEACCEL;
 
     // The distance needed to drag to reach the task size in recents.
@@ -481,10 +482,6 @@
             public void onUpdate(RectF currentRect, float progress) {
                 homeAnim.setPlayFraction(progress);
 
-                mTransformParams.setProgress(
-                        Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
-                        .setCurrentRect(currentRect)
-                        .setTargetAlpha(getWindowAlpha(progress));
                 rotatedRect.set(currentRect);
                 if (isFloatingIconView) {
                     RotationHelper.mapRectFromNormalOrientation(rotatedRect,
@@ -492,6 +489,10 @@
                     mTransformParams.setCornerRadius(endRadius * progress + startRadius
                         * (1f - progress));
                 }
+                mTransformParams.setProgress(
+                    Utilities.mapRange(progress, startTransformProgress, endTransformProgress))
+                    .setCurrentRect(rotatedRect)
+                    .setTargetAlpha(getWindowAlpha(progress));
                 mAppWindowAnimationHelper.applyTransform(mTransformParams);
 
                 if (isFloatingIconView) {
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/FallbackSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
index ce67457..ea5561b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -21,7 +21,6 @@
 import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.NEW_TASK;
 import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
-import static com.android.quickstep.LauncherSwipeHandler.MIN_PROGRESS_FOR_OVERVIEW;
 import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
 import static com.android.quickstep.RecentsActivity.EXTRA_TASK_ID;
 import static com.android.quickstep.RecentsActivity.EXTRA_THUMBNAIL;
@@ -97,7 +96,7 @@
 
     private final AnimatedFloat mLauncherAlpha = new AnimatedFloat(this::onLauncherAlphaChanged);
 
-    private boolean mIsMotionPaused = false;
+    private boolean mOverviewThresholdPassed = false;
 
     private final boolean mInQuickSwitchMode;
     private final boolean mContinuingLastGesture;
@@ -222,10 +221,16 @@
 
     @Override
     public void onMotionPauseChanged(boolean isPaused) {
-        if (!mInQuickSwitchMode) {
-            mIsMotionPaused = isPaused;
+        if (!mInQuickSwitchMode && mDeviceState.isFullyGesturalNavMode()) {
+            updateOverviewThresholdPassed(isPaused);
+        }
+    }
+
+    private void updateOverviewThresholdPassed(boolean passed) {
+        if (passed != mOverviewThresholdPassed) {
+            mOverviewThresholdPassed = passed;
             if (mSwipeUpOverHome) {
-                mLauncherAlpha.animateToValue(mLauncherAlpha.value, isPaused ? 0 : 1)
+                mLauncherAlpha.animateToValue(mLauncherAlpha.value, passed ? 0 : 1)
                         .setDuration(150).start();
             }
             performHapticFeedback();
@@ -234,7 +239,7 @@
 
     @Override
     public Intent getLaunchIntent() {
-        if (mInQuickSwitchMode || mSwipeUpOverHome) {
+        if (mInQuickSwitchMode || mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
             return mGestureState.getOverviewIntent();
         } else {
             return mGestureState.getHomeIntent();
@@ -248,6 +253,11 @@
             mRecentsAnimationController.setWindowThresholdCrossed(!mInQuickSwitchMode
                     && (mCurrentShift.value > 1 - UPDATE_SYSUI_FLAGS_THRESHOLD));
         }
+
+        if (!mInQuickSwitchMode && !mDeviceState.isFullyGesturalNavMode()) {
+            updateOverviewThresholdPassed(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW);
+        }
+
         if (mRecentsAnimationTargets != null) {
             applyTransformUnchecked();
         }
@@ -271,14 +281,25 @@
                     .getDimension(R.dimen.quickstep_fling_threshold_velocity);
             boolean isFling = Math.abs(endVelocity) > flingThreshold;
 
-            if (isFling) {
-                mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
-            } else if (mIsMotionPaused) {
-                mGestureState.setEndTarget(RECENTS);
+            if (mDeviceState.isFullyGesturalNavMode()) {
+                if (isFling) {
+                    mGestureState.setEndTarget(endVelocity < 0 ? HOME : LAST_TASK);
+                } else if (mOverviewThresholdPassed) {
+                    mGestureState.setEndTarget(RECENTS);
+                } else {
+                    mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
+                            ? HOME
+                            : LAST_TASK);
+                }
             } else {
-                mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
-                        ? HOME
-                        : LAST_TASK);
+                GestureEndTarget startState = mSwipeUpOverHome ? HOME : LAST_TASK;
+                if (isFling) {
+                    mGestureState.setEndTarget(endVelocity < 0 ? RECENTS : startState);
+                } else {
+                    mGestureState.setEndTarget(mCurrentShift.value >= MIN_PROGRESS_FOR_OVERVIEW
+                            ? RECENTS
+                            : startState);
+                }
             }
         }
         mStateCallback.setStateOnUiThread(STATE_GESTURE_COMPLETED);
@@ -353,7 +374,7 @@
                 mRecentsAnimationController.finish(false, null, false);
                 break;
             case RECENTS: {
-                if (mSwipeUpOverHome) {
+                if (mSwipeUpOverHome || !mDeviceState.isFullyGesturalNavMode()) {
                     mRecentsAnimationController.finish(true, null, true);
                     break;
                 }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
index d402a75..55e6ba2 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherActivityInterface.java
@@ -56,8 +56,8 @@
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
-import com.android.launcher3.uioverrides.BackgroundBlurController.ClampedBlurProperty;
+import com.android.launcher3.uioverrides.DepthController;
+import com.android.launcher3.uioverrides.DepthController.ClampedDepthProperty;
 import com.android.launcher3.userevent.nano.LauncherLogProto;
 import com.android.launcher3.views.FloatingIconView;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -330,18 +330,17 @@
                     endState.getVerticalProgress(activity)));
         }
 
-        // Animate the blur
-        BackgroundBlurController blurController = getBackgroundBlurController();
-        int fromBlurRadius = fromState.getBackgroundBlurRadius(activity);
-        int toBlurRadius = endState.getBackgroundBlurRadius(activity);
-        Animator backgroundBlur = ObjectAnimator.ofInt(blurController,
-                new ClampedBlurProperty(toBlurRadius, fromBlurRadius),
-                fromBlurRadius, toBlurRadius);
-        anim.play(backgroundBlur);
+        // Animate the blur and wallpaper zoom
+        DepthController depthController = getDepthController();
+        float fromDepthRatio = fromState.getDepth(activity);
+        float toDepthRatio = endState.getDepth(activity);
+        Animator depthAnimator = ObjectAnimator.ofFloat(depthController,
+                new ClampedDepthProperty(fromDepthRatio, toDepthRatio),
+                fromDepthRatio, toDepthRatio);
+        anim.play(depthAnimator);
 
         playScaleDownAnim(anim, activity, fromState, endState);
 
-
         anim.setDuration(transitionLength * 2);
         anim.setInterpolator(LINEAR);
         AnimatorPlaybackController controller =
@@ -558,11 +557,11 @@
 
     @Nullable
     @Override
-    public BackgroundBlurController getBackgroundBlurController() {
+    public DepthController getDepthController() {
         BaseQuickstepLauncher launcher = getCreatedActivity();
         if (launcher == null) {
             return null;
         }
-        return launcher.getBackgroundBlurController();
+        return launcher.getDepthController();
     }
 }
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 3328abc..cafdb62 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -294,7 +294,7 @@
         }
 
         setupRecentsViewUi();
-        mActivityInterface.getBackgroundBlurController().setSurfaceToLauncher(mRecentsView);
+        mActivityInterface.getDepthController().setSurfaceToLauncher(mRecentsView);
 
         if (mDeviceState.getNavMode() == TWO_BUTTONS) {
             // If the device is in two button mode, swiping up will show overview with predictions
@@ -588,7 +588,7 @@
             // We will handle the sysui flags based on the centermost task view.
             if (mRecentsAnimationController != null) {
                 mRecentsAnimationController.setWindowThresholdCrossed(centermostTaskFlags != 0
-                        || useHomeScreenFlags);
+                        && useHomeScreenFlags);
             }
             int sysuiFlags = useHomeScreenFlags ? 0 : centermostTaskFlags;
             mActivity.getSystemUiController().updateUiState(UI_STATE_OVERVIEW, sysuiFlags);
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/RecentsActivity.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
index f2e8f96..42d944f 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/RecentsActivity.java
@@ -70,7 +70,7 @@
         setContentView(R.layout.fallback_recents_activity);
         mRecentsRootView = findViewById(R.id.drag_layer);
         mFallbackRecentsView = findViewById(R.id.overview_panel);
-        mRecentsRootView.setup();
+        mRecentsRootView.recreateControllers();
     }
 
     @Override
@@ -108,7 +108,7 @@
     @Override
     protected void onHandleConfigChanged() {
         super.onHandleConfigChanged();
-        mRecentsRootView.setup();
+        mRecentsRootView.recreateControllers();
     }
 
     @Override
@@ -186,7 +186,7 @@
         AppWindowAnimationHelper helper = new AppWindowAnimationHelper(
             mFallbackRecentsView.getPagedViewOrientedState(), this);
         Animator recentsAnimator = getRecentsWindowAnimator(taskView, !activityClosing, appTargets,
-                wallpaperTargets, null /* backgroundBlurController */,
+                wallpaperTargets, null /* depthController */,
                 helper);
         target.play(recentsAnimator.setDuration(RECENTS_LAUNCH_DURATION));
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
index 38b86ce..7ec083e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TaskViewUtils.java
@@ -19,7 +19,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
+import static com.android.launcher3.uioverrides.DepthController.DEPTH;
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
 
 import android.animation.Animator;
@@ -35,7 +35,7 @@
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.ItemInfo;
 import com.android.launcher3.Utilities;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.DepthController;
 import com.android.quickstep.util.AppWindowAnimationHelper;
 import com.android.quickstep.util.MultiValueUpdateListener;
 import com.android.quickstep.views.RecentsView;
@@ -123,7 +123,7 @@
     public static Animator getRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
             RemoteAnimationTargetCompat[] appTargets,
             RemoteAnimationTargetCompat[] wallpaperTargets,
-            BackgroundBlurController backgroundBlurController,
+            DepthController depthController,
             final AppWindowAnimationHelper inOutHelper) {
         SyncRtSurfaceTransactionApplierCompat applier =
                 new SyncRtSurfaceTransactionApplierCompat(v);
@@ -215,9 +215,9 @@
             }
         });
 
-        if (backgroundBlurController != null) {
-            ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(backgroundBlurController,
-                    BACKGROUND_BLUR, BACKGROUND_APP.getBackgroundBlurRadius(v.getContext()));
+        if (depthController != null) {
+            ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController,
+                    DEPTH, BACKGROUND_APP.getDepth(v.getContext()));
             animatorSet.playTogether(appAnimator, backgroundRadiusAnim);
         } else {
             animatorSet.play(appAnimator);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 0f63336..eb5c7f9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -15,7 +15,9 @@
  */
 package com.android.quickstep;
 
+import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_UP;
 
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
@@ -50,8 +52,6 @@
 import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.allapps.DiscoveryBounce;
 import com.android.launcher3.config.FeatureFlags;
@@ -351,17 +351,6 @@
                 OverscrollPlugin.class, false /* allowMultiple */);
     }
 
-    private void onDeferredActivityLaunch() {
-        if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
-            mOverviewComponentObserver.getActivityInterface().switchRunningTaskViewToScreenshot(
-                    null, () -> {
-                        mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
-                    });
-        } else {
-            mTaskAnimationManager.finishRunningRecentsAnimation(true /* toHome */);
-        }
-    }
-
     private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
         if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
             // Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
@@ -448,7 +437,8 @@
                 TraceHelper.FLAG_ALLOW_BINDER_TRACKING);
         mDeviceState.setOrientationTransformIfNeeded(event);
 
-        if (event.getAction() == ACTION_DOWN) {
+        final int action = event.getAction();
+        if (action == ACTION_DOWN) {
             GestureState newGestureState = new GestureState(mOverviewComponentObserver,
                     ActiveGestureLog.INSTANCE.generateAndSetLogId());
             newGestureState.updateRunningTask(TraceHelper.whitelistIpcs("getRunningTask.0",
@@ -466,8 +456,11 @@
                 // Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we should
                 // not interrupt it. QuickSwitch assumes that interruption can only happen if the
                 // next gesture is also quick switch.
-                mUncheckedConsumer = new AssistantInputConsumer(this, newGestureState,
-                        InputConsumer.NO_OP, mInputMonitorCompat);
+                mUncheckedConsumer = new AssistantInputConsumer(
+                    this,
+                    newGestureState,
+                    InputConsumer.NO_OP, mInputMonitorCompat,
+                    mOverviewComponentObserver.assistantGestureIsConstrained());
             } else {
                 mUncheckedConsumer = InputConsumer.NO_OP;
             }
@@ -478,6 +471,13 @@
 
         ActiveGestureLog.INSTANCE.addLog("onMotionEvent", event.getActionMasked());
         mUncheckedConsumer.onMotionEvent(event);
+
+        if (action == ACTION_UP || action == ACTION_CANCEL) {
+            if (mConsumer != null && !mConsumer.isConsumerDetachedFromGesture()) {
+                onConsumerInactive(mConsumer);
+            }
+        }
+
         TraceHelper.INSTANCE.endFlagsOverride(traceToken);
     }
 
@@ -502,10 +502,17 @@
                         ? newBaseConsumer(previousGestureState, newGestureState, event)
                         : mResetGestureInputConsumer;
         // TODO(b/149880412): 2 button landscape mode is wrecked. Fixit!
-        if (mDeviceState.isFullyGesturalNavMode()) {
+        if (mDeviceState.isGesturalNavMode()) {
             handleOrientationSetup(base);
+        }
+        if (mDeviceState.isFullyGesturalNavMode()) {
             if (mDeviceState.canTriggerAssistantAction(event)) {
-                base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat);
+                base = new AssistantInputConsumer(
+                    this,
+                    newGestureState,
+                    base,
+                    mInputMonitorCompat,
+                    mOverviewComponentObserver.assistantGestureIsConstrained());
             }
 
             if (FeatureFlags.ENABLE_QUICK_CAPTURE_GESTURE.get()) {
@@ -548,19 +555,19 @@
     }
 
     private void handleOrientationSetup(InputConsumer baseInputConsumer) {
-        if (!PagedView.sFlagForcedRotation) {
+        if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
             return;
         }
         mDeviceState.enableMultipleRegions(baseInputConsumer instanceof OtherActivityInputConsumer);
-        Launcher l = (Launcher) mOverviewComponentObserver
-            .getActivityInterface().getCreatedActivity();
-        if (l == null || !(l.getOverviewPanel() instanceof RecentsView)) {
+        BaseDraggingActivity activity =
+                mOverviewComponentObserver.getActivityInterface().getCreatedActivity();
+        if (activity == null || !(activity.getOverviewPanel() instanceof RecentsView)) {
             return;
         }
-        ((RecentsView)l.getOverviewPanel())
+        ((RecentsView) activity.getOverviewPanel())
             .setLayoutRotation(mDeviceState.getCurrentActiveRotation(),
                 mDeviceState.getDisplayRotation());
-        l.getDragLayer().recreateControllers();
+        activity.getDragLayer().recreateControllers();
     }
 
     private InputConsumer newBaseConsumer(GestureState previousGestureState,
@@ -616,11 +623,7 @@
 
         if (!mOverviewComponentObserver.isHomeAndOverviewSame()) {
             shouldDefer = previousGestureState.getFinishingRecentsAnimationTaskId() < 0;
-            if (mDeviceState.isFullyGesturalNavMode()) {
-                factory = mFallbackSwipeHandlerFactory;
-            } else {
-                factory = this::determineFallbackTwoButtonSwipeHandler;
-            }
+            factory = mFallbackSwipeHandlerFactory;
         } else {
             shouldDefer = gestureState.getActivityInterface().deferStartingActivity(mDeviceState,
                     event);
@@ -633,23 +636,6 @@
                 mInputMonitorCompat, disableHorizontalSwipe, factory);
     }
 
-    /**
-     * Determines whether to use the LauncherSwipeHandler or FallbackSwipeHandler at runtime.
-     * We need to use the FallbackSwipeHandler to handle quick switch from home, otherwise the
-     * normal LauncherSwipeHandler works.
-     */
-    private BaseSwipeUpHandler determineFallbackTwoButtonSwipeHandler(GestureState gestureState,
-            long touchTimeMs, boolean continuingLastGesture, boolean isLikelyToStartNewTask) {
-        boolean runningOverHome = gestureState.getRunningTask() == null
-                || ActivityManagerWrapper.isHomeTask(gestureState.getRunningTask());
-        boolean isQuickSwitchMode = isLikelyToStartNewTask || continuingLastGesture;
-        BaseSwipeUpHandler.Factory factory = runningOverHome && isQuickSwitchMode
-                ? mFallbackSwipeHandlerFactory
-                : mLauncherSwipeHandlerFactory;
-        return factory.newHandler(gestureState, touchTimeMs, continuingLastGesture,
-                isLikelyToStartNewTask);
-    }
-
     private InputConsumer createDeviceLockedInputConsumer(GestureState gestureState) {
         if (mDeviceState.isFullyGesturalNavMode() && gestureState.getRunningTask() != null) {
             return new DeviceLockedInputConsumer(this, mDeviceState, mTaskAnimationManager,
@@ -685,8 +671,8 @@
      */
     private void onConsumerInactive(InputConsumer caller) {
         if (mConsumer == caller) {
-            mConsumer = mResetGestureInputConsumer;
-            mUncheckedConsumer = mConsumer;
+            mConsumer = mUncheckedConsumer = mResetGestureInputConsumer;
+            mGestureState = new GestureState();
         }
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
index de5fd7c..7f5ec9b 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/fallback/RecentsRootView.java
@@ -21,7 +21,6 @@
 import android.graphics.Rect;
 import android.util.AttributeSet;
 
-import com.android.launcher3.BaseActivity;
 import com.android.launcher3.R;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TouchController;
@@ -31,13 +30,11 @@
 public class RecentsRootView extends BaseDragLayer<RecentsActivity> {
 
     private static final int MIN_SIZE = 10;
-    private final RecentsActivity mActivity;
 
     private final Point mLastKnownSize = new Point(MIN_SIZE, MIN_SIZE);
 
     public RecentsRootView(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
-        mActivity = BaseActivity.fromContext(context);
         setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                 | SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                 | SYSTEM_UI_FLAG_LAYOUT_STABLE);
@@ -47,7 +44,8 @@
         return mLastKnownSize;
     }
 
-    public void setup() {
+    @Override
+    public void recreateControllers() {
         mControllers = new TouchController[] {
                 new RecentsTaskController(mActivity),
                 new FallbackNavBarTouchController(mActivity),
@@ -83,7 +81,8 @@
         if (!insets.equals(mInsets)) {
             super.setInsets(insets);
         }
-        setBackground(insets.top == 0 ? null
+        setBackground(insets.top == 0  || !mAllowSysuiScrims
+                ? null
                 : Themes.getAttrDrawable(getContext(), R.attr.workspaceStatusBarScrim));
     }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
index 94126ff..89e6931 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/AssistantInputConsumer.java
@@ -90,12 +90,18 @@
     private final float mSquaredSlop;
     private final Context mContext;
     private final GestureDetector mGestureDetector;
+    private final boolean mIsAssistGestureConstrained;
 
-    public AssistantInputConsumer(Context context, GestureState gestureState,
-            InputConsumer delegate, InputMonitorCompat inputMonitor) {
+    public AssistantInputConsumer(
+            Context context,
+            GestureState gestureState,
+            InputConsumer delegate,
+            InputMonitorCompat inputMonitor,
+            boolean isAssistGestureConstrained) {
         super(delegate, inputMonitor);
         final Resources res = context.getResources();
         mContext = context;
+        mIsAssistGestureConstrained = isAssistGestureConstrained;
         mDragDistThreshold = res.getDimension(R.dimen.gestures_assistant_drag_threshold);
         mFlingDistThreshold = res.getDimension(R.dimen.gestures_assistant_fling_threshold);
         mTimeThreshold = res.getInteger(R.integer.assistant_gesture_min_time_threshold);
@@ -258,7 +264,8 @@
     private class AssistantGestureListener extends SimpleOnGestureListener {
         @Override
         public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
-            if (isValidAssistantGestureAngle(velocityX, -velocityY)
+            if (!mIsAssistGestureConstrained
+                && isValidAssistantGestureAngle(velocityX, -velocityY)
                     && mDistance >= mFlingDistThreshold
                     && !mLaunchedAssistant
                     && mState != STATE_DELEGATE_ACTIVE) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
index 05c206f..a87e7eb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/DelegateInputConsumer.java
@@ -25,6 +25,11 @@
     }
 
     @Override
+    public boolean isConsumerDetachedFromGesture() {
+        return mDelegate.isConsumerDetachedFromGesture();
+    }
+
+    @Override
     public boolean allowInterceptByParent() {
         return mDelegate.allowInterceptByParent() && mState != STATE_ACTIVE;
     }
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 893868b..416d7a1 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -160,6 +160,11 @@
         return TYPE_OTHER_ACTIVITY;
     }
 
+    @Override
+    public boolean isConsumerDetachedFromGesture() {
+        return true;
+    }
+
     private void forceCancelGesture(MotionEvent ev) {
         int action = ev.getAction();
         ev.setAction(ACTION_CANCEL);
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 d2805ed..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,8 +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;
+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;
@@ -32,15 +34,13 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
-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,17 +145,20 @@
      * Setup workspace with 0 duration to prepare for our staggered animation.
      */
     private void prepareToAnimate(Launcher launcher) {
-        LauncherStateManager stateManager = launcher.getStateManager();
-        AnimatorSetBuilder builder = new AnimatorSetBuilder();
+        StateAnimationConfig config = new StateAnimationConfig();
+        config.animFlags = ANIM_ALL_COMPONENTS | SKIP_OVERVIEW;
+        config.duration = 0;
         // setRecentsAttachedToAppWindow() will animate recents out.
-        builder.addFlag(AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW);
-        stateManager.createAtomicAnimation(BACKGROUND_APP, NORMAL, builder, ANIM_ALL, 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.
      */
@@ -198,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/LauncherRecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
index f60da18..a027fea 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -42,13 +42,13 @@
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.LauncherStateManager.StateListener;
-import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.appprediction.PredictionUiStateManager;
 import com.android.launcher3.appprediction.PredictionUiStateManager.Client;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.DepthController;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.views.ScrimView;
@@ -287,7 +287,8 @@
 
     @Override
     protected boolean supportsVerticalLandscape() {
-        return PagedView.sFlagForcedRotation;
+        return FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()
+                && !mOrientationState.areMultipleLayoutOrientationsDisabled();
     }
 
     @Override
@@ -408,7 +409,7 @@
     }
 
     @Override
-    protected BackgroundBlurController getBackgroundBlurController() {
-        return mActivity.getBackgroundBlurController();
+    protected DepthController getDepthController() {
+        return mActivity.getDepthController();
     }
 }
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 8b0f138..68c51a0 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
@@ -19,6 +19,7 @@
 import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
 import static com.android.launcher3.InvariantDeviceProfile.CHANGE_FLAG_ICON_PARAMS;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
@@ -29,7 +30,7 @@
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
+import static com.android.launcher3.uioverrides.DepthController.DEPTH;
 import static com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController.SUCCESS_TRANSITION_PROGRESS;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch.TAP;
 import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType.CLEAR_ALL_BUTTON;
@@ -68,7 +69,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 +88,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,11 +95,10 @@
 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;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.DepthController;
 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;
@@ -169,7 +167,6 @@
                 }
             };
 
-    private final OrientationEventListener mOrientationListener;
     private int mPreviousRotation;
     protected RecentsAnimationController mRecentsAnimationController;
     protected RecentsAnimationTargets mRecentsAnimationTargets;
@@ -377,22 +374,6 @@
 
         // Initialize quickstep specific cache params here, as this is constructed only once
         mActivity.getViewCache().setCacheSize(R.layout.digital_wellbeing_toast, 5);
-        mOrientationListener = new OrientationEventListener(getContext()) {
-            @Override
-            public void onOrientationChanged(int i) {
-                int rotation = RotationHelper.getRotationFromDegrees(i);
-                if (mPreviousRotation != rotation) {
-                    animateRecentsRotationInPlace(rotation);
-                    if (rotation == 0) {
-                        showActionsView();
-                    } else {
-                        hideActionsView();
-                    }
-                    mPreviousRotation = rotation;
-                }
-            }
-        };
-
     }
 
     public OverScroller getScroller() {
@@ -521,13 +502,6 @@
     }
 
     public void setOverviewStateEnabled(boolean enabled) {
-        if (supportsVerticalLandscape() && mOrientationListener.canDetectOrientation()) {
-            if (enabled) {
-                mOrientationListener.enable();
-            } else {
-                mOrientationListener.disable();
-            }
-        }
         mOverviewStateEnabled = enabled;
         updateTaskStackListenerState();
         if (!enabled) {
@@ -744,6 +718,9 @@
         for (int i = 0; i < taskCount; i++) {
             getTaskViewAt(i).setFullscreenProgress(mFullscreenProgress);
         }
+        if (mActionsView != null) {
+            mActionsView.setVisibility(fullscreenProgress == 0 ? VISIBLE : INVISIBLE);
+        }
     }
 
     private void updateTaskStackListenerState() {
@@ -968,35 +945,6 @@
         setSwipeDownShouldLaunchApp(true);
     }
 
-    private void animateRecentsRotationInPlace(int newRotation) {
-        if (!supportsVerticalLandscape()) {
-            return;
-        }
-
-        AnimatorSet pa = setRecentsChangedOrientation(true);
-        pa.addListener(AnimationSuccessListener.forRunnable(() -> {
-            updateLayoutRotation(newRotation);
-            ((DragLayer) mActivity.getDragLayer()).recreateControllers();
-            rotateAllChildTasks();
-            setRecentsChangedOrientation(false).start();
-        }));
-        pa.start();
-    }
-
-    public AnimatorSet setRecentsChangedOrientation(boolean fadeInChildren) {
-        getRunningTaskIndex();
-        int runningIndex = getCurrentPage();
-        AnimatorSet as = new AnimatorSet();
-        for (int i = 0; i < getTaskViewCount(); i++) {
-            if (runningIndex == i) {
-                continue;
-            }
-            View taskView = getTaskViewAt(i);
-            as.play(ObjectAnimator.ofFloat(taskView, View.ALPHA, fadeInChildren ? 0 : 1));
-        }
-        return as;
-    }
-
     abstract protected boolean supportsVerticalLandscape();
 
     private void rotateAllChildTasks() {
@@ -1218,7 +1166,9 @@
     }
 
     private void addDismissedTaskAnimations(View taskView, long duration, PendingAnimation anim) {
-        anim.add(ObjectAnimator.ofFloat(taskView, ALPHA, 0).setDuration(duration), ACCEL_2);
+        // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
+        // alpha is set to 0 so that it can be recycled in the view pool properly
+        anim.setFloat(taskView, VIEW_ALPHA, 0, ACCEL_2);
         FloatProperty<View> secondaryViewTranslate =
             mOrientationHandler.getSecondaryViewTranslate();
         int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
@@ -1248,7 +1198,7 @@
         if (mPendingAnimation != null) {
             mPendingAnimation.finish(false, Touch.SWIPE);
         }
-        PendingAnimation anim = new PendingAnimation();
+        PendingAnimation anim = new PendingAnimation(duration);
 
         int count = getPageCount();
         if (count == 0) {
@@ -1310,9 +1260,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
@@ -1332,6 +1280,7 @@
                 }
             }
 
+            @SuppressWarnings("WrongCall")
             private void onEnd(EndState endState) {
                 if (endState.isSuccess) {
                     if (shouldRemoveTask) {
@@ -1343,15 +1292,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;
@@ -1364,7 +1316,7 @@
         if (FeatureFlags.IS_STUDIO_BUILD && mPendingAnimation != null) {
             throw new IllegalStateException("Another pending animation is still running");
         }
-        PendingAnimation anim = new PendingAnimation();
+        PendingAnimation anim = new PendingAnimation(duration);
 
         int count = getTaskViewCount();
         for (int i = 0; i < count; i++) {
@@ -1398,8 +1350,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);
@@ -1520,7 +1471,7 @@
 
     @Override
     public void setLayoutRotation(int touchRotation, int displayRotation) {
-        if (!sFlagForcedRotation) {
+        if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
             return;
         }
 
@@ -1602,6 +1553,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
@@ -1724,7 +1676,7 @@
 
         int count = getTaskViewCount();
         if (count == 0) {
-            return new PendingAnimation();
+            return new PendingAnimation(duration);
         }
 
         int targetSysUiFlags = tv.getThumbnail().getSysUiStatusNavFlags();
@@ -1756,17 +1708,16 @@
         appWindowAnimationHelper.prepareAnimation(mActivity.getDeviceProfile(), true /* isOpening */);
         AnimatorSet anim = createAdjacentPageAnimForTaskLaunch(tv, appWindowAnimationHelper);
 
-        BackgroundBlurController blurController = getBackgroundBlurController();
-        if (blurController != null) {
-            ObjectAnimator backgroundBlur = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
-                    BACKGROUND_APP.getBackgroundBlurRadius(mActivity));
-            anim.play(backgroundBlur);
+        DepthController depthController = getDepthController();
+        if (depthController != null) {
+            ObjectAnimator depthAnimator = ObjectAnimator.ofFloat(depthController, DEPTH,
+                    BACKGROUND_APP.getDepth(mActivity));
+            anim.play(depthAnimator);
         }
         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) {
@@ -1972,7 +1923,8 @@
     public Consumer<MotionEvent> getEventDispatcher(RotationMode navBarRotationMode) {
         float degreesRotated;
         if (navBarRotationMode == RotationMode.NORMAL) {
-            degreesRotated = RotationHelper.getDegreesFromRotation(mLayoutRotation);
+            degreesRotated = mOrientationState.areMultipleLayoutOrientationsDisabled() ? 0 :
+                    RotationHelper.getDegreesFromRotation(mLayoutRotation);
         } else {
             degreesRotated = -navBarRotationMode.surfaceRotation;
         }
@@ -1984,6 +1936,13 @@
         // undo that transformation since PagedView also accommodates for the transformation via
         // PagedOrientationHandler
         return e -> {
+            if (navBarRotationMode != RotationMode.NORMAL
+                    && !mOrientationState.areMultipleLayoutOrientationsDisabled()) {
+                RotationHelper.transformEventForNavBar(e, true);
+                super.onTouchEvent(e);
+                RotationHelper.transformEventForNavBar(e, false);
+                return;
+            }
             RotationHelper.transformEvent(-degreesRotated, e, true);
             super.onTouchEvent(e);
             RotationHelper.transformEvent(-degreesRotated, e, false);
@@ -2056,7 +2015,7 @@
     }
 
     @Nullable
-    protected BackgroundBlurController getBackgroundBlurController() {
+    protected DepthController getDepthController() {
         return null;
     }
 
@@ -2076,7 +2035,6 @@
         void onEmptyMessageUpdated(boolean isEmpty);
     }
 
-
     private static class PinnedStackAnimationListener<T extends BaseActivity> extends
             IPinnedStackAnimationListener.Stub {
         private T mActivity;
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..56e3632 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
@@ -247,8 +247,17 @@
 
     /** Updates UI based on whether the task is modal. */
     public void updateUiForModalTask() {
+        boolean isOverlayModal = isTaskOverlayModal();
         if (getRecentsView() != null) {
-            getRecentsView().updateUiForModalTask(this, isTaskOverlayModal());
+            getRecentsView().updateUiForModalTask(this, isOverlayModal);
+        }
+        // Hide footers when overlay is modal.
+        if (isOverlayModal) {
+            for (FooterWrapper footer : mFooters) {
+                if (footer != null) {
+                    footer.animateHide();
+                }
+            }
         }
     }
 
@@ -285,8 +294,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);
@@ -781,6 +789,22 @@
             animator.setDuration(100);
             animator.start();
         }
+
+        void animateHide() {
+            ValueAnimator animator = ValueAnimator.ofFloat(0.0f, 1.0f);
+            animator.addUpdateListener(anim -> {
+                mFooterVerticalOffset = anim.getAnimatedFraction();
+                updateFooterOffset();
+            });
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    removeView(mView);
+                }
+            });
+            animator.setDuration(100);
+            animator.start();
+        }
     }
 
     @Override
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 90d4245..b55b042 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -68,30 +68,25 @@
 
 
     <!-- Hotseat migration notification title -->
-    <string translatable="false" name="hotseat_edu_prompt_title">Get app suggestions based on your routines</string>
+    <string name="hotseat_edu_prompt_title">Easily access your most-used apps</string>
     <!-- Hotseat migration notification content -->
-    <string translatable="false" name="hotseat_edu_prompt_content">Tap to set up</string>
-
+    <string name="hotseat_edu_prompt_content">Pixel predicts apps you\’ll need next, right on your Home screen. Tap to set up.</string>
 
     <!-- Hotseat educational strings for users who don't qualify for migration -->
-    <string translatable="false" name="hotseat_edu_title_migrate">Suggested apps replace the bottom row of apps</string>
-    <string translatable="false" name="hotseat_edu_message_migrate">Your hotseat items will be moved up on the homescreen</string>
-    <string translatable="false" name="hotseat_edu_message_migrate_alt">Your hotseat items will be moved to the last page of your workspace</string>
+    <string name="hotseat_edu_title_migrate">Get app suggestions on the bottom row of your Home screen</string>
 
-
-    <!-- Hotseat educational strings for users who don't qualify -->
-    <string translatable="false" name="hotseat_edu_title_no_migrate">Suggested apps will be found at the bottom row of your home screen</string>
-    <string translatable="false" name="hotseat_edu_message_no_migrate">Drag one or many apps off the bottom row of home screen to see app suggestions</string>
+    <string name="hotseat_edu_message_migrate">Easily access your most-used apps right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move up to your Home screen. </string>
+    <string name="hotseat_edu_message_migrate_alt">Easily access your most-used apps, right on the Home screen. Suggestions will change based on your routines. Apps on the bottom row will move to a new folder.</string>
 
     <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
-    <string translatable="false" name="hotseat_items_migrated">Bottom row of apps moved up.</string>
-    <string translatable="false" name="hotseat_items_migrated_alt">Bottom row of apps moved to last page.</string>
+    <string name="hotseat_items_migrated">Your hotseat items have been moved up to your homescreen</string>
+    <string name="hotseat_items_migrated_alt">Your hotseat items have been moved to a folder</string>
     <!-- Toast message user sees after opting into fully predicted hybrid hotseat -->
-    <string translatable="false" name="hotseat_no_migration">Bottom row won\'t be replaced. Manually drag apps for predictions.</string>
+    <string name="hotseat_no_migration">Drag apps off the bottom row to see app suggestions</string>
     <!-- Button text to opt in for fully predicted hotseat -->
-    <string translatable="false" name="hotseat_edu_accept">Got it</string>
+    <string name="hotseat_edu_accept">Get app suggestions</string>
     <!-- Button text to dismiss opt in for fully predicted hotseat -->
-    <string translatable="false" name="hotseat_edu_dismiss">No thanks</string>
+    <string name="hotseat_edu_dismiss">No thanks</string>
 
 
     <!-- Title shown during interactive part of Back gesture tutorial for right edge. [CHAR LIMIT=30] -->
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 809543a..ec66f11 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;
 
@@ -242,7 +244,7 @@
         return new StateHandler[] {
                 getAllAppsController(),
                 getWorkspace(),
-                getBackgroundBlurController(),
+                getDepthController(),
                 new RecentsViewStateController(this),
                 new BackButtonAlphaHandler(this)};
     }
@@ -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..c93a4ba 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,8 +31,9 @@
 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.uioverrides.DepthController.DEPTH;
 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius;
@@ -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;
@@ -64,17 +69,18 @@
 
 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.allapps.AllAppsTransitionController;
-import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.shortcuts.DeepShortcutView;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.DepthController;
+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 +92,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 +161,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 +304,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;
                 }
             }
@@ -377,18 +386,35 @@
             alpha.setInterpolator(LINEAR);
             launcherAnimator.play(alpha);
 
-            mDragLayer.setTranslationY(trans[0]);
-            ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans);
-            transY.setInterpolator(AGGRESSIVE_EASE);
-            transY.setDuration(CONTENT_TRANSLATION_DURATION);
-            launcherAnimator.play(transY);
+            Workspace workspace = mLauncher.getWorkspace();
+            View currentPage = ((CellLayout) workspace.getChildAt(workspace.getCurrentPage()))
+                    .getShortcutsAndWidgets();
+            View hotseat = mLauncher.getHotseat();
+            View qsb = mLauncher.findViewById(R.id.search_container_all_apps);
 
-            mDragLayer.getScrim().hideSysUiScrim(true);
+            currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            qsb.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+            launcherAnimator.play(ObjectAnimator.ofFloat(currentPage, View.TRANSLATION_Y, trans));
+            launcherAnimator.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, trans));
+            launcherAnimator.play(ObjectAnimator.ofFloat(qsb, View.TRANSLATION_Y, trans));
+
             // Pause page indicator animations as they lead to layer trashing.
             mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
-            mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
 
-            endListener = this::resetContentView;
+            endListener = () -> {
+                currentPage.setTranslationY(0);
+                hotseat.setTranslationY(0);
+                qsb.setTranslationY(0);
+
+                currentPage.setLayerType(View.LAYER_TYPE_NONE, null);
+                hotseat.setLayerType(View.LAYER_TYPE_NONE, null);
+                qsb.setLayerType(View.LAYER_TYPE_NONE, null);
+
+                mDragLayerAlpha.setValue(1f);
+                mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
+            };
         }
         return new Pair<>(launcherAnimator, endListener);
     }
@@ -460,6 +486,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 +574,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 +597,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));
@@ -578,17 +612,17 @@
         // When launching an app from overview that doesn't map to a task, we still want to just
         // blur the wallpaper instead of the launcher surface as well
         boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW;
-        BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
-        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR,
-                BACKGROUND_APP.getBackgroundBlurRadius(mLauncher))
+        DepthController depthController = mLauncher.getDepthController();
+        ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH,
+                BACKGROUND_APP.getDepth(mLauncher))
                 .setDuration(APP_LAUNCH_DURATION);
         if (allowBlurringLauncher) {
-            blurController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
+            depthController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget(
                     appTargets, MODE_OPENING));
             backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
-                    blurController.setSurfaceToLauncher(mLauncher.getDragLayer());
+                    depthController.setSurfaceToLauncher(mLauncher.getDragLayer());
                 }
             });
         }
@@ -612,6 +646,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 +673,7 @@
             // definition so we don't have to wait for the system gc
             mWallpaperOpenRunner = null;
             mAppLaunchRunner = null;
+            mKeyguardGoingAwayRunner = null;
         }
     }
 
@@ -662,7 +708,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 +727,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 +744,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();
                 }
@@ -723,114 +776,12 @@
         return closingAnimator;
     }
 
-    /**
-     * Creates an animator that modifies Launcher as a result from 
-     * {@link #createWallpaperOpenRunner}.
-     */
-    private void createLauncherResumeAnimation(AnimatorSet anim) {
-        if (mLauncher.isInState(LauncherState.ALL_APPS)) {
-            Pair<AnimatorSet, Runnable> contentAnimator =
-                    getLauncherContentAnimator(false /* isAppOpening */,
-                            new float[] {-mContentTransY, 0});
-            contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
-            anim.play(contentAnimator.first);
-            anim.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    contentAnimator.second.run();
-                }
-            });
-        } else {
-            AnimatorSet workspaceAnimator = new AnimatorSet();
-
-            mDragLayer.setTranslationY(-mWorkspaceTransY);;
-            workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y,
-                    -mWorkspaceTransY, 0));
-
-            mDragLayerAlpha.setValue(0);
-            workspaceAnimator.play(ObjectAnimator.ofFloat(
-                    mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f));
-
-            workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY);
-            workspaceAnimator.setDuration(333);
-            workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7);
-
-            mDragLayer.getScrim().hideSysUiScrim(true);
-
-            // Pause page indicator animations as they lead to layer trashing.
-            mLauncher.getWorkspace().getPageIndicator().pauseAnimations();
-            mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-
-            workspaceAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    resetContentView();
-                }
-            });
-            anim.play(workspaceAnimator);
-        }
-    }
-
-    private void resetContentView() {
-        mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd();
-        mDragLayerAlpha.setValue(1f);
-        mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null);
-        mDragLayer.setTranslationY(0f);
-        mDragLayer.getScrim().hideSysUiScrim(false);
-    }
-
     private boolean hasControlRemoteAppTransitionPermission() {
         return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
                 == PackageManager.PERMISSION_GRANTED;
     }
 
     /**
-     * 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,11 +847,12 @@
                         || mLauncher.isForceInvisible()) {
                     // Only register the content animation for cancellation when state changes
                     mLauncher.getStateManager().setCurrentAnimation(anim);
-                    if (mFromUnlock) {
+
+                    if (mLauncher.isInState(LauncherState.ALL_APPS)) {
                         Pair<AnimatorSet, Runnable> contentAnimator =
                                 getLauncherContentAnimator(false /* isAppOpening */,
-                                        new float[] {mContentTransY, 0});
-                        contentAnimator.first.setStartDelay(0);
+                                        new float[] {-mContentTransY, 0});
+                        contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY);
                         anim.play(contentAnimator.first);
                         anim.addListener(new AnimatorListenerAdapter() {
                             @Override
@@ -909,7 +861,12 @@
                             }
                         });
                     } else {
-                        createLauncherResumeAnimation(anim);
+                        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());
                     }
                 }
             }
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 43dc882..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,9 +42,9 @@
     public void setState(LauncherState state) { }
 
     @Override
-    public void setStateWithAnimation(LauncherState toState,
-            AnimatorSetBuilder builder, LauncherStateManager.AnimationConfig config) {
-        if (!config.playNonAtomicComponent()) {
+    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
deleted file mode 100644
index 9e4ada7..0000000
--- a/quickstep/src/com/android/launcher3/uioverrides/BackgroundBlurController.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * 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.anim.Interpolators.LINEAR;
-
-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.R;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.anim.PropertySetter;
-import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
-import com.android.systemui.shared.system.SurfaceControlCompat;
-import com.android.systemui.shared.system.TransactionCompat;
-
-/**
- * Controls the blur, for the Launcher surface only.
- */
-public class BackgroundBlurController implements LauncherStateManager.StateHandler {
-
-    public static final IntProperty<BackgroundBlurController> BACKGROUND_BLUR =
-            new IntProperty<BackgroundBlurController>("backgroundBlur") {
-                @Override
-                public void setValue(BackgroundBlurController blurController, int blurRadius) {
-                    blurController.setBackgroundBlurRadius(blurRadius);
-                }
-
-                @Override
-                public Integer get(BackgroundBlurController blurController) {
-                    return blurController.mBackgroundBlurRadius;
-                }
-            };
-
-    /**
-     * A property that updates the background blur within a given range of values (ie. even if the
-     * animator goes beyond 0..1, the interpolated value will still be bounded).
-     */
-    public static class ClampedBlurProperty extends IntProperty<BackgroundBlurController> {
-        private final int mMinValue;
-        private final int mMaxValue;
-
-        public ClampedBlurProperty(int minValue, int maxValue) {
-            super(("backgroundBlurClamped"));
-            mMinValue = minValue;
-            mMaxValue = maxValue;
-        }
-
-        @Override
-        public void setValue(BackgroundBlurController blurController, int blurRadius) {
-            blurController.setBackgroundBlurRadius(Utilities.boundToRange(blurRadius,
-                    mMinValue, mMaxValue));
-        }
-
-        @Override
-        public Integer get(BackgroundBlurController blurController) {
-            return blurController.mBackgroundBlurRadius;
-        }
-    }
-
-    private final Launcher mLauncher;
-    private SurfaceControlCompat mSurface;
-    private int mBackgroundBlurRadius;
-
-    public BackgroundBlurController(Launcher l) {
-        mLauncher = l;
-    }
-
-    /**
-     * @return the background blur adjustment for folders
-     */
-    public int getFolderBackgroundBlurAdjustment() {
-        return mLauncher.getResources().getInteger(
-                R.integer.folder_background_blur_radius_adjustment);
-    }
-
-    /**
-     * Sets the specified app target surface to apply the blur to.
-     */
-    public void setSurfaceToApp(RemoteAnimationTargetCompat target) {
-        if (target != null) {
-            setSurface(target.leash);
-        }
-    }
-
-    /**
-     * Sets the surface to apply the blur to as the launcher surface.
-     */
-    public void setSurfaceToLauncher(View v) {
-        setSurface(v != null ? new SurfaceControlCompat(v) : null);
-    }
-
-    private void setSurface(SurfaceControlCompat surface) {
-        if (mSurface != surface) {
-            mSurface = surface;
-            if (surface != null) {
-                setBackgroundBlurRadius(mBackgroundBlurRadius);
-            } else {
-                // If there is no surface, then reset the blur radius
-                setBackgroundBlurRadius(0);
-            }
-        }
-    }
-
-    @Override
-    public void setState(LauncherState toState) {
-        if (mSurface == null) {
-            return;
-        }
-
-        int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
-        if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
-            setBackgroundBlurRadius(toBackgroundBlurRadius);
-        }
-    }
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
-            LauncherStateManager.AnimationConfig config) {
-        if (mSurface == null || !config.playNonAtomicComponent()) {
-            return;
-        }
-
-        int toBackgroundBlurRadius = toState.getBackgroundBlurRadius(mLauncher);
-        if (mBackgroundBlurRadius != toBackgroundBlurRadius) {
-            PropertySetter propertySetter = config.getPropertySetter(builder);
-            propertySetter.setInt(this, BACKGROUND_BLUR, toBackgroundBlurRadius, LINEAR);
-        }
-    }
-
-    private void setBackgroundBlurRadius(int blurRadius) {
-        // TODO: Do nothing if the shadows are not enabled
-        // Always update the background blur as it will be reapplied when a surface is next
-        // available
-        mBackgroundBlurRadius = blurRadius;
-        if (mSurface == null || !mSurface.isValid()) {
-            return;
-        }
-        new TransactionCompat()
-                .setBackgroundBlurRadius(mSurface, blurRadius)
-                .apply();
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
index 9b57fb7..03454f7 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/BaseRecentsViewStateController.java
@@ -20,15 +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.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.AnimatorSetBuilder.FLAG_DONT_ANIMATE_OVERVIEW;
 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;
@@ -39,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
@@ -84,36 +85,33 @@
     }
 
     @Override
-    public final void setStateWithAnimation(@NonNull final LauncherState toState,
-            @NonNull AnimatorSetBuilder builder, @NonNull AnimationConfig config) {
-        boolean playAtomicOverviewComponent = config.playAtomicOverviewScaleComponent()
-                || config.playAtomicOverviewPeekComponent();
-        if (!playAtomicOverviewComponent) {
+    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;
         }
-        if (builder.hasFlag(FLAG_DONT_ANIMATE_OVERVIEW)) {
+        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/DepthController.java b/quickstep/src/com/android/launcher3/uioverrides/DepthController.java
new file mode 100644
index 0000000..8995a7e
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/DepthController.java
@@ -0,0 +1,172 @@
+/*
+ * 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.anim.Interpolators.LINEAR;
+
+import android.os.IBinder;
+import android.util.FloatProperty;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+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;
+import com.android.systemui.shared.system.WallpaperManagerCompat;
+
+/**
+ * Controls blur and wallpaper zoom, for the Launcher surface only.
+ */
+public class DepthController implements LauncherStateManager.StateHandler {
+
+    public static final FloatProperty<DepthController> DEPTH =
+            new FloatProperty<DepthController>("depth") {
+                @Override
+                public void setValue(DepthController depthController, float depth) {
+                    depthController.setDepth(depth);
+                }
+
+                @Override
+                public Float get(DepthController depthController) {
+                    return depthController.mDepth;
+                }
+            };
+
+    /**
+     * A property that updates the background blur within a given range of values (ie. even if the
+     * animator goes beyond 0..1, the interpolated value will still be bounded).
+     */
+    public static class ClampedDepthProperty extends FloatProperty<DepthController> {
+        private final float mMinValue;
+        private final float mMaxValue;
+
+        public ClampedDepthProperty(float minValue, float maxValue) {
+            super("depthClamped");
+            mMinValue = minValue;
+            mMaxValue = maxValue;
+        }
+
+        @Override
+        public void setValue(DepthController depthController, float depth) {
+            depthController.setDepth(Utilities.boundToRange(depth, mMinValue, mMaxValue));
+        }
+
+        @Override
+        public Float get(DepthController depthController) {
+            return depthController.mDepth;
+        }
+    }
+
+    private final Launcher mLauncher;
+    /**
+     * Blur radius when completely zoomed out, in pixels.
+     */
+    private int mMaxBlurRadius;
+    private WallpaperManagerCompat mWallpaperManager;
+    private SurfaceControlCompat mSurface;
+    /**
+     * Ratio from 0 to 1, where 0 is fully zoomed out, and 1 is zoomed in.
+     * @see android.service.wallpaper.WallpaperService.Engine#onZoomChanged(float)
+     */
+    private float mDepth;
+
+    public DepthController(Launcher l) {
+        mLauncher = l;
+    }
+
+    private void ensureDependencies() {
+        if (mWallpaperManager != null) {
+            return;
+        }
+        mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
+        mWallpaperManager = new WallpaperManagerCompat(mLauncher);
+    }
+
+    /**
+     * Sets the specified app target surface to apply the blur to.
+     */
+    public void setSurfaceToApp(RemoteAnimationTargetCompat target) {
+        if (target != null) {
+            setSurface(target.leash);
+        }
+    }
+
+    /**
+     * Sets the surface to apply the blur to as the launcher surface.
+     */
+    public void setSurfaceToLauncher(View v) {
+        setSurface(v != null ? new SurfaceControlCompat(v) : null);
+    }
+
+    private void setSurface(SurfaceControlCompat surface) {
+        if (mSurface != surface) {
+            mSurface = surface;
+            if (surface != null) {
+                setDepth(mDepth);
+            } else {
+                // If there is no surface, then reset the ratio
+                setDepth(0f);
+            }
+        }
+    }
+
+    @Override
+    public void setState(LauncherState toState) {
+        if (mSurface == null) {
+            return;
+        }
+
+        float toDepth = toState.getDepth(mLauncher);
+        if (Float.compare(mDepth, toDepth) != 0) {
+            setDepth(toDepth);
+        }
+    }
+
+    @Override
+    public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
+            PendingAnimation animation) {
+        if (mSurface == null || config.onlyPlayAtomicComponent()) {
+            return;
+        }
+
+        float toDepth = toState.getDepth(mLauncher);
+        if (Float.compare(mDepth, toDepth) != 0) {
+            animation.setFloat(this, DEPTH, toDepth, LINEAR);
+        }
+    }
+
+    private void setDepth(float depth) {
+        mDepth = depth;
+        if (mSurface == null || !mSurface.isValid()) {
+            return;
+        }
+        ensureDependencies();
+        IBinder windowToken = mLauncher.getRootView().getWindowToken();
+        if (windowToken != null) {
+            mWallpaperManager.setWallpaperZoomOut(windowToken, mDepth);
+        }
+        new TransactionCompat()
+                .setBackgroundBlurRadius(mSurface, (int) (mDepth * mMaxBlurRadius))
+                .apply();
+    }
+}
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..c7cce0b
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/uioverrides/PreviewSurfaceRenderer.java
@@ -0,0 +1,48 @@
+/*
+ * 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) {
+        String gridName = bundle.getString("name");
+        bundle.remove("name");
+        if (gridName == null) {
+            gridName = InvariantDeviceProfile.getCurrentGridName(context);
+        }
+        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/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 971d917..93e02a1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -22,7 +22,6 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherState;
-import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 import com.android.quickstep.SysUINavigationMode;
@@ -88,8 +87,8 @@
     }
 
     @Override
-    public int getBackgroundBlurRadius(Context context) {
-        return context.getResources().getInteger(R.integer.allapps_background_blur_radius);
+    public float getDepth(Context context) {
+        return 1f;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/LandscapeEdgeSwipeController.java
index 3cb0088..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.AnimationComponents;
+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;
@@ -56,7 +56,7 @@
     }
 
     @Override
-    protected float initCurrentAnimation(@AnimationComponents int animComponent) {
+    protected float initCurrentAnimation(@AnimationFlags int animComponent) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
         mCurrentAnimation = mLauncher.getStateManager().createAnimationToNewWorkspace(mToState,
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 95e38e3..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.AnimationComponents;
 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,40 +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(@AnimationComponents int animComponents) {
+    protected float initCurrentAnimation(@AnimationFlags int animFlags) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
 
@@ -216,9 +217,10 @@
 
         float totalShift = endVerticalShift - startVerticalShift;
 
-        final AnimatorSetBuilder builder = totalShift == 0 ? new AnimatorSetBuilder()
-                : getAnimatorSetBuilderForStates(mFromState, mToState);
-        updateAnimatorBuilderOnReinit(builder);
+        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) {
@@ -253,7 +255,9 @@
     /**
      * Give subclasses the chance to update the animation when we re-initialize towards a new state.
      */
-    protected void updateAnimatorBuilderOnReinit(AnimatorSetBuilder builder) {
+    @AnimationFlags
+    protected int updateAnimComponentsOnReinit(@AnimationFlags int animComponents) {
+        return animComponents;
     }
 
     private void cancelPendingAnim() {
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index be0bdd8..2a569f5 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -33,7 +33,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.anim.AnimatorPlaybackController;
 import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.DepthController;
 import com.android.quickstep.util.ActivityInitListener;
 import com.android.quickstep.util.ShelfPeekAnim;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -81,7 +81,8 @@
     @Nullable
     T getCreatedActivity();
 
-    default @Nullable BackgroundBlurController getBackgroundBlurController() {
+    @Nullable
+    default DepthController getDepthController() {
         return null;
     }
 
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index 3e84e7d..8efaeb9 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -60,6 +60,16 @@
     }
 
     /**
+     * Returns true if the lifecycle of this input consumer is detached from the normal gesture
+     * down/up flow. If so, it is the responsibility of the input consumer to call back to
+     * {@link TouchInteractionService#onConsumerInactive(InputConsumer)} after the consumer is
+     * finished.
+     */
+    default boolean isConsumerDetachedFromGesture() {
+        return false;
+    }
+
+    /**
      * Called by the event queue when the consumer is about to be switched to a new consumer.
      * Consumers should update the state accordingly here before the state is passed to the new
      * consumer.
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 92eb036..3e73f49 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -75,8 +75,12 @@
         mContractInfo = contractInfo;
     }
 
-    void setNavigationMode(SysUINavigationMode.Mode newMode) {
+    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info) {
+        if (mMode == newMode) {
+            return;
+        }
         this.mMode = newMode;
+        resetSwipeRegions(info);
     }
 
     /**
@@ -120,7 +124,9 @@
             mQuickStepStartingRotation = -1;
             resetSwipeRegions(info);
         } else {
-            if (mQuickStepStartingRotation < 0) {
+            if (mLastRectTouched != null) {
+                // mLastRectTouched can be null if gesture type is changed (ex. from settings)
+                // but nav bar hasn't been interacted with yet.
                 mQuickStepStartingRotation = mLastRectTouched.mRotation;
             }
         }
@@ -138,10 +144,8 @@
         }
 
         mCurrentRotation = region.rotation;
-        OrientationRectF regionToKeep = mSwipeTouchRegions.get(mCurrentRotation);
         mSwipeTouchRegions.clear();
-        mSwipeTouchRegions.put(mCurrentRotation,
-                regionToKeep != null ? regionToKeep : createRegionForDisplay(region));
+        mSwipeTouchRegions.put(mCurrentRotation, createRegionForDisplay(region));
     }
 
     private OrientationRectF createRegionForDisplay(DefaultDisplay.Info display) {
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 866836e..9edc86e 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -22,6 +22,7 @@
 
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 import static com.android.systemui.shared.system.PackageManagerWrapper.ACTION_PREFERRED_ACTIVITY_CHANGED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -104,6 +105,10 @@
         updateOverviewTargets();
     }
 
+    public boolean assistantGestureIsConstrained() {
+        return (mDeviceState.getSystemUiStateFlags() & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0;
+    }
+
     /**
      * Update overview intent and {@link BaseActivityInterface} based off the current launcher home
      * component.
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 85464aa..1299a53 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -47,9 +47,9 @@
 
 import androidx.annotation.BinderThread;
 
-import com.android.launcher3.PagedView;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
@@ -100,13 +100,23 @@
         }
     };
 
+    private TaskStackChangeListener mFrozenTaskListener = new TaskStackChangeListener() {
+        @Override
+        public void onRecentTaskListFrozenChanged(boolean frozen) {
+            if (frozen) {
+                return;
+            }
+            mOrientationTouchTransformer.enableMultipleRegions(false, mDefaultDisplay.getInfo());
+        }
+    };
+
     private OrientationTouchTransformer mOrientationTouchTransformer;
 
     private Region mExclusionRegion;
     private SystemGestureExclusionListenerCompat mExclusionListener;
 
     private final List<ComponentName> mGestureBlockedActivities;
-    private TaskStackChangeListener mFrozenTaskListener;
+    private Runnable mOnDestroyFrozenTaskRunnable;
 
     public RecentsAnimationDeviceState(Context context) {
         final ContentResolver resolver = context.getContentResolver();
@@ -136,7 +146,9 @@
         };
         runOnDestroy(mExclusionListener::unregister);
 
-        setupOrientationSwipeHandler(context);
+        Resources resources = mContext.getResources();
+        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
+                () -> QuickStepContract.getWindowCornerRadius(resources));
 
         // Register for navigation mode changes
         onNavigationModeChanged(mSysUiNavMode.addModeChangeListener(this));
@@ -159,24 +171,20 @@
         }
     }
 
-    private void setupOrientationSwipeHandler(Context context) {
-        final Resources resources = context.getResources();
-        mOrientationTouchTransformer = new OrientationTouchTransformer(resources, mMode,
-                () -> QuickStepContract.getWindowCornerRadius(resources));
-
-        if (!PagedView.sFlagForcedRotation) {
+    private void setupOrientationSwipeHandler() {
+        if (!FeatureFlags.ENABLE_FIXED_ROTATION_TRANSFORM.get()) {
             return;
         }
 
-        mFrozenTaskListener = new TaskStackChangeListener() {
-            @Override
-            public void onRecentTaskListFrozenChanged(boolean frozen) {
-                mOrientationTouchTransformer.enableMultipleRegions(frozen, mDefaultDisplay.getInfo());
-            }
-        };
         ActivityManagerWrapper.getInstance().registerTaskStackListener(mFrozenTaskListener);
-        runOnDestroy(() -> ActivityManagerWrapper.getInstance()
-                .unregisterTaskStackListener(mFrozenTaskListener));
+        mOnDestroyFrozenTaskRunnable = () -> ActivityManagerWrapper.getInstance()
+                .unregisterTaskStackListener(mFrozenTaskListener);
+        runOnDestroy(mOnDestroyFrozenTaskRunnable);
+    }
+
+    private void destroyOrientationSwipeHandlerCallback() {
+        ActivityManagerWrapper.getInstance().unregisterTaskStackListener(mFrozenTaskListener);
+        mOnDestroyActions.remove(mOnDestroyFrozenTaskRunnable);
     }
 
     private void runOnDestroy(Runnable action) {
@@ -216,11 +224,17 @@
         } else {
             mExclusionListener.unregister();
         }
+
+        mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
+
+        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
+        if (!mMode.hasGestures && newMode.hasGestures) {
+            setupOrientationSwipeHandler();
+        } else if (mMode.hasGestures && !newMode.hasGestures){
+            destroyOrientationSwipeHandlerCallback();
+        }
+
         mMode = newMode;
-
-        mNavBarPosition = new NavBarPosition(mMode, mDefaultDisplay.getInfo());
-
-        mOrientationTouchTransformer.setNavigationMode(mMode);
     }
 
     @Override
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/layout/work_apps_paused.xml b/res/layout/work_apps_paused.xml
index 1c18076..cf1e835 100644
--- a/res/layout/work_apps_paused.xml
+++ b/res/layout/work_apps_paused.xml
@@ -15,19 +15,10 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="?attr/allAppsScrimColor"
-    android:padding="8dp"
+    android:padding="48dp"
     android:orientation="vertical"
     android:gravity="center">
 
-    <ImageView
-        android:id="@+id/icon"
-        android:contentDescription="@string/work_apps_paused_title"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:tint="?attr/workProfileOverlayTextColor"
-        android:src="@drawable/ic_corp_off" />
-
     <TextView
         style="@style/TextHeadline"
         android:textColor="?attr/workProfileOverlayTextColor"
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
index 04094c4..5506b94 100644
--- a/res/layout/work_profile_edu.xml
+++ b/res/layout/work_profile_edu.xml
@@ -41,6 +41,7 @@
             android:layout_height="wrap_content"
             android:layout_marginTop="48dp"
             android:layout_marginBottom="48dp"
+            android:gravity="center"
             android:text="@string/work_profile_edu_personal_apps"
             android:textAlignment="center"
             android:textColor="@android:color/white"
diff --git a/res/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index 2cffedd..264e273 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -19,19 +19,20 @@
     android:layout_height="wrap_content"
     android:id="@+id/work_toggle_container"
     android:focusable="true"
-    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding_vertical"
     android:orientation="horizontal"
-    android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding_horizontal"
     android:background="?attr/allAppsScrimColor"
-    android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding_horizontal"
-    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding_vertical">
+    android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding"
+    android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding">
 
     <TextView
+        style="@style/PrimaryMediumText"
         android:id="@+id/work_mode_label"
         android:layout_width="0dp"
         android:layout_weight="1"
         android:drawableStart="@drawable/ic_corp"
-        android:drawablePadding="3dp"
+        android:drawablePadding="16dp"
         android:drawableTint="?attr/workProfileOverlayTextColor"
         android:textColor="?attr/workProfileOverlayTextColor"
         android:layout_height="wrap_content"
@@ -40,6 +41,7 @@
         android:lines="1"
         android:minHeight="24dp"
         android:paddingEnd="12dp"
+        android:text="@string/work_profile_toggle_label"
         android:textSize="16sp"/>
     <com.android.launcher3.allapps.WorkModeSwitch
         android:id="@+id/work_mode_toggle"
diff --git a/res/values/config.xml b/res/values/config.xml
index df0f233..1675a98 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,8 +171,12 @@
 
         <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>
     </array>
+
+    <string-array name="live_wallpapers_remove_sysui_scrims">
+    </string-array>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index edae7f4..871651d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,8 +85,7 @@
     <dimen name="all_apps_tabs_side_padding">12dp</dimen>
     <dimen name="all_apps_divider_height">1dp</dimen>
 
-    <dimen name="all_apps_work_profile_tab_footer_padding_vertical">20dp</dimen>
-    <dimen name="all_apps_work_profile_tab_footer_padding_horizontal">24dp</dimen>
+    <dimen name="all_apps_work_profile_tab_footer_padding">20dp</dimen>
 
 <!-- Search bar in All Apps -->
     <dimen name="all_apps_header_max_elevation">3dp</dimen>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9d0fb56..b1077be 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>
 
@@ -328,22 +328,21 @@
     <!-- This string is in the work profile tab when a user has All Apps open on their phone. This is a label for a toggle to turn the work profile on and off. "Work profile" means a separate profile on a user's phone that's specifically for their work apps and managed by their company. "Work" is used as an adjective.-->
     <string name="work_profile_toggle_label">Work profile</string>
     <!--- User onboarding title for personal apps -->
-    <string name="work_profile_edu_personal_apps">Personal apps are private &amp; can\'t be seen by IT</string>
+    <string name="work_profile_edu_personal_apps">Personal data is separate &amp; hidden from work apps</string>
     <!--- User onboarding title for work profile apps -->
-    <string name="work_profile_edu_work_apps">Work apps are badged &amp; visible to IT</string>
+    <string name="work_profile_edu_work_apps">Work apps &amp; data are visible to your IT admin</string>
     <!-- Action label to proceed to the next work profile edu section-->
     <string name="work_profile_edu_next">Next</string>
     <!-- Action label to finish work profile edu-->
     <string name="work_profile_edu_accept">Got it</string>
 
-    <!-- This string is in the work profile tab when a user has All Apps open on their phone. It describes the label of a toggle, "Work profile," as being managed by the user's employer.
-    "Organization" is used to represent a variety of businesses, non-profits, and educational institutions).-->
-    <string name="work_mode_on_label">Work apps: On</string>
-    <!-- This string appears under a the label of a toggle in the work profile tab on a user's phone. It describes the status of the toggle, "Work profile," when it's turned off. "Work profile" means a separate profile on a user's phone that's speficially for their work apps and is managed by their company.-->
-    <string name="work_mode_off_label">Work apps: Paused</string>
+    <!--- heading shown when user opens work apps tab while work apps are paused -->
+    <string name="work_apps_paused_title">Work profile is paused</string>
+    <!--- body shown when user opens work apps tab while work apps are paused -->
+    <string name="work_apps_paused_body">Work apps can\’t send you notifications, use your battery, or access your location</string>
 
-    <string name="work_apps_paused_title">Work apps are paused</string>
-    <string name="work_apps_paused_body">You won\'t get any work notifications, and your IT admin can\'t see your location</string>
+    <!-- A tip shown pointing at work toggle -->
+    <string name="work_switch_tip">Pause work apps and notifications</string>
 
     <!-- Failed action error message: e.g. Failed: Pause -->
     <string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index cee268b..bc6ab45 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -225,6 +225,7 @@
     <style name="DropTargetButton" parent="DropTargetButtonBase" />
 
     <style name="TextHeadline" parent="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
+    <style name="PrimaryMediumText" parent="@android:style/TextAppearance.DeviceDefault.Medium"/>
 
     <style name="TextTitle" parent="@android:style/TextAppearance.DeviceDefault" />
 
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..bed8278 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);
 
@@ -181,7 +180,10 @@
         return null;
     }
 
-    protected static <T extends AbstractFloatingView> T getOpenView(
+    /**
+     * Returns a view matching FloatingViewType
+     */
+    public static <T extends AbstractFloatingView> T getOpenView(
             ActivityContext activity, @FloatingViewType int type) {
         BaseDragLayer dragLayer = activity.getDragLayer();
         if (dragLayer == null) return null;
diff --git a/src/com/android/launcher3/BaseRecyclerView.java b/src/com/android/launcher3/BaseRecyclerView.java
index 864fa6e..38e1201 100644
--- a/src/com/android/launcher3/BaseRecyclerView.java
+++ b/src/com/android/launcher3/BaseRecyclerView.java
@@ -22,11 +22,11 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.recyclerview.widget.RecyclerView;
+
 import com.android.launcher3.compat.AccessibilityManagerCompat;
 import com.android.launcher3.views.RecyclerViewFastScroller;
 
-import androidx.recyclerview.widget.RecyclerView;
-
 
 /**
  * A base {@link RecyclerView}, which does the following:
@@ -138,7 +138,7 @@
         if (getCurrentScrollY() == 0) {
             return true;
         }
-        return false;
+        return getAdapter() == null || getAdapter().getItemCount() == 0;
     }
 
     /**
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 2b0da43..34d7067 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -155,7 +155,7 @@
 
     @Override
     public final void onDragEnter(DragObject d) {
-        if (!d.accessibleDrag && !mTextVisible) {
+        if (!mAccessibleDrag && !mTextVisible) {
             // Show tooltip
             hideTooltip();
 
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index e3eb387..f2d07f2 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -53,8 +53,6 @@
 
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
-import com.android.launcher3.accessibility.FolderAccessibilityHelper;
-import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.anim.PropertyListBuilder;
 import com.android.launcher3.config.FeatureFlags;
@@ -79,9 +77,6 @@
 import java.util.Stack;
 
 public class CellLayout extends ViewGroup implements Transposable {
-    public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
-    public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
-
     private static final String TAG = "CellLayout";
     private static final boolean LOGD = false;
 
@@ -182,7 +177,6 @@
     private static final Paint sPaint = new Paint();
 
     // Related to accessible drag and drop
-    private DragAndDropAccessibilityDelegate mTouchHelper;
     private boolean mUseTouchHelper = false;
     private RotationMode mRotationMode = RotationMode.NORMAL;
 
@@ -292,26 +286,20 @@
         addView(mShortcutsAndWidgets);
     }
 
-    public void enableAccessibleDrag(boolean enable, int dragType) {
-        mUseTouchHelper = enable;
-        if (!enable) {
-            ViewCompat.setAccessibilityDelegate(this, null);
-            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
-            setOnClickListener(null);
-        } else {
-            if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
-                    !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
-                mTouchHelper = new WorkspaceAccessibilityHelper(this);
-            } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
-                    !(mTouchHelper instanceof FolderAccessibilityHelper)) {
-                mTouchHelper = new FolderAccessibilityHelper(this);
-            }
-            ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
-            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
-            setOnClickListener(mTouchHelper);
-        }
+
+    /**
+     * Sets or clears a delegate used for accessible drag and drop
+     */
+    public void setDragAndDropAccessibilityDelegate(DragAndDropAccessibilityDelegate delegate) {
+        setOnClickListener(delegate);
+        setOnHoverListener(delegate);
+        ViewCompat.setAccessibilityDelegate(this, delegate);
+
+        mUseTouchHelper = delegate != null;
+        int accessibilityFlag = mUseTouchHelper
+                ? IMPORTANT_FOR_ACCESSIBILITY_YES : IMPORTANT_FOR_ACCESSIBILITY_NO;
+        setImportantForAccessibility(accessibilityFlag);
+        getShortcutsAndWidgets().setImportantForAccessibility(accessibilityFlag);
 
         // Invalidate the accessibility hierarchy
         if (getParent() != null) {
@@ -339,15 +327,6 @@
     }
 
     @Override
-    public boolean dispatchHoverEvent(MotionEvent event) {
-        // Always attempt to dispatch hover events to accessibility first.
-        if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) {
-            return true;
-        }
-        return super.dispatchHoverEvent(event);
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (mUseTouchHelper ||
                 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
@@ -2787,7 +2766,6 @@
      * Finds solution to accept hotseat migration to cell layout. commits solution if commitConfig
      */
     public boolean makeSpaceForHotseatMigration(boolean commitConfig) {
-        if (FeatureFlags.HOTSEAT_MIGRATE_NEW_PAGE.get()) return false;
         int[] cellPoint = new int[2];
         int[] directionVector = new int[]{0, -1};
         cellToPoint(0, mCountY, cellPoint);
@@ -2797,12 +2775,23 @@
             if (commitConfig) {
                 copySolutionToTempState(configuration, null);
                 commitTempPlacement();
+                // undo marking cells occupied since there is actually nothing being placed yet.
+                mOccupied.markCells(0, mCountY - 1, mCountX, 1, false);
             }
             return true;
         }
         return false;
     }
 
+    /**
+     * returns a copy of cell layout's grid occupancy
+     */
+    public GridOccupancy cloneGridOccupancy() {
+        GridOccupancy occupancy = new GridOccupancy(mCountX, mCountY);
+        mOccupied.copyTo(occupancy);
+        return occupancy;
+    }
+
     public boolean isRegionVacant(int x, int y, int spanX, int spanY) {
         return mOccupied.isRegionVacant(x, y, spanX, spanY);
     }
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index a32fd12..c03011b 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -59,9 +59,6 @@
         /** Where the drag originated */
         public DragSource dragSource = null;
 
-        /** The object is part of an accessible drag operation */
-        public boolean accessibleDrag;
-
         /** Indicates that the drag operation was cancelled */
         public boolean cancelled = false;
 
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 2ad84b9..7414a88 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -162,9 +162,7 @@
                     "PreviewContext is passed into this IDP constructor");
         }
 
-        String gridName = Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
-                ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
-                : null;
+        String gridName = getCurrentGridName(context);
         initGrid(context, gridName);
         mConfigMonitor = new ConfigMonitor(context,
                 APPLY_CONFIG_AT_RUNTIME.get() ? this::onConfigChanged : this::killProcess);
@@ -188,6 +186,12 @@
         initGrid(context, null, new Info(display));
     }
 
+    public static String getCurrentGridName(Context context) {
+        return Utilities.getPrefs(context).getBoolean(GRID_OPTIONS_PREFERENCE_KEY, false)
+                ? Utilities.getPrefs(context).getString(KEY_IDP_GRID_NAME, null)
+                : null;
+    }
+
     /**
      * Retrieve system defined or RRO overriden icon shape.
      */
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index bc0e75f..a83a694 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -124,7 +124,7 @@
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.touch.AllAppsSwipeController;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
+import com.android.launcher3.uioverrides.DepthController;
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
@@ -326,8 +326,20 @@
     private boolean mDeferOverlayCallbacks;
     private final Runnable mDeferredOverlayCallbacks = this::checkIfOverlayStillDeferred;
 
-    private BackgroundBlurController mBackgroundBlurController =
-            new BackgroundBlurController(this);
+    private DepthController mDepthController =
+            new DepthController(this);
+
+    private final ViewTreeObserver.OnDrawListener mOnDrawListener =
+            new ViewTreeObserver.OnDrawListener() {
+                @Override
+                public void onDraw() {
+                    getDepthController().setSurfaceToLauncher(mDragLayer);
+                    mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(
+                            this));
+                }
+            };
+
+    private long mLastTouchUpTime = -1;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -930,6 +942,8 @@
         final int origDragLayerChildCount = mDragLayer.getChildCount();
         super.onStop();
 
+        mDragLayer.getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
+
         if (mDeferOverlayCallbacks) {
             checkIfOverlayStillDeferred();
         } else {
@@ -942,7 +956,7 @@
 
         NotificationListener.removeNotificationsChangedListener();
         getStateManager().moveToRestState();
-        getBackgroundBlurController().setSurfaceToLauncher(null);
+        getDepthController().setSurfaceToLauncher(null);
 
         // Workaround for b/78520668, explicitly trim memory once UI is hidden
         onTrimMemory(TRIM_MEMORY_UI_HIDDEN);
@@ -970,13 +984,7 @@
         if (!mDeferOverlayCallbacks) {
             mOverlayManager.onActivityStarted(this);
         }
-        mDragLayer.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
-            @Override
-            public void onDraw() {
-                getBackgroundBlurController().setSurfaceToLauncher(mDragLayer);
-                mDragLayer.post(() -> mDragLayer.getViewTreeObserver().removeOnDrawListener(this));
-            }
-        });
+        mDragLayer.getViewTreeObserver().addOnDrawListener(mOnDrawListener);
 
         mAppWidgetHost.setListenIfResumed(true);
         TraceHelper.INSTANCE.endSection(traceToken);
@@ -1109,7 +1117,7 @@
 
         super.onPause();
         mDragController.cancelDrag();
-        mDragController.resetLastGestureUpTime();
+        mLastTouchUpTime = -1;
         mDropTargetBar.animateToVisibility(false);
 
         if (!mDeferOverlayCallbacks) {
@@ -1832,6 +1840,9 @@
 
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_UP) {
+            mLastTouchUpTime = System.currentTimeMillis();
+        }
         TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
         return super.dispatchTouchEvent(ev);
     }
@@ -2459,8 +2470,12 @@
     }
 
     private boolean canRunNewAppsAnimation() {
-        long diff = System.currentTimeMillis() - mDragController.getLastGestureUpTime();
-        return diff > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+        if (mDragController.isDragging()) {
+            return false;
+        } else {
+            return (System.currentTimeMillis() - mLastTouchUpTime)
+                    > (NEW_APPS_ANIMATION_INACTIVE_TIMEOUT_SECONDS * 1000);
+        }
     }
 
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
@@ -2702,7 +2717,7 @@
 
     protected StateHandler[] createStateHandlers() {
         return new StateHandler[] { getAllAppsController(), getWorkspace(),
-                getBackgroundBlurController() };
+                getDepthController() };
     }
 
     public TouchController[] createTouchControllers() {
@@ -2739,8 +2754,8 @@
         return Stream.of(APP_INFO, WIDGETS, INSTALL);
     }
 
-    public BackgroundBlurController getBackgroundBlurController() {
-        return mBackgroundBlurController;
+    public DepthController getDepthController() {
+        return mDepthController;
     }
 
     public static Launcher getLauncher(Context context) {
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..6ee82cd 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;
@@ -271,11 +271,13 @@
     }
 
     /**
-     * The amount of blur to apply to the background of either the app or Launcher surface in this
-     * state.
+     * The amount of blur and wallpaper zoom to apply to the background of either the app
+     * or Launcher surface in this state. Should be a number between 0 and 1, inclusive.
+     *
+     * 0 means completely zoomed in, without blurs. 1 is zoomed out, with blurs.
      */
-    public int getBackgroundBlurRadius(Context context) {
-        return 0;
+    public float getDepth(Context context) {
+        return 0f;
     }
 
     public String getDescription(Launcher launcher) {
@@ -325,13 +327,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 +365,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 4267f2b..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,26 +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,
-    // ATOMIC_OVERVIEW_SCALE_COMPONENT *or* ATOMIC_OVERVIEW_PEEK_COMPONENT.
-    @IntDef(flag = true, value = {
-            NON_ATOMIC_COMPONENT,
-            ATOMIC_OVERVIEW_SCALE_COMPONENT,
-            ATOMIC_OVERVIEW_PEEK_COMPONENT,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface AnimationComponents {}
-    public static final int NON_ATOMIC_COMPONENT = 1 << 0;
-    public static final int ATOMIC_OVERVIEW_SCALE_COMPONENT = 1 << 1;
-    public static final int ATOMIC_OVERVIEW_PEEK_COMPONENT = 1 << 2;
-
-    public static final int ANIM_ALL = NON_ATOMIC_COMPONENT | ATOMIC_OVERVIEW_SCALE_COMPONENT
-            | ATOMIC_OVERVIEW_PEEK_COMPONENT;
-
-    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<>();
@@ -138,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() {
@@ -169,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);
     }
 
     /**
@@ -216,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);
             }
@@ -235,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;
@@ -274,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);
@@ -292,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));
     }
 
@@ -306,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, @AnimationComponents int atomicComponent, long duration) {
-        prepareForAtomicAnimation(fromState, toState, builder);
-        AnimationConfig config = new AnimationConfig();
-        config.animComponents = atomicComponent;
-        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();
     }
 
     /**
@@ -355,37 +311,32 @@
      */
     public AnimatorPlaybackController createAnimationToNewWorkspace(
             LauncherState state, long duration) {
-        return createAnimationToNewWorkspace(state, duration, LauncherStateManager.ANIM_ALL);
+        return createAnimationToNewWorkspace(state, duration, ANIM_ALL_COMPONENTS);
     }
 
     public AnimatorPlaybackController createAnimationToNewWorkspace(
-            LauncherState state, long duration, @AnimationComponents int animComponents) {
-        return createAnimationToNewWorkspace(state, new AnimatorSetBuilder(), duration, null,
-                animComponents);
+            LauncherState state, long duration, @AnimationFlags int 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,
-            @AnimationComponents int animComponents) {
+            StateAnimationConfig config) {
         mConfig.reset();
-        mConfig.userControlled = true;
-        mConfig.animComponents = 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) {
@@ -395,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) {
@@ -450,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;
         }
@@ -498,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();
@@ -555,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;
     }
@@ -572,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;
-        public @AnimationComponents int animComponents = ANIM_ALL;
-        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;
-            animComponents = ANIM_ALL;
-            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
@@ -627,28 +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);
         }
 
-        public boolean playAtomicOverviewScaleComponent() {
-            return (animComponents & ATOMIC_OVERVIEW_SCALE_COMPONENT) != 0;
-        }
+        @Override
+        public void onAnimationStart(Animator animator) { }
 
-        public boolean playAtomicOverviewPeekComponent() {
-            return (animComponents & ATOMIC_OVERVIEW_PEEK_COMPONENT) != 0;
-        }
+        @Override
+        public void onAnimationCancel(Animator animator) { }
 
-        public boolean playNonAtomicComponent() {
-            return (animComponents & NON_ATOMIC_COMPONENT) != 0;
-        }
+        @Override
+        public void onAnimationRepeat(Animator animator) { }
     }
 
     public interface StateHandler {
@@ -661,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/PagedView.java b/src/com/android/launcher3/PagedView.java
index a6180a6..e38631d 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -42,21 +42,6 @@
 import android.view.animation.Interpolator;
 import android.widget.ScrollView;
 
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.PagedViewOrientedState;
-import com.android.launcher3.pageindicators.PageIndicator;
-import com.android.launcher3.states.RotationHelper;
-import com.android.launcher3.touch.PortraitPagedViewHandler;
-import com.android.launcher3.touch.OverScroll;
-import com.android.launcher3.touch.PagedOrientationHandler;
-import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
-import com.android.launcher3.util.OverScroller;
-import com.android.launcher3.util.Thunk;
-
-import java.util.ArrayList;
-
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isAccessibilityEnabled;
 import static com.android.launcher3.compat.AccessibilityManagerCompat.isObservedEventType;
 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
@@ -65,6 +50,20 @@
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_BY;
 import static com.android.launcher3.touch.PagedOrientationHandler.VIEW_SCROLL_TO;
 
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.compat.AccessibilityManagerCompat;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.PagedViewOrientedState;
+import com.android.launcher3.pageindicators.PageIndicator;
+import com.android.launcher3.touch.OverScroll;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.touch.PagedOrientationHandler.ChildBounds;
+import com.android.launcher3.touch.PortraitPagedViewHandler;
+import com.android.launcher3.util.OverScroller;
+import com.android.launcher3.util.Thunk;
+
+import java.util.ArrayList;
+
 /**
  * An abstraction of the original Workspace which supports browsing through a
  * sequential list of "pages"
@@ -73,8 +72,6 @@
     private static final String TAG = "PagedView";
     private static final boolean DEBUG = false;
 
-    public static boolean sFlagForcedRotation = false;
-
     public static final int INVALID_PAGE = -1;
     protected static final ComputePageScrollsLogic SIMPLE_SCROLL_LOGIC = (v) -> v.getVisibility() != GONE;
 
@@ -200,8 +197,6 @@
         if (Utilities.ATLEAST_OREO) {
             setDefaultFocusHighlightEnabled(false);
         }
-
-        sFlagForcedRotation = Utilities.isForcedRotation(context);
     }
 
     protected void setDefaultInterpolator(Interpolator interpolator) {
@@ -1515,7 +1510,7 @@
         // interpolator at zero, ie. 5. We use 4 to make it a little slower.
         duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
 
-        if (QUICKSTEP_SPRINGS.get()) {
+        if (QUICKSTEP_SPRINGS.get() && mCurrentPage != whichPage) {
             return snapToPage(whichPage, delta, duration, false, null,
                     velocity * Math.signum(delta), true);
         } else {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 9780630..122b393 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -17,7 +17,6 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.ItemInfoWithIcon.FLAG_ICON_BADGED;
-import static com.android.launcher3.states.RotationHelper.FIXED_ROTATION_TRANSFORM_SETTING_NAME;
 
 import android.animation.ValueAnimator;
 import android.annotation.TargetApi;
@@ -61,6 +60,7 @@
 import android.view.ViewConfiguration;
 import android.view.animation.Interpolator;
 
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
 import com.android.launcher3.graphics.RotationMode;
 import com.android.launcher3.graphics.TintedDrawableSpan;
@@ -128,11 +128,6 @@
                         Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
     }
 
-    public static boolean isForcedRotation(Context context) {
-        return Settings.Global.getInt(context.getContentResolver(),
-            FIXED_ROTATION_TRANSFORM_SETTING_NAME, 0) != 0;
-    }
-
     // An intent extra to indicate the horizontal scroll of the wallpaper.
     public static final String EXTRA_WALLPAPER_OFFSET = "com.android.launcher3.WALLPAPER_OFFSET";
     public static final String EXTRA_WALLPAPER_FLAVOR = "com.android.launcher3.WALLPAPER_FLAVOR";
@@ -481,6 +476,15 @@
                 LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
     }
 
+    /**
+     * @return {@link SharedPreferences} that backs {@link FeatureFlags}
+     */
+    public static SharedPreferences getFeatureFlagsPrefs(Context context) {
+        // Use application context for shared preferences, so that we use a single cached instance
+        return context.getApplicationContext().getSharedPreferences(
+            FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE);
+    }
+
     public static boolean areAnimationsEnabled(Context context) {
         return ATLEAST_OREO
                 ? ValueAnimator.areAnimatorsEnabled()
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index fc1a074..46493b7 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;
@@ -192,10 +192,7 @@
     final WallpaperOffsetInterpolator mWallpaperOffset;
     private boolean mUnlockWallpaperFromDefaultPageOnLayout;
 
-    // Variables relating to the creation of user folders by hovering shortcuts over shortcuts
-    private static final int FOLDER_CREATION_TIMEOUT = 0;
     public static final int REORDER_TIMEOUT = 650;
-    private final Alarm mFolderCreationAlarm = new Alarm();
     private final Alarm mReorderAlarm = new Alarm();
     private PreviewBackground mFolderCreateBg;
     private FolderIcon mDragOverFolderIcon = null;
@@ -567,11 +564,6 @@
         addView(newScreen, insertIndex);
         mStateTransitionAnimation.applyChildState(
                 mLauncher.getStateManager().getState(), newScreen, insertIndex);
-
-        if (mLauncher.getAccessibilityDelegate().isInAccessibleDrag()) {
-            newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
-        }
-
         return newScreen;
     }
 
@@ -818,11 +810,6 @@
                 if (indexOfChild(cl) < currentPage) {
                     pageShift++;
                 }
-
-                if (isInAccessibleDrag) {
-                    cl.enableAccessibleDrag(false, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
-                }
-
                 removeView(cl);
             } else {
                 // if this is the last screen, convert it to the empty screen
@@ -1383,10 +1370,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 +1386,7 @@
         stepAnimator.addUpdateListener(listener);
         stepAnimator.setDuration(config.duration);
         stepAnimator.addListener(listener);
-        builder.play(stepAnimator);
+        animation.add(stepAnimator);
     }
 
     public WorkspaceStateTransitionAnimation getStateTransitionAnimation() {
@@ -1444,14 +1431,14 @@
         child.setVisibility(INVISIBLE);
 
         if (options.isAccessibleDrag) {
-            mDragController.addDragListener(new AccessibleDragListenerAdapter(
-                    this, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG) {
-                @Override
-                protected void enableAccessibleDrag(boolean enable) {
-                    super.enableAccessibleDrag(enable);
-                    setEnableForLayout(mLauncher.getHotseat(),enable);
-                }
-            });
+            mDragController.addDragListener(
+                    new AccessibleDragListenerAdapter(this, WorkspaceAccessibilityHelper::new) {
+                        @Override
+                        protected void enableAccessibleDrag(boolean enable) {
+                            super.enableAccessibleDrag(enable);
+                            setEnableForLayout(mLauncher.getHotseat(), enable);
+                        }
+                    });
         }
 
         beginDragShared(child, this, options);
@@ -1883,12 +1870,10 @@
                         final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell;
                         AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo();
                         if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE
-                                && !d.accessibleDrag) {
-                            onCompleteRunnable = new Runnable() {
-                                public void run() {
-                                    if (!isPageInTransition()) {
-                                        AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
-                                    }
+                                && !options.isAccessibleDrag) {
+                            onCompleteRunnable = () -> {
+                                if (!isPageInTransition()) {
+                                    AppWidgetResizeFrame.showForWidget(hostView, cellLayout);
                                 }
                             };
                         }
@@ -2088,8 +2073,6 @@
         if (mFolderCreateBg != null) {
             mFolderCreateBg.animateToRest();
         }
-        mFolderCreationAlarm.setOnAlarmListener(null);
-        mFolderCreationAlarm.cancelAlarm();
     }
 
     private void cleanupAddToFolder() {
@@ -2196,7 +2179,7 @@
             float targetCellDistance = mDragTargetLayout.getDistanceFromCell(
                     mDragViewVisualCenter[0], mDragViewVisualCenter[1], mTargetCell);
 
-            manageFolderFeedback(mDragTargetLayout, mTargetCell, targetCellDistance, d);
+            manageFolderFeedback(targetCellDistance, d);
 
             boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int)
                     mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX,
@@ -2294,8 +2277,7 @@
         return null;
     }
 
-    private void manageFolderFeedback(CellLayout targetLayout,
-            int[] targetCell, float distance, DragObject dragObject) {
+    private void manageFolderFeedback(float distance, DragObject dragObject) {
         if (distance > mMaxDistanceForFolderCreation) {
             if (mDragMode != DRAG_MODE_NONE) {
                 setDragMode(DRAG_MODE_NONE);
@@ -2306,18 +2288,18 @@
         final View dragOverView = mDragTargetLayout.getChildAt(mTargetCell[0], mTargetCell[1]);
         ItemInfo info = dragObject.dragInfo;
         boolean userFolderPending = willCreateUserFolder(info, dragOverView, false);
-        if (mDragMode == DRAG_MODE_NONE && userFolderPending &&
-                !mFolderCreationAlarm.alarmPending()) {
+        if (mDragMode == DRAG_MODE_NONE && userFolderPending) {
 
-            FolderCreationAlarmListener listener = new
-                    FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]);
+            mFolderCreateBg = new PreviewBackground();
+            mFolderCreateBg.setup(mLauncher, mLauncher, null,
+                    dragOverView.getMeasuredWidth(), dragOverView.getPaddingTop());
 
-            if (!dragObject.accessibleDrag) {
-                mFolderCreationAlarm.setOnAlarmListener(listener);
-                mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT);
-            } else {
-                listener.onAlarm(mFolderCreationAlarm);
-            }
+            // The full preview background should appear behind the icon
+            mFolderCreateBg.isClipping = false;
+
+            mFolderCreateBg.animateToAccept(mDragTargetLayout, mTargetCell[0], mTargetCell[1]);
+            mDragTargetLayout.clearDragOutlines();
+            setDragMode(DRAG_MODE_CREATE_FOLDER);
 
             if (dragObject.stateAnnouncer != null) {
                 dragObject.stateAnnouncer.announce(WorkspaceAccessibilityHelper
@@ -2330,8 +2312,8 @@
         if (willAddToFolder && mDragMode == DRAG_MODE_NONE) {
             mDragOverFolderIcon = ((FolderIcon) dragOverView);
             mDragOverFolderIcon.onDragEnter(info);
-            if (targetLayout != null) {
-                targetLayout.clearDragOutlines();
+            if (mDragTargetLayout != null) {
+                mDragTargetLayout.clearDragOutlines();
             }
             setDragMode(DRAG_MODE_ADD_TO_FOLDER);
 
@@ -2350,33 +2332,6 @@
         }
     }
 
-    class FolderCreationAlarmListener implements OnAlarmListener {
-        final CellLayout layout;
-        final int cellX;
-        final int cellY;
-
-        final PreviewBackground bg = new PreviewBackground();
-
-        public FolderCreationAlarmListener(CellLayout layout, int cellX, int cellY) {
-            this.layout = layout;
-            this.cellX = cellX;
-            this.cellY = cellY;
-
-            BubbleTextView cell = (BubbleTextView) layout.getChildAt(cellX, cellY);
-            bg.setup(mLauncher, mLauncher, null, cell.getMeasuredWidth(), cell.getPaddingTop());
-
-            // The full preview background should appear behind the icon
-            bg.isClipping = false;
-        }
-
-        public void onAlarm(Alarm alarm) {
-            mFolderCreateBg = bg;
-            mFolderCreateBg.animateToAccept(layout, cellX, cellY);
-            layout.clearDragOutlines();
-            setDragMode(DRAG_MODE_CREATE_FOLDER);
-        }
-    }
-
     class ReorderAlarmListener implements OnAlarmListener {
         final float[] dragViewCenter;
         final int minSpanX, minSpanY, spanX, spanY;
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 388d074..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);
@@ -120,20 +122,20 @@
                     hotseatIconsAlpha, fadeInterpolator);
         }
 
-        if (!config.playNonAtomicComponent()) {
+        if (config.onlyPlayAtomicComponent()) {
             // Only the alpha and scale, handled above, are included in the atomic animation.
             return;
         }
 
         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,21 +168,22 @@
 
     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));
 
-        if (config.playNonAtomicComponent()) {
+        if (!config.onlyPlayAtomicComponent()) {
+            // Don't update the scrim during the atomic animation.
             propertySetter.setInt(cl.getScrimBackground(),
                     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/accessibility/AccessibleDragListenerAdapter.java b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
index f8df5d7..0d7df2b 100644
--- a/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
+++ b/src/com/android/launcher3/accessibility/AccessibleDragListenerAdapter.java
@@ -16,7 +16,9 @@
 
 package com.android.launcher3.accessibility;
 
+import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewGroup.OnHierarchyChangeListener;
 
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.DropTarget.DragObject;
@@ -24,36 +26,55 @@
 import com.android.launcher3.dragndrop.DragController.DragListener;
 import com.android.launcher3.dragndrop.DragOptions;
 
+import java.util.function.Function;
+
 /**
  * Utility listener to enable/disable accessibility drag flags for a ViewGroup
  * containing CellLayouts
  */
-public class AccessibleDragListenerAdapter implements DragListener {
+public class AccessibleDragListenerAdapter implements DragListener, OnHierarchyChangeListener {
 
     private final ViewGroup mViewGroup;
-    private final int mDragType;
+    private final Function<CellLayout, DragAndDropAccessibilityDelegate> mDelegateFactory;
 
     /**
-     * @param parent
-     * @param dragType either {@link CellLayout#WORKSPACE_ACCESSIBILITY_DRAG} or
-     *                 {@link CellLayout#FOLDER_ACCESSIBILITY_DRAG}
+     * @param parent the viewgroup containing all the children
+     * @param delegateFactory function to create no delegates
      */
-    public AccessibleDragListenerAdapter(ViewGroup parent, int dragType) {
+    public AccessibleDragListenerAdapter(ViewGroup parent,
+            Function<CellLayout, DragAndDropAccessibilityDelegate> delegateFactory) {
         mViewGroup = parent;
-        mDragType = dragType;
+        mDelegateFactory = delegateFactory;
     }
 
     @Override
     public void onDragStart(DragObject dragObject, DragOptions options) {
+        mViewGroup.setOnHierarchyChangeListener(this);
         enableAccessibleDrag(true);
     }
 
     @Override
     public void onDragEnd() {
+        mViewGroup.setOnHierarchyChangeListener(null);
         enableAccessibleDrag(false);
         Launcher.getLauncher(mViewGroup.getContext()).getDragController().removeDragListener(this);
     }
 
+
+    @Override
+    public void onChildViewAdded(View parent, View child) {
+        if (parent == mViewGroup) {
+            setEnableForLayout((CellLayout) child, true);
+        }
+    }
+
+    @Override
+    public void onChildViewRemoved(View parent, View child) {
+        if (parent == mViewGroup) {
+            setEnableForLayout((CellLayout) child, false);
+        }
+    }
+
     protected void enableAccessibleDrag(boolean enable) {
         for (int i = 0; i < mViewGroup.getChildCount(); i++) {
             setEnableForLayout((CellLayout) mViewGroup.getChildAt(i), enable);
@@ -61,6 +82,6 @@
     }
 
     protected final void setEnableForLayout(CellLayout layout, boolean enable) {
-        layout.enableAccessibleDrag(enable, mDragType);
+        layout.setDragAndDropAccessibilityDelegate(enable ? mDelegateFactory.apply(layout) : null);
     }
 }
diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
index 117296d..ddb547f 100644
--- a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
@@ -19,24 +19,26 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.View.OnHoverListener;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
+import androidx.customview.widget.ExploreByTouchHelper;
+
 import com.android.launcher3.CellLayout;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 
 import java.util.List;
 
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
-import androidx.customview.widget.ExploreByTouchHelper;
-
 /**
  * Helper class to make drag-and-drop in a {@link CellLayout} accessible.
  */
 public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper
-        implements OnClickListener {
+        implements OnClickListener, OnHoverListener {
     protected static final int INVALID_POSITION = -1;
 
     private static final int[] sTempArray = new int[2];
@@ -123,6 +125,11 @@
         node.setFocusable(true);
     }
 
+    @Override
+    public boolean onHover(View view, MotionEvent motionEvent) {
+        return dispatchHoverEvent(motionEvent);
+    }
+
     protected abstract String getLocationDescriptionForIconDrop(int id);
 
     protected abstract String getConfirmationForIconDrop(int id);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 0b439ec..6f7f8e6 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -7,6 +7,7 @@
 import android.app.AlertDialog;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.DialogInterface;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.os.Handler;
@@ -400,11 +401,11 @@
 
         Rect pos = new Rect();
         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
-        mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
         mLauncher.getDragController().addDragListener(this);
 
         DragOptions options = new DragOptions();
         options.isAccessibleDrag = true;
+        options.simulatedDndStartPoint = new Point(pos.centerX(), pos.centerY());
         ItemLongClickListener.beginDrag(item, mLauncher, info, options);
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index a2957bc..e085ff0 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,9 +15,6 @@
  */
 package com.android.launcher3.allapps;
 
-import static android.view.View.MeasureSpec.EXACTLY;
-import static android.view.View.MeasureSpec.makeMeasureSpec;
-
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
 import android.animation.ValueAnimator;
@@ -395,7 +392,7 @@
         rebindAdapters(showTabs, false /* force */);
     }
 
-    private void rebindAdapters(boolean showTabs, boolean force) {
+    protected void rebindAdapters(boolean showTabs, boolean force) {
         if (showTabs == mUsingTabs && !force) {
             return;
         }
@@ -463,6 +460,7 @@
     public void onTabChanged(int pos) {
         mHeader.setMainActive(pos == 0);
         reset(true /* animate */);
+        mViewPager.getPageIndicator().updateTabTextColor(pos);
         if (mAH[pos].recyclerView != null) {
             mAH[pos].recyclerView.bindFastScrollbar();
 
@@ -608,6 +606,7 @@
         public static final int MAIN = 0;
         public static final int WORK = 1;
 
+        private ItemInfoMatcher mInfoMatcher;
         private final boolean mIsWork;
         public final AllAppsGridAdapter adapter;
         final LinearLayoutManager layoutManager;
@@ -627,6 +626,7 @@
         }
 
         void setup(@NonNull View rv, @Nullable ItemInfoMatcher matcher) {
+            mInfoMatcher = matcher;
             appsList.updateItemFilter(matcher);
             recyclerView = (AllAppsRecyclerView) rv;
             recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
@@ -647,19 +647,14 @@
         void setupOverlay() {
             if (!mIsWork || recyclerView == null) return;
             boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
-            recyclerView.getOverlay().clear();
+            if (mWorkDisabled == workDisabled) return;
             if (workDisabled) {
-                View pausedOverlay = mLauncher.getLayoutInflater().inflate(
-                        R.layout.work_apps_paused, null);
-                recyclerView.post(() -> {
-                    int width = recyclerView.getWidth();
-                    int height = recyclerView.getHeight() -  mWorkFooterContainer.getHeight();
-                    pausedOverlay.measure(makeMeasureSpec(recyclerView.getWidth(), EXACTLY),
-                            makeMeasureSpec(recyclerView.getHeight(), EXACTLY));
-                    pausedOverlay.layout(0, 0, width, height);
-                    applyPadding();
-                });
-                recyclerView.getOverlay().add(pausedOverlay);
+                appsList.updateItemFilter((info, cn) -> false);
+                recyclerView.addAutoSizedOverlay(
+                        mLauncher.getLayoutInflater().inflate(R.layout.work_apps_paused, null));
+            } else if (mInfoMatcher != null) {
+                appsList.updateItemFilter(mInfoMatcher);
+                recyclerView.clearAutoSizedOverlays();
             }
             mWorkDisabled = workDisabled;
         }
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index c228ddf..8fe4633 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -15,7 +15,9 @@
  */
 package com.android.launcher3.allapps;
 
+import static android.view.View.MeasureSpec.EXACTLY;
 import static android.view.View.MeasureSpec.UNSPECIFIED;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
 
 import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
 
@@ -62,6 +64,8 @@
     private AllAppsBackgroundDrawable mEmptySearchBackground;
     private int mEmptySearchBackgroundTopOffset;
 
+    private ArrayList<View> mAutoSizedOverlays = new ArrayList<>();
+
     public AllAppsRecyclerView(Context context) {
         this(context, null);
     }
@@ -145,6 +149,30 @@
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         updateEmptySearchBackgroundBounds();
         updatePoolSize();
+        for (int i = 0; i < mAutoSizedOverlays.size(); i++) {
+            View overlay = mAutoSizedOverlays.get(i);
+            overlay.measure(makeMeasureSpec(w, EXACTLY), makeMeasureSpec(w, EXACTLY));
+            overlay.layout(0, 0, w, h);
+        }
+    }
+
+    /**
+     * Adds an overlay that automatically rescales with the recyclerview.
+     */
+    public void addAutoSizedOverlay(View overlay) {
+        mAutoSizedOverlays.add(overlay);
+        getOverlay().add(overlay);
+        onSizeChanged(getWidth(), getHeight(), getWidth(), getHeight());
+    }
+
+    /**
+     * Clears auto scaling overlay views added by #addAutoSizedOverlay
+     */
+    public void clearAutoSizedOverlays() {
+        for (View v : mAutoSizedOverlays) {
+            getOverlay().remove(v);
+        }
+        mAutoSizedOverlays.clear();
     }
 
     @Override
diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java
index c4b2f68..a6ef10a 100644
--- a/src/com/android/launcher3/allapps/AllAppsStore.java
+++ b/src/com/android/launcher3/allapps/AllAppsStore.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.AppInfo.COMPONENT_KEY_COMPARATOR;
 import static com.android.launcher3.AppInfo.EMPTY_ARRAY;
 
+import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -55,6 +56,8 @@
     private int mDeferUpdatesFlags = 0;
     private boolean mUpdatePending = false;
 
+    private boolean mListenerUpdateInProgress = false;
+
     public AppInfo[] getApps() {
         return mApps;
     }
@@ -99,10 +102,12 @@
             mUpdatePending = true;
             return;
         }
+        mListenerUpdateInProgress = true;
         int count = mUpdateListeners.size();
         for (int i = 0; i < count; i++) {
             mUpdateListeners.get(i).onAppsUpdated();
         }
+        mListenerUpdateInProgress = false;
     }
 
     public void addUpdateListener(OnUpdateListener listener) {
@@ -110,6 +115,9 @@
     }
 
     public void removeUpdateListener(OnUpdateListener listener) {
+        if (mListenerUpdateInProgress) {
+            Log.e("AllAppsStore", "Trying to remove listener during update", new Exception());
+        }
         mUpdateListeners.remove(listener);
     }
 
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 0e60f5b..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);
@@ -162,20 +162,20 @@
             return;
         }
 
-        if (!config.playNonAtomicComponent()) {
+        if (config.onlyPlayAtomicComponent()) {
             // There is no atomic component for the all apps transition, so just return early.
             return;
         }
 
         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/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index 0e39bbe..3e40392 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -71,12 +71,10 @@
         mIsRtl = Utilities.isRtl(getResources());
     }
 
-    private void updateIndicatorPosition(float scrollOffset) {
-        mScrollOffset = scrollOffset;
-        updateIndicatorPosition();
-    }
-
-    private void updateTabTextColor(int pos) {
+    /**
+     * Highlights tab with index pos
+     */
+    public void updateTabTextColor(int pos) {
         mSelectedPosition = pos;
         for (int i = 0; i < getChildCount(); i++) {
             Button tab = (Button) getChildAt(i);
@@ -84,6 +82,11 @@
         }
     }
 
+    private void updateIndicatorPosition(float scrollOffset) {
+        mScrollOffset = scrollOffset;
+        updateIndicatorPosition();
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
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 cd30dea..0000000
--- a/src/com/android/launcher3/anim/AnimatorSetBuilder.java
+++ /dev/null
@@ -1,93 +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;
-import java.util.List;
-
-/**
- * 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
-
-    public static final int FLAG_DONT_ANIMATE_OVERVIEW = 1 << 0;
-
-    protected final ArrayList<Animator> mAnims = new ArrayList<>();
-
-    private final SparseArray<Interpolator> mInterpolators = new SparseArray<>();
-    private List<Runnable> mOnFinishRunnables = new ArrayList<>();
-    private int mFlags = 0;
-
-    public void play(Animator anim) {
-        mAnims.add(anim);
-    }
-
-    public void addOnFinishRunnable(Runnable onFinishRunnable) {
-        mOnFinishRunnables.add(onFinishRunnable);
-    }
-
-    public AnimatorSet build() {
-        AnimatorSet anim = new AnimatorSet();
-        anim.playTogether(mAnims);
-        if (!mOnFinishRunnables.isEmpty()) {
-            anim.addListener(new AnimationSuccessListener() {
-                @Override
-                public void onAnimationSuccess(Animator animation) {
-                    for (Runnable onFinishRunnable : mOnFinishRunnables) {
-                        onFinishRunnable.run();
-                    }
-                    mOnFinishRunnables.clear();
-                }
-            });
-        }
-        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);
-    }
-
-    public void addFlag(int flag) {
-        mFlags |= flag;
-    }
-
-    public boolean hasFlag(int flag) {
-        return (mFlags & flag) != 0;
-    }
-}
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 a6f9e6b..4df3b0a 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -36,6 +36,8 @@
     private static final List<DebugFlag> sDebugFlags = new ArrayList<>();
 
     public static final String FLAGS_PREF_NAME = "featureFlags";
+    public static final String FLAG_ENABLE_FIXED_ROTATION_TRANSFORM =
+            "ENABLE_FIXED_ROTATION_TRANSFORM";
 
     private FeatureFlags() { }
 
@@ -81,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.");
 
@@ -98,11 +103,11 @@
             "Suggests folder names instead of blank text.");
 
     public static final BooleanFlag APP_SEARCH_IMPROVEMENTS = new DeviceFlag(
-            "APP_SEARCH_IMPROVEMENTS", false,
+            "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");
@@ -118,9 +123,8 @@
     public static final BooleanFlag ENABLE_HYBRID_HOTSEAT = new DeviceFlag(
             "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,
-            "Migrates hotseat to a new workspace page instead of same page");
+    public static final BooleanFlag HOTSEAT_MIGRATE_TO_FOLDER = new DeviceFlag(
+            "HOTSEAT_MIGRATE_TO_FOLDER", false, "Should move hotseat items into a folder");
 
     public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
             "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
@@ -129,13 +133,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", true, "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");
@@ -153,6 +162,10 @@
             "ALWAYS_USE_HARDWARE_OPTIMIZATION_FOR_FOLDER_ANIMATIONS", false,
             "Always use hardware optimization for folder animations.");
 
+    public static final BooleanFlag ENABLE_FIXED_ROTATION_TRANSFORM = getDebugFlag(
+            FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true,
+            "Launch/close apps without rotation animation. Fix Launcher to portrait");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
index 75693c6..9b91a1d 100644
--- a/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
+++ b/src/com/android/launcher3/dragndrop/BaseItemDragListener.java
@@ -26,7 +26,6 @@
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.DragEvent;
 import android.view.View;
@@ -63,7 +62,6 @@
 
     protected Launcher mLauncher;
     private DragController mDragController;
-    private long mDragStartTime;
 
     public BaseItemDragListener(Rect previewRect, int previewBitmapWidth, int previewViewWidth) {
         mPreviewRect = previewRect;
@@ -102,7 +100,7 @@
                 return false;
             }
         }
-        return mDragController.onDragEvent(mDragStartTime, event);
+        return mDragController.onDragEvent(event);
     }
 
     protected boolean onDragStart(DragEvent event) {
@@ -118,7 +116,7 @@
 
         Point downPos = new Point((int) event.getX(), (int) event.getY());
         DragOptions options = new DragOptions();
-        options.systemDndStartPoint = downPos;
+        options.simulatedDndStartPoint = downPos;
         options.preDragCondition = preDragCondition;
 
         // We use drag event position as the screenPos for the preview image. Since mPreviewRect
@@ -128,7 +126,6 @@
         // to source window.
         createDragHelper().startDrag(new Rect(mPreviewRect),
                 mPreviewBitmapWidth, mPreviewViewWidth, downPos, this, options);
-        mDragStartTime = SystemClock.uptimeMillis();
         return true;
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index de7bc6d..1539747 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -43,7 +43,6 @@
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
 import com.android.launcher3.util.ItemInfoMatcher;
-import com.android.launcher3.util.Thunk;
 import com.android.launcher3.util.TouchController;
 import com.android.launcher3.util.UiThreadHelper;
 
@@ -61,8 +60,8 @@
      */
     private static final int DEEP_PRESS_DISTANCE_FACTOR = 3;
 
-    @Thunk Launcher mLauncher;
-    private FlingToDeleteHelper mFlingToDeleteHelper;
+    private final Launcher mLauncher;
+    private final FlingToDeleteHelper mFlingToDeleteHelper;
 
     // temporaries to avoid gc thrash
     private Rect mRectTemp = new Rect();
@@ -77,11 +76,12 @@
     /** Options controlling the drag behavior. */
     private DragOptions mOptions;
 
-    /** X coordinate of the down event. */
-    private int mMotionDownX;
+    /** Coordinate for motion down event */
+    private final Point mMotionDown = new Point();
+    /** Coordinate for last touch event **/
+    private final Point mLastTouch = new Point();
 
-    /** Y coordinate of the down event. */
-    private int mMotionDownY;
+    private final Point mTmpPoint = new Point();
 
     private DropTarget.DragObject mDragObject;
 
@@ -96,12 +96,9 @@
 
     private DropTarget mLastDropTarget;
 
-    private final int[] mLastTouch = new int[2];
-    private long mLastTouchUpTime = -1;
     private int mLastTouchClassification;
     private int mDistanceSinceScroll = 0;
 
-    private int mTmpPoint[] = new int[2];
     private Rect mDragLayerRect = new Rect();
 
     private boolean mIsInPreDrag;
@@ -159,13 +156,13 @@
         AbstractFloatingView.closeOpenViews(mLauncher, false, TYPE_DISCOVERY_BOUNCE);
 
         mOptions = options;
-        if (mOptions.systemDndStartPoint != null) {
-            mLastTouch[0] = mMotionDownX = mOptions.systemDndStartPoint.x;
-            mLastTouch[1] = mMotionDownY = mOptions.systemDndStartPoint.y;
+        if (mOptions.simulatedDndStartPoint != null) {
+            mLastTouch.x = mMotionDown.x = mOptions.simulatedDndStartPoint.x;
+            mLastTouch.y = mMotionDown.y = mOptions.simulatedDndStartPoint.y;
         }
 
-        final int registrationX = mMotionDownX - dragLayerX;
-        final int registrationY = mMotionDownY - dragLayerY;
+        final int registrationX = mMotionDown.x - dragLayerX;
+        final int registrationY = mMotionDown.y - dragLayerY;
 
         final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
         final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
@@ -184,17 +181,13 @@
                 registrationY, initialDragViewScale, dragViewScaleOnDrop, scaleDps);
         dragView.setItemInfo(dragInfo);
         mDragObject.dragComplete = false;
-        if (mOptions.isAccessibleDrag) {
-            // For an accessible drag, we assume the view is being dragged from the center.
-            mDragObject.xOffset = b.getWidth() / 2;
-            mDragObject.yOffset = b.getHeight() / 2;
-            mDragObject.accessibleDrag = true;
-        } else {
-            mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
-            mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
-            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
 
-            mDragDriver = DragDriver.create(mLauncher, this, mDragObject, mOptions);
+        mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
+        mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
+
+        mDragDriver = DragDriver.create(this, mOptions, mFlingToDeleteHelper::recordMotionEvent);
+        if (!mOptions.isAccessibleDrag) {
+            mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
         }
 
         mDragObject.dragSource = source;
@@ -210,7 +203,7 @@
         }
 
         mLauncher.getDragLayer().performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
-        dragView.show(mLastTouch[0], mLastTouch[1]);
+        dragView.show(mLastTouch.x, mLastTouch.y);
         mDistanceSinceScroll = 0;
 
         if (!mIsInPreDrag) {
@@ -219,7 +212,7 @@
             mOptions.preDragCondition.onPreDragStart(mDragObject);
         }
 
-        handleMoveEvent(mLastTouch[0], mLastTouch[1]);
+        handleMoveEvent(mLastTouch.x, mLastTouch.y);
         mLauncher.getUserEventDispatcher().resetActionDurationMillis();
         return dragView;
     }
@@ -336,7 +329,7 @@
                 }
             }
         };
-        mDragObject.dragView.animateTo(mMotionDownX, mMotionDownY, onCompleteRunnable, duration);
+        mDragObject.dragView.animateTo(mMotionDown.x, mMotionDown.y, onCompleteRunnable, duration);
     }
 
     private void callOnDragEnd() {
@@ -365,30 +358,17 @@
     /**
      * Clamps the position to the drag layer bounds.
      */
-    private int[] getClampedDragLayerPos(float x, float y) {
+    private Point getClampedDragLayerPos(float x, float y) {
         mLauncher.getDragLayer().getLocalVisibleRect(mDragLayerRect);
-        mTmpPoint[0] = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
-        mTmpPoint[1] = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
+        mTmpPoint.x = (int) Math.max(mDragLayerRect.left, Math.min(x, mDragLayerRect.right - 1));
+        mTmpPoint.y = (int) Math.max(mDragLayerRect.top, Math.min(y, mDragLayerRect.bottom - 1));
         return mTmpPoint;
     }
 
-    public long getLastGestureUpTime() {
-        if (mDragDriver != null) {
-            return System.currentTimeMillis();
-        } else {
-            return mLastTouchUpTime;
-        }
-    }
-
-    public void resetLastGestureUpTime() {
-        mLastTouchUpTime = -1;
-    }
-
     @Override
     public void onDriverDragMove(float x, float y) {
-        final int[] dragLayerPos = getClampedDragLayerPos(x, y);
-
-        handleMoveEvent(dragLayerPos[0], dragLayerPos[1]);
+        Point dragLayerPos = getClampedDragLayerPos(x, y);
+        handleMoveEvent(dragLayerPos.x, dragLayerPos.y);
     }
 
     @Override
@@ -422,53 +402,38 @@
     /**
      * Call this from a drag source view.
      */
+    @Override
     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
         if (mOptions != null && mOptions.isAccessibleDrag) {
             return false;
         }
 
-        // Update the velocity tracker
-        mFlingToDeleteHelper.recordMotionEvent(ev);
+        Point dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
+        mLastTouch.set(dragLayerPos.x,  dragLayerPos.y);
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            // Remember location of down touch
+            mMotionDown.set(dragLayerPos.x,  dragLayerPos.y);
+        }
 
-        final int action = ev.getAction();
-        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
-        final int dragLayerX = dragLayerPos[0];
-        final int dragLayerY = dragLayerPos[1];
-        mLastTouch[0] = dragLayerX;
-        mLastTouch[1] = dragLayerY;
         if (ATLEAST_Q) {
             mLastTouchClassification = ev.getClassification();
         }
-
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                // Remember location of down touch
-                mMotionDownX = dragLayerX;
-                mMotionDownY = dragLayerY;
-                break;
-            case MotionEvent.ACTION_UP:
-                mLastTouchUpTime = System.currentTimeMillis();
-                break;
-        }
-
         return mDragDriver != null && mDragDriver.onInterceptTouchEvent(ev);
     }
 
     /**
      * Call this from a drag source view.
      */
-    public boolean onDragEvent(long dragStartTime, DragEvent event) {
-        mFlingToDeleteHelper.recordDragEvent(dragStartTime, event);
-        return mDragDriver != null && mDragDriver.onDragEvent(event);
+    @Override
+    public boolean onControllerTouchEvent(MotionEvent ev) {
+        return mDragDriver != null && mDragDriver.onTouchEvent(ev);
     }
 
     /**
-     * Call this from a drag view.
+     * Call this from a drag source view.
      */
-    public void onDragViewAnimationEnd() {
-        if (mDragDriver != null) {
-            mDragDriver.onDragViewAnimationEnd();
-        }
+    public boolean onDragEvent(DragEvent event) {
+        return mDragDriver != null && mDragDriver.onDragEvent(event);
     }
 
     /**
@@ -493,9 +458,8 @@
         checkTouchMove(dropTarget);
 
         // Check if we are hovering over the scroll areas
-        mDistanceSinceScroll += Math.hypot(mLastTouch[0] - x, mLastTouch[1] - y);
-        mLastTouch[0] = x;
-        mLastTouch[1] = y;
+        mDistanceSinceScroll += Math.hypot(mLastTouch.x - x, mLastTouch.y - y);
+        mLastTouch.set(x, y);
 
         int distanceDragged = mDistanceSinceScroll;
         if (ATLEAST_Q && mLastTouchClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS) {
@@ -513,7 +477,7 @@
 
     public void forceTouchMove() {
         int[] dummyCoordinates = mCoordinatesTemp;
-        DropTarget dropTarget = findDropTarget(mLastTouch[0], mLastTouch[1], dummyCoordinates);
+        DropTarget dropTarget = findDropTarget(mLastTouch.x, mLastTouch.y, dummyCoordinates);
         mDragObject.x = dummyCoordinates[0];
         mDragObject.y = dummyCoordinates[1];
         checkTouchMove(dropTarget);
@@ -537,44 +501,6 @@
     }
 
     /**
-     * Call this from a drag source view.
-     */
-    public boolean onControllerTouchEvent(MotionEvent ev) {
-        if (mDragDriver == null || mOptions == null || mOptions.isAccessibleDrag) {
-            return false;
-        }
-
-        // Update the velocity tracker
-        mFlingToDeleteHelper.recordMotionEvent(ev);
-
-        final int action = ev.getAction();
-        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
-        final int dragLayerX = dragLayerPos[0];
-        final int dragLayerY = dragLayerPos[1];
-
-        switch (action) {
-            case MotionEvent.ACTION_DOWN:
-                // Remember where the motion event started
-                mMotionDownX = dragLayerX;
-                mMotionDownY = dragLayerY;
-                break;
-        }
-
-        return mDragDriver.onTouchEvent(ev);
-    }
-
-    /**
-     * Since accessible drag and drop won't cause the same sequence of touch events, we manually
-     * inject the appropriate state which would have been otherwise initiated via touch events.
-     */
-    public void prepareAccessibleDrag(int x, int y) {
-        mMotionDownX = x;
-        mMotionDownY = y;
-        mLastTouch[0] = x;
-        mLastTouch[1] = y;
-    }
-
-    /**
      * As above, since accessible drag and drop won't cause the same sequence of touch events,
      * we manually ensure appropriate drag and drop events get emulated for accessible drag.
      */
diff --git a/src/com/android/launcher3/dragndrop/DragDriver.java b/src/com/android/launcher3/dragndrop/DragDriver.java
index 87461d5..d4ce308 100644
--- a/src/com/android/launcher3/dragndrop/DragDriver.java
+++ b/src/com/android/launcher3/dragndrop/DragDriver.java
@@ -16,19 +16,19 @@
 
 package com.android.launcher3.dragndrop;
 
-import android.content.Context;
-import android.util.Log;
+import android.os.SystemClock;
 import android.view.DragEvent;
 import android.view.MotionEvent;
 
-import com.android.launcher3.DropTarget.DragObject;
-import com.android.launcher3.testing.TestProtocol;
+import java.util.function.Consumer;
 
 /**
  * Base class for driving a drag/drop operation.
  */
 public abstract class DragDriver {
+
     protected final EventListener mEventListener;
+    protected final Consumer<MotionEvent> mSecondaryEventConsumer;
 
     public interface EventListener {
         void onDriverDragMove(float x, float y);
@@ -37,131 +37,175 @@
         void onDriverDragCancel();
     }
 
-    public DragDriver(EventListener eventListener) {
+    public DragDriver(EventListener eventListener, Consumer<MotionEvent> sec) {
         mEventListener = eventListener;
+        mSecondaryEventConsumer = sec;
     }
 
     /**
-     * Handles ending of the DragView animation.
+     * Called to handle system touch event
      */
-    public void onDragViewAnimationEnd() { }
-
     public boolean onTouchEvent(MotionEvent ev) {
-        final int action = ev.getAction();
-
-        switch (action) {
-            case MotionEvent.ACTION_MOVE:
-                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
-                break;
-            case MotionEvent.ACTION_UP:
-                mEventListener.onDriverDragMove(ev.getX(), ev.getY());
-                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                mEventListener.onDriverDragCancel();
-                break;
-        }
-
-        return true;
+        return false;
     }
 
-    public abstract boolean onDragEvent (DragEvent event);
-
-
+    /**
+     * Called to handle system touch intercept event
+     */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        final int action = ev.getAction();
-
-        switch (action) {
-            case MotionEvent.ACTION_UP:
-                mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
-                break;
-            case MotionEvent.ACTION_CANCEL:
-                mEventListener.onDriverDragCancel();
-                break;
-        }
-
-        return true;
+        return false;
     }
 
-    public static DragDriver create(Context context, DragController dragController,
-            DragObject dragObject, DragOptions options) {
-        if (options.systemDndStartPoint != null) {
-            return new SystemDragDriver(dragController, context, dragObject);
+    /**
+     * Called to handle system drag event
+     */
+    public boolean onDragEvent(DragEvent event) {
+        return false;
+    }
+
+    /**
+     * Created a driver for handing the actual events
+     */
+    public static DragDriver create(DragController dragController, DragOptions options,
+            Consumer<MotionEvent> sec) {
+        if (options.simulatedDndStartPoint != null) {
+            if  (options.isAccessibleDrag) {
+                return null;
+            }
+            return new SystemDragDriver(dragController, sec);
         } else {
-            return new InternalDragDriver(dragController);
+            return new InternalDragDriver(dragController, sec);
+        }
+    }
+
+    /**
+     * Class for driving a system (i.e. framework) drag/drop operation.
+     */
+    static class SystemDragDriver extends DragDriver {
+
+        private final long mDragStartTime;
+        float mLastX = 0;
+        float mLastY = 0;
+
+        SystemDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
+            super(dragController, sec);
+            mDragStartTime = SystemClock.uptimeMillis();
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent ev) {
+            return false;
+        }
+
+        /**
+         * It creates a temporary {@link MotionEvent} object for secondary consumer
+         */
+        private void simulateSecondaryMotionEvent(DragEvent event) {
+            final int motionAction;
+            switch (event.getAction()) {
+                case DragEvent.ACTION_DRAG_STARTED:
+                    motionAction = MotionEvent.ACTION_DOWN;
+                    break;
+                case DragEvent.ACTION_DRAG_LOCATION:
+                    motionAction = MotionEvent.ACTION_MOVE;
+                    break;
+                case DragEvent.ACTION_DRAG_ENDED:
+                    motionAction = MotionEvent.ACTION_UP;
+                    break;
+                default:
+                    return;
+            }
+            MotionEvent emulatedEvent = MotionEvent.obtain(mDragStartTime,
+                    SystemClock.uptimeMillis(), motionAction, event.getX(), event.getY(), 0);
+            mSecondaryEventConsumer.accept(emulatedEvent);
+            emulatedEvent.recycle();
+        }
+
+        @Override
+        public boolean onDragEvent(DragEvent event) {
+            simulateSecondaryMotionEvent(event);
+            final int action = event.getAction();
+
+            switch (action) {
+                case DragEvent.ACTION_DRAG_STARTED:
+                    mLastX = event.getX();
+                    mLastY = event.getY();
+                    return true;
+
+                case DragEvent.ACTION_DRAG_ENTERED:
+                    return true;
+
+                case DragEvent.ACTION_DRAG_LOCATION:
+                    mLastX = event.getX();
+                    mLastY = event.getY();
+                    mEventListener.onDriverDragMove(event.getX(), event.getY());
+                    return true;
+
+                case DragEvent.ACTION_DROP:
+                    mLastX = event.getX();
+                    mLastY = event.getY();
+                    mEventListener.onDriverDragMove(event.getX(), event.getY());
+                    mEventListener.onDriverDragEnd(mLastX, mLastY);
+                    return true;
+                case DragEvent.ACTION_DRAG_EXITED:
+                    mEventListener.onDriverDragExitWindow();
+                    return true;
+
+                case DragEvent.ACTION_DRAG_ENDED:
+                    mEventListener.onDriverDragCancel();
+                    return true;
+
+                default:
+                    return false;
+            }
+        }
+    }
+
+    /**
+     * Class for driving an internal (i.e. not using framework) drag/drop operation.
+     */
+    static class InternalDragDriver extends DragDriver {
+        InternalDragDriver(DragController dragController, Consumer<MotionEvent> sec) {
+            super(dragController, sec);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent ev) {
+            mSecondaryEventConsumer.accept(ev);
+            final int action = ev.getAction();
+
+            switch (action) {
+                case MotionEvent.ACTION_MOVE:
+                    mEventListener.onDriverDragMove(ev.getX(), ev.getY());
+                    break;
+                case MotionEvent.ACTION_UP:
+                    mEventListener.onDriverDragMove(ev.getX(), ev.getY());
+                    mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                    mEventListener.onDriverDragCancel();
+                    break;
+            }
+
+            return true;
+        }
+
+
+        public boolean onInterceptTouchEvent(MotionEvent ev) {
+            mSecondaryEventConsumer.accept(ev);
+            final int action = ev.getAction();
+
+            switch (action) {
+                case MotionEvent.ACTION_UP:
+                    mEventListener.onDriverDragEnd(ev.getX(), ev.getY());
+                    break;
+                case MotionEvent.ACTION_CANCEL:
+                    mEventListener.onDriverDragCancel();
+                    break;
+            }
+            return true;
         }
     }
 }
 
-/**
- * Class for driving a system (i.e. framework) drag/drop operation.
- */
-class SystemDragDriver extends DragDriver {
 
-    float mLastX = 0;
-    float mLastY = 0;
-
-    SystemDragDriver(DragController dragController, Context context, DragObject dragObject) {
-        super(dragController);
-    }
-
-    @Override
-    public boolean onTouchEvent(MotionEvent ev) {
-        return false;
-    }
-
-    @Override
-    public boolean onInterceptTouchEvent(MotionEvent ev) {
-        return false;
-    }
-
-    @Override
-    public boolean onDragEvent (DragEvent event) {
-        final int action = event.getAction();
-
-        switch (action) {
-            case DragEvent.ACTION_DRAG_STARTED:
-                mLastX = event.getX();
-                mLastY = event.getY();
-                return true;
-
-            case DragEvent.ACTION_DRAG_ENTERED:
-                return true;
-
-            case DragEvent.ACTION_DRAG_LOCATION:
-                mLastX = event.getX();
-                mLastY = event.getY();
-                mEventListener.onDriverDragMove(event.getX(), event.getY());
-                return true;
-
-            case DragEvent.ACTION_DROP:
-                mLastX = event.getX();
-                mLastY = event.getY();
-                mEventListener.onDriverDragMove(event.getX(), event.getY());
-                mEventListener.onDriverDragEnd(mLastX, mLastY);
-                return true;
-            case DragEvent.ACTION_DRAG_EXITED:
-                mEventListener.onDriverDragExitWindow();
-                return true;
-
-            case DragEvent.ACTION_DRAG_ENDED:
-                mEventListener.onDriverDragCancel();
-                return true;
-
-            default:
-                return false;
-        }
-    }
-}
-
-/**
- * Class for driving an internal (i.e. not using framework) drag/drop operation.
- */
-class InternalDragDriver extends DragDriver {
-    InternalDragDriver(DragController dragController) {
-        super(dragController);
-    }
-
-    @Override
-    public boolean onDragEvent (DragEvent event) { return false; }
-}
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 92f35e2..9d07595 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -119,6 +119,7 @@
         recreateControllers();
     }
 
+    @Override
     public void recreateControllers() {
         mControllers = mActivity.createTouchControllers();
     }
@@ -558,7 +559,7 @@
     @Override
     public void setInsets(Rect insets) {
         super.setInsets(insets);
-        mWorkspaceScrim.onInsetsChanged(insets);
+        mWorkspaceScrim.onInsetsChanged(insets, mAllowSysuiScrims);
         mOverviewScrim.onInsetsChanged(insets);
     }
 
diff --git a/src/com/android/launcher3/dragndrop/DragOptions.java b/src/com/android/launcher3/dragndrop/DragOptions.java
index 2d19f36..959602b 100644
--- a/src/com/android/launcher3/dragndrop/DragOptions.java
+++ b/src/com/android/launcher3/dragndrop/DragOptions.java
@@ -28,8 +28,11 @@
     /** Whether or not an accessible drag operation is in progress. */
     public boolean isAccessibleDrag = false;
 
-    /** Specifies the start location for the system DnD, null when using internal DnD */
-    public Point systemDndStartPoint = null;
+    /**
+     * Specifies the start location for a simulated DnD (like system drag or accessibility drag),
+     * null when using internal DnD
+     */
+    public Point simulatedDndStartPoint = null;
 
     /** Determines when a pre-drag should transition to a drag. By default, this is immediate. */
     public PreDragCondition preDragCondition = null;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 145885a..7c76d34 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -19,8 +19,6 @@
 import static com.android.launcher3.Utilities.getBadge;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.FloatArrayEvaluator;
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -146,15 +144,6 @@
             }
         });
 
-        mAnim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (!mAnimationCancelled) {
-                    mDragController.onDragViewAnimationEnd();
-                }
-            }
-        });
-
         mBitmap = bitmap;
         setDragRegion(new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()));
 
diff --git a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
index 06b5c40..7788f93 100644
--- a/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
+++ b/src/com/android/launcher3/dragndrop/FlingToDeleteHelper.java
@@ -17,8 +17,6 @@
 package com.android.launcher3.dragndrop;
 
 import android.graphics.PointF;
-import android.os.SystemClock;
-import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
@@ -55,31 +53,6 @@
         mVelocityTracker.addMovement(ev);
     }
 
-    /**
-     * Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object
-     * using {@param event} for tracking velocity.
-     */
-    public void recordDragEvent(long dragStartTime, DragEvent event) {
-        final int motionAction;
-        switch (event.getAction()) {
-            case DragEvent.ACTION_DRAG_STARTED:
-                motionAction = MotionEvent.ACTION_DOWN;
-                break;
-            case DragEvent.ACTION_DRAG_LOCATION:
-                motionAction = MotionEvent.ACTION_MOVE;
-                break;
-            case DragEvent.ACTION_DRAG_ENDED:
-                motionAction = MotionEvent.ACTION_UP;
-                break;
-            default:
-                return;
-        }
-        MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(),
-                motionAction, event.getX(), event.getY(), 0);
-        recordMotionEvent(emulatedEvent);
-        emulatedEvent.recycle();
-    }
-
     public void releaseVelocityTracker() {
         if (mVelocityTracker != null) {
             mVelocityTracker.recycle();
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 2be8ff4..14fa1f4 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -85,6 +85,7 @@
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.WorkspaceItemInfo;
 import com.android.launcher3.accessibility.AccessibleDragListenerAdapter;
+import com.android.launcher3.accessibility.FolderAccessibilityHelper;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragController.DragListener;
@@ -271,16 +272,15 @@
             mDragController.addDragListener(this);
             if (options.isAccessibleDrag) {
                 mDragController.addDragListener(new AccessibleDragListenerAdapter(
-                        mContent, CellLayout.FOLDER_ACCESSIBILITY_DRAG) {
-
-                    @Override
-                    protected void enableAccessibleDrag(boolean enable) {
-                        super.enableAccessibleDrag(enable);
-                        mFooter.setImportantForAccessibility(enable
-                                ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-                    }
-                });
+                        mContent, FolderAccessibilityHelper::new) {
+                            @Override
+                            protected void enableAccessibleDrag(boolean enable) {
+                                super.enableAccessibleDrag(enable);
+                                mFooter.setImportantForAccessibility(enable
+                                        ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                        : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+                            }
+                        });
             }
 
             mLauncher.getWorkspace().beginDragShared(v, this, options);
@@ -324,13 +324,11 @@
     public void startEditingFolderName() {
         post(() -> {
             if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
-                if (isEmpty(mFolderName.getText())) {
-                    ofNullable(mInfo)
-                            .map(info -> info.suggestedFolderNames)
-                            .map(folderNames -> (FolderNameInfo[]) folderNames
-                                    .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
-                            .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
-                }
+                ofNullable(mInfo)
+                        .map(info -> info.suggestedFolderNames)
+                        .map(folderNames -> (FolderNameInfo[]) folderNames
+                                .getParcelableArrayExtra(FolderInfo.EXTRA_FOLDER_SUGGESTIONS))
+                        .ifPresent(nameInfos -> showLabelSuggestion(nameInfos, false));
             }
             mFolderName.setHint("");
             mIsEditingName = true;
@@ -485,19 +483,24 @@
                 nameInfos[1].getLabel());
 
         if (shouldOpen) {
-            CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
-            if (!isEmpty(firstLabel)) {
-                mFolderName.setHint("");
-                mFolderName.setText(firstLabel);
+            // update the primary suggestion if the folder name is empty.
+            if (isEmpty(mFolderName.getText())) {
+                CharSequence firstLabel = nameInfos[0] == null ? "" : nameInfos[0].getLabel();
+                if (!isEmpty(firstLabel)) {
+                    mFolderName.setHint("");
+                    mFolderName.setText(firstLabel);
+                }
             }
             if (animate) {
                 animateOpen(mInfo.contents, 0, true);
             }
             mFolderName.showKeyboard();
             mFolderName.displayCompletions(
-                    asList(nameInfos).subList(1, nameInfos.length).stream()
+                    asList(nameInfos).subList(0, nameInfos.length).stream()
                             .filter(Objects::nonNull)
                             .map(s -> s.getLabel().toString())
+                            .filter(s -> !s.isEmpty())
+                            .filter(s -> !s.equalsIgnoreCase(mFolderName.getText().toString()))
                             .collect(Collectors.toList()));
         }
     }
@@ -516,9 +519,6 @@
     }
 
     private void startAnimation(final AnimatorSet a) {
-        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
-            mCurrentAnimator.cancel();
-        }
         final Workspace workspace = mLauncher.getWorkspace();
         final CellLayout currentCellLayout =
                 (CellLayout) workspace.getChildAt(workspace.getCurrentPage());
@@ -635,6 +635,9 @@
         // dropping. One resulting issue is that replaceFolderWithFinalItem() can be called twice.
         mDeleteFolderOnDropCompleted = false;
 
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.cancel();
+        }
         AnimatorSet anim = new FolderAnimationManager(this, true /* isOpening */).getAnimator();
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
@@ -738,6 +741,9 @@
     }
 
     private void animateClosed() {
+        if (mCurrentAnimator != null && mCurrentAnimator.isRunning()) {
+            mCurrentAnimator.cancel();
+        }
         AnimatorSet a = new FolderAnimationManager(this, false /* isOpening */).getAnimator();
         a.addListener(new AnimatorListenerAdapter() {
             @Override
diff --git a/src/com/android/launcher3/folder/FolderAnimationManager.java b/src/com/android/launcher3/folder/FolderAnimationManager.java
index f72e674..b83609e 100644
--- a/src/com/android/launcher3/folder/FolderAnimationManager.java
+++ b/src/com/android/launcher3/folder/FolderAnimationManager.java
@@ -21,7 +21,6 @@
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.graphics.IconShape.getShape;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
-import static com.android.launcher3.uioverrides.BackgroundBlurController.BACKGROUND_BLUR;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -47,7 +46,6 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PropertyResetListener;
 import com.android.launcher3.dragndrop.DragLayer;
-import com.android.launcher3.uioverrides.BackgroundBlurController;
 import com.android.launcher3.util.Themes;
 
 import java.util.List;
@@ -222,14 +220,6 @@
         Animator z = getAnimator(mFolder, View.TRANSLATION_Z, -mFolder.getElevation(), 0);
         play(a, z, mIsOpening ? midDuration : 0, midDuration);
 
-        BackgroundBlurController blurController = mLauncher.getBackgroundBlurController();
-        int stateBackgroundBlur = mLauncher.getStateManager().getState()
-                .getBackgroundBlurRadius(mLauncher);
-        int folderBackgroundBlurAdjustment = blurController.getFolderBackgroundBlurAdjustment();
-        play(a, ObjectAnimator.ofInt(blurController, BACKGROUND_BLUR, mIsOpening
-                ? stateBackgroundBlur + folderBackgroundBlurAdjustment
-                : stateBackgroundBlur));
-
         // Store clip variables
         CellLayout cellLayout = mContent.getCurrentCellLayout();
         boolean folderClipChildren = mFolder.getClipChildren();
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 184dbb9..07161da 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -15,8 +15,10 @@
  */
 package com.android.launcher3.folder;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.os.Process;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -34,12 +36,9 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Function;
-import java.util.function.Predicate;
+import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -101,27 +100,23 @@
         }
         // If all the icons are from work profile,
         // Then, suggest "Work" as the folder name
-        List<WorkspaceItemInfo> distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p -> p.user))
-                .collect(Collectors.toList());
-
-        if (distinctItemInfos.size() == 1
-                && !distinctItemInfos.get(0).user.equals(Process.myUserHandle())) {
-            // Place it as last viable suggestion
+        Set<UserHandle> users = workspaceItemInfos.stream().map(w -> w.user)
+                .collect(Collectors.toSet());
+        if (users.size() == 1 && !users.contains(Process.myUserHandle())) {
             setAsLastSuggestion(nameInfos,
                     context.getResources().getString(R.string.work_folder_name));
         }
 
         // If all the icons are from same package (e.g., main icon, shortcut, shortcut)
         // Then, suggest the package's title as the folder name
-        distinctItemInfos = workspaceItemInfos.stream()
-                .filter(distinctByKey(p -> p.getTargetComponent() != null
-                        ? p.getTargetComponent().getPackageName() : ""))
-                .collect(Collectors.toList());
+        Set<String> packageNames = workspaceItemInfos.stream()
+                .map(WorkspaceItemInfo::getTargetComponent)
+                .filter(Objects::nonNull)
+                .map(ComponentName::getPackageName)
+                .collect(Collectors.toSet());
 
-        if (distinctItemInfos.size() == 1) {
-            Optional<AppInfo> info = getAppInfoByPackageName(
-                    distinctItemInfos.get(0).getTargetComponent().getPackageName());
+        if (packageNames.size() == 1) {
+            Optional<AppInfo> info = getAppInfoByPackageName(packageNames.iterator().next());
             // Place it as first viable suggestion and shift everything else
             info.ifPresent(i -> setAsFirstSuggestion(nameInfos, i.title.toString()));
         }
@@ -135,6 +130,7 @@
             return Optional.empty();
         }
         return mAppInfos.stream()
+                .filter(info -> info.componentName != null)
                 .filter(info -> info.componentName.getPackageName().equals(packageName))
                 .findAny();
     }
@@ -174,12 +170,6 @@
                         label.toString()));
     }
 
-    // This method can be moved to some Utility class location.
-    private static <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor) {
-        Map<Object, Boolean> map = new ConcurrentHashMap<>();
-        return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
-    }
-
     private class FolderNameWorker extends BaseModelUpdateTask {
         @Override
         public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
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..3fb2bf6 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,26 +185,29 @@
         }
     }
 
-    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) {
-        mDrawTopScrim = mTopScrim != null && insets.top > 0;
-        mDrawBottomScrim = mBottomMask != null &&
-                !mLauncher.getDeviceProfile().isVerticalBarLayout();
+    /**
+     * Determines whether to draw the top and/or bottom scrim based on new insets.
+     */
+    public void onInsetsChanged(Rect insets, boolean allowSysuiScrims) {
+        mDrawTopScrim = allowSysuiScrims && mTopScrim != null && insets.top > 0;
+        mDrawBottomScrim = allowSysuiScrims && mBottomMask != null
+                && !mLauncher.getDeviceProfile().isVerticalBarLayout();
     }
 
     @Override
     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 +217,7 @@
     @Override
     public void onViewDetachedFromWindow(View view) {
         super.onViewDetachedFromWindow(view);
-        if (mTopScrim != null) {
+        if (!KEYGUARD_ANIMATION.get() && mTopScrim != null) {
             mRoot.getContext().unregisterReceiver(mReceiver);
         }
     }
@@ -229,14 +239,6 @@
         }
     }
 
-    public void hideSysUiScrim(boolean hideSysUiScrim) {
-        mHideSysUiScrim = hideSysUiScrim || (mTopScrim == null);
-        if (!hideSysUiScrim) {
-            mAnimateScrimOnNextDraw = true;
-        }
-        invalidate();
-    }
-
     private void setSysUiProgress(float progress) {
         if (progress != mSysUiProgress) {
             mSysUiProgress = progress;
diff --git a/src/com/android/launcher3/logging/UserEventDispatcher.java b/src/com/android/launcher3/logging/UserEventDispatcher.java
index 513bf62..89077ee 100644
--- a/src/com/android/launcher3/logging/UserEventDispatcher.java
+++ b/src/com/android/launcher3/logging/UserEventDispatcher.java
@@ -25,6 +25,9 @@
 import static com.android.launcher3.logging.LoggerUtils.newLauncherEvent;
 import static com.android.launcher3.logging.LoggerUtils.newTarget;
 import static com.android.launcher3.logging.LoggerUtils.newTouchAction;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
+import static com.android.launcher3.userevent.nano.LauncherLogProto.TipType;
 
 import static java.util.Optional.ofNullable;
 
@@ -48,7 +51,7 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
-import com.android.launcher3.userevent.nano.LauncherLogProto;
+import com.android.launcher3.userevent.LauncherLogProto;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
 import com.android.launcher3.userevent.nano.LauncherLogProto.LauncherEvent;
 import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -57,8 +60,11 @@
 import com.android.launcher3.util.LogConfig;
 import com.android.launcher3.util.ResourceBasedOverride;
 
+import com.google.protobuf.InvalidProtocolBufferException;
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
+import com.google.protobuf.nano.MessageNano;
+
 import java.util.ArrayList;
-import java.util.Locale;
 import java.util.UUID;
 
 /**
@@ -137,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(
@@ -162,7 +168,7 @@
             // Direction DOWN means the task was launched, UP means it was dismissed.
             event.action.dir = direction;
         }
-        event.srcTarget[0].itemType = LauncherLogProto.ItemType.TASK;
+        event.srcTarget[0].itemType = ItemType.TASK;
         event.srcTarget[0].pageIndex = taskIndex;
         fillComponentInfo(event.srcTarget[0], componentKey.componentName);
         dispatchUserEvent(event, null);
@@ -289,7 +295,7 @@
     public void logActionBounceTip(int containerType) {
         LauncherEvent event = newLauncherEvent(newAction(Action.Type.TIP),
                 newContainerTarget(containerType));
-        event.srcTarget[0].tipType = LauncherLogProto.TipType.BOUNCE;
+        event.srcTarget[0].tipType = TipType.BOUNCE;
         dispatchUserEvent(event, null);
     }
 
@@ -316,7 +322,7 @@
             int srcChildTargetType, int srcParentContainerType, int dstContainerType,
             int pageIndex) {
         LauncherEvent event;
-        if (srcChildTargetType == LauncherLogProto.ItemType.TASK) {
+        if (srcChildTargetType == ItemType.TASK) {
             event = newLauncherEvent(newTouchAction(action),
                     newItemTarget(srcChildTargetType),
                     newContainerTarget(srcParentContainerType));
@@ -374,10 +380,11 @@
                 .setElapsedContainerMillis(SystemClock.uptimeMillis() - mElapsedContainerMillis)
                 .setElapsedSessionMillis(
                         SystemClock.uptimeMillis() - mElapsedSessionMillis).build();
-        if (!IS_VERBOSE) {
-            return;
+        try {
+            dispatchUserEvent(LauncherEvent.parseFrom(launcherEvent.toByteArray()), null);
+        } catch (InvalidProtocolBufferNanoException e) {
+            throw new RuntimeException("Cannot convert LauncherEvent from Lite to Nano version.");
         }
-        Log.d(TAG, launcherEvent.toString());
     }
 
     public void logDeepShortcutsOpen(View icon) {
@@ -421,8 +428,8 @@
         action.command = Action.Command.BACK;
         action.dir = isButton ? Action.Direction.NONE :
                 gestureSwipeLeft ? Action.Direction.LEFT : Action.Direction.RIGHT;
-        Target target = newControlTarget(isButton ? LauncherLogProto.ControlType.BACK_BUTTON :
-                LauncherLogProto.ControlType.BACK_GESTURE);
+        Target target = newControlTarget(isButton ? ControlType.BACK_BUTTON :
+                ControlType.BACK_GESTURE);
         target.spanX = downX;
         target.spanY = downY;
         target.cardinality = completed ? 1 : 0;
@@ -471,36 +478,14 @@
         if (!IS_VERBOSE) {
             return;
         }
-        Log.d(TAG, generateLog(ev));
-    }
-
-    /**
-     * Returns a human-readable log for given user event.
-     */
-    public static String generateLog(LauncherEvent ev) {
-        String log = "\n-----------------------------------------------------"
-                + "\naction:" + LoggerUtils.getActionStr(ev.action);
-        if (ev.srcTarget != null && ev.srcTarget.length > 0) {
-            log += "\n Source " + getTargetsStr(ev.srcTarget);
+        LauncherLogProto.LauncherEvent liteLauncherEvent;
+        try {
+            liteLauncherEvent =
+                    LauncherLogProto.LauncherEvent.parseFrom(MessageNano.toByteArray(ev));
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException("Cannot parse LauncherEvent from Nano to Lite version");
         }
-        if (ev.destTarget != null && ev.destTarget.length > 0) {
-            log += "\n Destination " + getTargetsStr(ev.destTarget);
-        }
-        log += String.format(Locale.US,
-                "\n Elapsed container %d ms, session %d ms, action %d ms",
-                ev.elapsedContainerMillis,
-                ev.elapsedSessionMillis,
-                ev.actionDurationMillis);
-        log += "\n\n";
-        return log;
-    }
-
-    private static String getTargetsStr(Target[] targets) {
-        String result = "child:" + LoggerUtils.getTargetStr(targets[0]);
-        for (int i = 1; i < targets.length; i++) {
-            result += "\tparent:" + LoggerUtils.getTargetStr(targets[i]);
-        }
-        return result;
+        Log.d(TAG, liteLauncherEvent.toString());
     }
 
     /**
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/model/PagedViewOrientedState.java b/src/com/android/launcher3/model/PagedViewOrientedState.java
index 1349eff..e48b8c1 100644
--- a/src/com/android/launcher3/model/PagedViewOrientedState.java
+++ b/src/com/android/launcher3/model/PagedViewOrientedState.java
@@ -50,7 +50,16 @@
      */
     private boolean mDisableMultipleOrientations;
 
+    /**
+     * Sets the appropriate {@link PagedOrientationHandler} for {@link #mOrientationHandler}
+     * @param touchRotation The rotation the nav bar region that is touched is in
+     * @param displayRotation Rotation of the display/device
+     */
     public void update(int touchRotation, int displayRotation) {
+        if (mDisableMultipleOrientations) {
+            return;
+        }
+
         mDisplayRotation = displayRotation;
         mTouchRotation = touchRotation;
         if (mTouchRotation == Surface.ROTATION_90) {
@@ -62,20 +71,13 @@
         }
     }
 
-    /**
-     * @return {@code true} if the area where the user touched the nav bar is the expected
-     * location for the given display rotation. Ex. bottom of phone in portrait, or left side of
-     * phone in landscape, right side in seascape, etc.
-     * False otherwise
-     */
-    public boolean isTouchRegionNaturalForDisplay() {
-        return mTouchRotation == mDisplayRotation;
-    }
-
     public boolean areMultipleLayoutOrientationsDisabled() {
         return mDisableMultipleOrientations;
     }
 
+    /**
+     * Setting this preference will render future calls to {@link #update(int, int)} as a no-op.
+     */
     public void disableMultipleOrientations(boolean disable) {
         mDisableMultipleOrientations = disable;
         if (disable) {
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/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
index 8fffee8..936d377 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -54,6 +54,11 @@
 
     public SecondaryDragLayer(Context context, AttributeSet attrs) {
         super(context, attrs, 1 /* alphaChannelCount */);
+        recreateControllers();
+    }
+
+    @Override
+    public void recreateControllers() {
         mControllers = new TouchController[] {new CloseAllAppsTouchController()};
     }
 
diff --git a/src/com/android/launcher3/states/RotationHelper.java b/src/com/android/launcher3/states/RotationHelper.java
index 43d54eb..fae0fe2 100644
--- a/src/com/android/launcher3/states/RotationHelper.java
+++ b/src/com/android/launcher3/states/RotationHelper.java
@@ -21,13 +21,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.DisplayMetrics.DENSITY_DEVICE_STABLE;
 
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.config.FeatureFlags.FLAG_ENABLE_FIXED_ROTATION_TRANSFORM;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
 
 import android.content.ContentResolver;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.res.Resources;
-import android.database.ContentObserver;
 import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -72,26 +72,13 @@
         return originalSmallestWidth >= 600;
     }
 
-
-    private final ContentObserver mContentObserver =
-        new ContentObserver(MAIN_EXECUTOR.getHandler()) {
-            @Override
-            public void onChange(boolean selfChange) {
-                boolean forcedRotation = Utilities.isForcedRotation(mLauncher);
-                PagedView.sFlagForcedRotation = forcedRotation;
-                updateForcedRotation();
-                for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
-                    listener.onForcedRotationChanged(forcedRotation);
-                }
-            }
-        };
-
     public static final int REQUEST_NONE = 0;
     public static final int REQUEST_ROTATE = 1;
     public static final int REQUEST_LOCK = 2;
 
     private final Launcher mLauncher;
-    private final SharedPreferences mPrefs;
+    private final SharedPreferences mSharedPrefs;
+    private final SharedPreferences mFeatureFlagsPrefs;
 
     private boolean mIgnoreAutoRotateSettings;
     private boolean mAutoRotateEnabled;
@@ -125,24 +112,42 @@
 
         // On large devices we do not handle auto-rotate differently.
         mIgnoreAutoRotateSettings = mLauncher.getResources().getBoolean(R.bool.allow_rotation);
-        updateForcedRotation();
         if (!mIgnoreAutoRotateSettings) {
-            mPrefs = Utilities.getPrefs(mLauncher);
-            mPrefs.registerOnSharedPreferenceChangeListener(this);
-            mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+            mSharedPrefs = Utilities.getPrefs(mLauncher);
+            mSharedPrefs.registerOnSharedPreferenceChangeListener(this);
+            mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                     getAllowRotationDefaultValue());
         } else {
-            mPrefs = null;
+            mSharedPrefs = null;
         }
 
-        // TODO(b/150260456) Add this in home settings as well
         mContentResolver = launcher.getContentResolver();
-        mContentResolver.registerContentObserver(Settings.Global.getUriFor(
-            FIXED_ROTATION_TRANSFORM_SETTING_NAME), false, mContentObserver);
+        mFeatureFlagsPrefs = Utilities.getFeatureFlagsPrefs(mLauncher);
+        mFeatureFlagsPrefs.registerOnSharedPreferenceChangeListener(this);
+        updateForcedRotation(true);
     }
 
-    private void updateForcedRotation() {
-        mForcedRotation = !getAllowRotationDefaultValue() && Utilities.isForcedRotation(mLauncher);
+    /**
+     * @param setValueFromPrefs If true, then {@link #mForcedRotation} will get set to the value
+     *                          from the home developer settings. Otherwise it will not.
+     *                          This is primarily to allow tests to set their own conditions.
+     */
+    private void updateForcedRotation(boolean setValueFromPrefs) {
+        boolean isForcedRotation = mFeatureFlagsPrefs
+                .getBoolean(FLAG_ENABLE_FIXED_ROTATION_TRANSFORM, true)
+                && !getAllowRotationDefaultValue();
+        if (mForcedRotation == isForcedRotation) {
+            return;
+        }
+        if (setValueFromPrefs) {
+            mForcedRotation = isForcedRotation;
+        }
+        UI_HELPER_EXECUTOR.execute(
+                () -> Settings.Global.putInt(mContentResolver, FIXED_ROTATION_TRANSFORM_SETTING_NAME,
+                        mForcedRotation ? 1 : 0));
+        for (ForcedRotationChangedListener listener : mForcedRotationChangedListeners) {
+            listener.onForcedRotationChanged(mForcedRotation);
+        }
     }
 
     /**
@@ -181,8 +186,13 @@
 
     @Override
     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
+        if (FLAG_ENABLE_FIXED_ROTATION_TRANSFORM.equals(s)) {
+            updateForcedRotation(true);
+            return;
+        }
+
         boolean wasRotationEnabled = mAutoRotateEnabled;
-        mAutoRotateEnabled = mPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
+        mAutoRotateEnabled = mSharedPrefs.getBoolean(ALLOW_ROTATION_PREFERENCE_KEY,
                 getAllowRotationDefaultValue());
         if (mAutoRotateEnabled != wasRotationEnabled) {
 
@@ -218,6 +228,10 @@
     public void forceAllowRotationForTesting(boolean allowRotation) {
         mIgnoreAutoRotateSettings =
                 allowRotation || mLauncher.getResources().getBoolean(R.bool.allow_rotation);
+        // TODO(b/150214193) Tests currently expect launcher to be able to be rotated
+        //   Modify tests for this new behavior
+        mForcedRotation = !allowRotation;
+        updateForcedRotation(false);
         notifyChange();
     }
 
@@ -232,13 +246,11 @@
     public void destroy() {
         if (!mDestroyed) {
             mDestroyed = true;
-            if (mPrefs != null) {
-                mPrefs.unregisterOnSharedPreferenceChangeListener(this);
-            }
-            if (mContentResolver != null) {
-                mContentResolver.unregisterContentObserver(mContentObserver);
+            if (mSharedPrefs != null) {
+                mSharedPrefs.unregisterOnSharedPreferenceChangeListener(this);
             }
             mForcedRotationChangedListeners.clear();
+            mFeatureFlagsPrefs.unregisterOnSharedPreferenceChangeListener(this);
         }
     }
 
@@ -293,7 +305,7 @@
         return degrees;
     }
 
-    public static int getRotationFromDegrees(int degrees) {
+    public static int getRotationFromDegrees(float degrees) {
         int threshold = 70;
         if (degrees >= (360 - threshold) || degrees < (threshold)) {
             return Surface.ROTATION_0;
@@ -318,11 +330,30 @@
     }
 
     /**
+     * For landscape, since the navbar is already in a vertical position, we don't have to do any
+     * rotations as the change in Y coordinate is what is read. We only flip the sign of the
+     * y coordinate to make it match existing behavior of swipe to the top to go previous
+     */
+    public static void transformEventForNavBar(MotionEvent ev, boolean inverse) {
+        // TODO(b/151269990): Use a temp matrix
+        Matrix m = new Matrix();
+        m.setScale(1, -1);
+        if (inverse) {
+            Matrix inv = new Matrix();
+            m.invert(inv);
+            ev.transform(inv);
+        } else {
+            ev.transform(m);
+        }
+    }
+
+    /**
      * Creates a matrix to transform the given motion event specified by degrees.
      * If {@param inverse} is {@code true}, the inverse of that matrix will be applied
      */
     public static void transformEvent(float degrees, MotionEvent ev, boolean inverse) {
         Matrix transform = new Matrix();
+        // TODO(b/151269990): Use a temp matrix
         transform.setRotate(degrees);
         if (inverse) {
             Matrix inv = new Matrix();
@@ -344,6 +375,7 @@
      */
     public static Matrix getRotationMatrix(int screenWidth, int screenHeight, int displayRotation) {
         Matrix m = new Matrix();
+        // TODO(b/151269990): Use a temp matrix
         switch (displayRotation) {
             case Surface.ROTATION_0:
                 return m;
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 4e98781..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;
-import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
-import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
 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.AnimationComponents;
 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;
@@ -176,7 +176,7 @@
     protected abstract LauncherState getTargetState(LauncherState fromState,
             boolean isDragTowardPositive);
 
-    protected abstract float initCurrentAnimation(@AnimationComponents int animComponents);
+    protected abstract float initCurrentAnimation(@AnimationFlags int animComponents);
 
     /**
      * Returns the container that the touch started from when leaving NORMAL state.
@@ -201,10 +201,10 @@
             mCurrentAnimation.setOnCancelRunnable(null);
         }
         int animComponents = goingBetweenNormalAndOverview(mFromState, mToState)
-                ? NON_ATOMIC_COMPONENT : ANIM_ALL;
+                ? PLAY_NON_ATOMIC : ANIM_ALL_COMPONENTS;
         mScheduleResumeAtomicComponent = false;
         if (mAtomicAnim != null) {
-            animComponents = NON_ATOMIC_COMPONENT;
+            animComponents = PLAY_NON_ATOMIC;
             // Control the non-atomic components until the atomic animation finishes, then control
             // the atomic components as well.
             mScheduleResumeAtomicComponent = true;
@@ -215,7 +215,7 @@
         }
 
         if (mAtomicComponentsController != null) {
-            animComponents &= ~ATOMIC_OVERVIEW_SCALE_COMPONENT;
+            animComponents &= ~PLAY_ATOMIC_OVERVIEW_SCALE;
         }
         mProgressMultiplier = initCurrentAnimation(animComponents);
         mCurrentAnimation.dispatchOnStart();
@@ -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,
-                ATOMIC_OVERVIEW_SCALE_COMPONENT, 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 31a5d79..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.AnimationComponents;
+import com.android.launcher3.states.StateAnimationConfig.AnimationFlags;
 import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
 
 /**
@@ -76,7 +76,7 @@
     }
 
     @Override
-    protected float initCurrentAnimation(@AnimationComponents int animComponents) {
+    protected float initCurrentAnimation(@AnimationFlags int animComponents) {
         float range = getShiftRange();
         long maxAccuracy = (long) (2 * range);
         mCurrentAnimation = mLauncher.getStateManager()
diff --git a/src/com/android/launcher3/views/BaseDragLayer.java b/src/com/android/launcher3/views/BaseDragLayer.java
index 254655c..868c91d 100644
--- a/src/com/android/launcher3/views/BaseDragLayer.java
+++ b/src/com/android/launcher3/views/BaseDragLayer.java
@@ -23,7 +23,11 @@
 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
 
 import android.annotation.TargetApi;
+import android.app.WallpaperInfo;
+import android.app.WallpaperManager;
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -38,12 +42,15 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
+import com.android.launcher3.util.SimpleBroadcastReceiver;
 import com.android.launcher3.util.TouchController;
 
 import java.io.PrintWriter;
@@ -99,6 +106,10 @@
 
     protected final T mActivity;
     private final MultiValueAlpha mMultiValueAlpha;
+    private final WallpaperManager mWallpaperManager;
+    private final SimpleBroadcastReceiver mWallpaperChangeReceiver =
+            new SimpleBroadcastReceiver(this::onWallpaperChanged);
+    private final String[] mWallpapersWithoutSysuiScrims;
 
     // All the touch controllers for the view
     protected TouchController[] mControllers;
@@ -109,13 +120,23 @@
 
     private TouchCompleteListener mTouchCompleteListener;
 
+    protected boolean mAllowSysuiScrims = true;
+
     public BaseDragLayer(Context context, AttributeSet attrs, int alphaChannelCount) {
         super(context, attrs);
         mActivity = (T) ActivityContext.lookupContext(context);
         mMultiValueAlpha = new MultiValueAlpha(this, alphaChannelCount);
+        mWallpaperManager = context.getSystemService(WallpaperManager.class);
+        mWallpapersWithoutSysuiScrims = getResources().getStringArray(
+                R.array.live_wallpapers_remove_sysui_scrims);
     }
 
     /**
+     * Called to reinitialize touch controllers.
+     */
+    public abstract void recreateControllers();
+
+    /**
      * Same as {@link #isEventOverView(View, MotionEvent, View)} where evView == this drag layer.
      */
     public boolean isEventOverView(View view, MotionEvent ev) {
@@ -513,4 +534,46 @@
         }
         return super.dispatchApplyWindowInsets(insets);
     }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mWallpaperChangeReceiver.register(mActivity, Intent.ACTION_WALLPAPER_CHANGED);
+        onWallpaperChanged(null);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mActivity.unregisterReceiver(mWallpaperChangeReceiver);
+    }
+
+    private void onWallpaperChanged(Intent unusedBroadcastIntent) {
+        WallpaperInfo newWallpaperInfo = mWallpaperManager.getWallpaperInfo();
+        boolean oldAllowSysuiScrims = mAllowSysuiScrims;
+        mAllowSysuiScrims = computeAllowSysuiScrims(newWallpaperInfo);
+        if (mAllowSysuiScrims != oldAllowSysuiScrims) {
+            // Reapply insets so scrim can be removed or re-added if necessary.
+            setInsets(mInsets);
+        }
+    }
+
+    /**
+     * Determines whether we can scrim the status bar and nav bar for the given wallpaper by
+     * checking against a list of live wallpapers that we don't show the scrims on.
+     */
+    private boolean computeAllowSysuiScrims(@Nullable WallpaperInfo newWallpaperInfo) {
+        if (newWallpaperInfo == null) {
+            // New wallpaper is static, not live. Thus, blacklist isn't applicable.
+            return true;
+        }
+        ComponentName newWallpaper = newWallpaperInfo.getComponent();
+        for (String wallpaperWithoutScrim : mWallpapersWithoutSysuiScrims) {
+            if (newWallpaper.equals(ComponentName.unflattenFromString(wallpaperWithoutScrim))) {
+                // New wallpaper is blacklisted from showing a scrim.
+                return false;
+            }
+        }
+        return true;
+    }
 }
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
index 81f8327..d849138 100644
--- a/src/com/android/launcher3/views/WorkEduView.java
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -46,7 +46,8 @@
 public class WorkEduView extends AbstractSlideInView implements Insettable {
 
     private static final int DEFAULT_CLOSE_DURATION = 200;
-    private static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
+    public static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
+    public static final String KEY_LEGACY_WORK_EDU_SEEN = "showed_bottom_user_education";
 
     private static final int WORK_EDU_NOT_STARTED = 0;
     private static final int WORK_EDU_PERSONAL_APPS = 1;
@@ -102,6 +103,8 @@
         mProceedButton = findViewById(R.id.proceed);
         mContentText = findViewById(R.id.content_text);
 
+        // make sure layout does not shrink when we change the text
+        mContentText.post(() -> mContentText.setMinLines(mContentText.getLineCount()));
         if (mLauncher.getAppsView().getContentView() instanceof AllAppsPagedView) {
             mAllAppsPagedView = (AllAppsPagedView) mLauncher.getAppsView().getContentView();
         }
@@ -179,8 +182,8 @@
         if (oldListener != null) {
             launcher.getStateManager().removeStateListener(oldListener);
         }
-        if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
-                != WORK_EDU_NOT_STARTED) {
+        if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
+                WORK_EDU_NOT_STARTED) != WORK_EDU_NOT_STARTED) {
             return null;
         }
 
@@ -210,8 +213,8 @@
      * Shows work apps edu if user had dismissed full edu flow
      */
     public static void showWorkEduIfNeeded(Launcher launcher) {
-        if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
-                != WORK_EDU_PERSONAL_APPS) {
+        if (hasSeenLegacyEdu(launcher) || launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP,
+                WORK_EDU_NOT_STARTED) != WORK_EDU_PERSONAL_APPS) {
             return;
         }
         LayoutInflater layoutInflater = LayoutInflater.from(launcher);
@@ -220,4 +223,8 @@
         v.show();
         v.goToWorkTab(false);
     }
+
+    private static boolean hasSeenLegacyEdu(Launcher launcher) {
+        return launcher.getSharedPrefs().getBoolean(KEY_LEGACY_WORK_EDU_SEEN, false);
+    }
 }
diff --git a/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java
index 9ac8230..d86d0ff 100644
--- a/src/com/android/launcher3/views/WorkFooterContainer.java
+++ b/src/com/android/launcher3/views/WorkFooterContainer.java
@@ -112,8 +112,6 @@
         boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
                 getContext()).isAnyProfileQuietModeEnabled();
 
-        mWorkModeLabel.setText(anyProfileQuietModeEnabled
-                ? R.string.work_mode_off_label : R.string.work_mode_on_label);
         mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds(
                 anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0);
         mWorkModeSwitch.refresh();
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
deleted file mode 100644
index 232bad3..0000000
--- a/src_ui_overrides/com/android/launcher3/uioverrides/BackgroundBlurController.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.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;
-
-/**
- * Controls the blur, for the Launcher surface only.
- */
-public class BackgroundBlurController implements LauncherStateManager.StateHandler {
-
-    public static final IntProperty<BackgroundBlurController> BACKGROUND_BLUR =
-            new IntProperty<BackgroundBlurController>("backgroundBlur") {
-                @Override
-                public void setValue(BackgroundBlurController blurController, int blurRadius) {}
-
-                @Override
-                public Integer get(BackgroundBlurController blurController) {
-                    return 0;
-                }
-            };
-
-    public BackgroundBlurController(Launcher l) {}
-
-    public int getFolderBackgroundBlurAdjustment() {
-        return 0;
-    }
-
-    public void setSurfaceToLauncher(View v) {}
-
-    @Override
-    public void setState(LauncherState toState) {}
-
-    @Override
-    public void setStateWithAnimation(LauncherState toState, AnimatorSetBuilder builder,
-            LauncherStateManager.AnimationConfig config) {}
-}
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java b/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java
new file mode 100644
index 0000000..7ad85e2
--- /dev/null
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/DepthController.java
@@ -0,0 +1,55 @@
+/*
+ * 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.util.FloatProperty;
+import android.view.View;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.states.StateAnimationConfig;
+
+/**
+ * Controls blur and wallpaper zoom, for the Launcher surface only.
+ */
+public class DepthController implements LauncherStateManager.StateHandler {
+
+    public static final FloatProperty<DepthController> DEPTH =
+            new FloatProperty<DepthController>("depth") {
+                @Override
+                public void setValue(DepthController depthController, float depth) {}
+
+                @Override
+                public Float get(DepthController depthController) {
+                    return 0f;
+                }
+            };
+
+    public DepthController(Launcher l) {}
+
+    public void setSurfaceToLauncher(View v) {}
+
+    @Override
+    public void setState(LauncherState toState) {}
+
+    @Override
+    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) { }
+}
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 3d12248..f5dd995 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -102,6 +102,7 @@
 
     private static String sDetectedActivityLeak;
     private static boolean sActivityLeakReported;
+    private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
 
     protected LooperExecutor mMainThreadExecutor = MAIN_EXECUTOR;
     protected final UiDevice mDevice = UiDevice.getInstance(getInstrumentation());
@@ -213,8 +214,20 @@
         return mDevice;
     }
 
+    private boolean hasSystemUiObject(String resId) {
+        return mDevice.hasObject(By.res(SYSTEMUI_PACKAGE, resId));
+    }
+
     @Before
     public void setUp() throws Exception {
+        mDevice.executeShellCommand("settings put global stay_on_while_plugged_in 3");
+        if (hasSystemUiObject("keyguard_status_view")) {
+            Log.d(TAG, "Before unlocking the phone");
+            mDevice.executeShellCommand("input keyevent 82");
+        } else {
+            Log.d(TAG, "Phone isn't locked");
+        }
+
         final String launcherPackageName = mDevice.getLauncherPackageName();
         try {
             final Context context = InstrumentationRegistry.getContext();
diff --git a/tests/src/com/android/launcher3/ui/WorkTabTest.java b/tests/src/com/android/launcher3/ui/WorkTabTest.java
index 6fe6739..db2d974 100644
--- a/tests/src/com/android/launcher3/ui/WorkTabTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkTabTest.java
@@ -16,8 +16,6 @@
 package com.android.launcher3.ui;
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
-import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
-import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -25,13 +23,16 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.widget.TextView;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.launcher3.R;
 import com.android.launcher3.allapps.AllAppsContainerView;
 import com.android.launcher3.allapps.AllAppsPagedView;
-import com.android.launcher3.util.rule.TestStabilityRule;
+import com.android.launcher3.dragndrop.DragLayer;
+import com.android.launcher3.views.WorkEduView;
 import com.android.launcher3.views.WorkFooterContainer;
 
 import org.junit.After;
@@ -48,6 +49,8 @@
 
     private int mProfileUserId;
 
+    private static final int WORK_PAGE = AllAppsContainerView.AdapterHolder.WORK;
+
     @Before
     public void createWorkProfile() throws Exception {
         String output =
@@ -67,8 +70,6 @@
     }
 
     @Test
-    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
-    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
     public void workTabExists() {
         mDevice.pressHome();
         waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
@@ -80,8 +81,6 @@
     }
 
     @Test
-    // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
-    @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
     public void toggleWorks() {
         mDevice.pressHome();
         waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
@@ -112,4 +111,78 @@
                 l -> l.getSystemService(UserManager.class).isQuietModeEnabled(workProfile));
     }
 
+    @Test
+    public void testWorkEduFlow() {
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+        executeOnLauncher(launcher -> launcher.getSharedPrefs().edit().remove(
+                WorkEduView.KEY_WORK_EDU_STEP).remove(
+                WorkEduView.KEY_LEGACY_WORK_EDU_SEEN).commit());
+
+        waitForLauncherCondition("Work tab not setup",
+                launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
+                60000);
+
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        WorkEduView workEduView = getEduView();
+        // verify personal app edu is seen first and click "next"
+        executeOnLauncher(l -> {
+            assertEquals(((TextView) workEduView.findViewById(R.id.content_text)).getText(),
+                    l.getResources().getString(R.string.work_profile_edu_personal_apps));
+            workEduView.findViewById(R.id.proceed).callOnClick();
+        });
+        // verify work edu is seen next
+        waitForLauncherCondition("Launcher did not show the next edu screen", l ->
+                ((AllAppsPagedView) l.getAppsView().getContentView()).getCurrentPage() == WORK_PAGE
+                        && ((TextView) workEduView.findViewById(
+                        R.id.content_text)).getText().equals(
+                        l.getResources().getString(R.string.work_profile_edu_work_apps)));
+    }
+
+    @Test
+    public void testWorkEduIntermittent() {
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+        executeOnLauncher(launcher -> launcher.getSharedPrefs().edit().remove(
+                WorkEduView.KEY_WORK_EDU_STEP).remove(
+                WorkEduView.KEY_LEGACY_WORK_EDU_SEEN).commit());
+
+
+        waitForLauncherCondition("Work tab not setup",
+                launcher -> launcher.getAppsView().getContentView() instanceof AllAppsPagedView,
+                60000);
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+
+        // verify personal app edu is seen
+        getEduView();
+
+        // dismiss personal edu
+        mDevice.pressHome();
+
+        // open work tab
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        executeOnLauncher(launcher -> {
+            AllAppsPagedView pagedView = (AllAppsPagedView) launcher.getAppsView().getContentView();
+            pagedView.setCurrentPage(WORK_PAGE);
+        });
+
+        WorkEduView workEduView = getEduView();
+
+        // verify work tab edu is shown
+        waitForLauncherCondition("Launcher did not show the next edu screen",
+                l -> ((TextView) workEduView.findViewById(R.id.content_text)).getText().equals(
+                        l.getResources().getString(R.string.work_profile_edu_work_apps)));
+    }
+
+
+    private WorkEduView getEduView() {
+        waitForLauncherCondition("Edu did not show", l -> {
+            DragLayer dragLayer = l.getDragLayer();
+            return dragLayer.getChildCount() > 0 && dragLayer.getChildAt(
+                    dragLayer.getChildCount() - 1) instanceof WorkEduView;
+        });
+        return getFromLauncher(launcher -> (WorkEduView) launcher.getDragLayer().getChildAt(
+                launcher.getDragLayer().getChildCount() - 1));
+    }
+
 }
\ No newline at end of file