Merge "Passing in the launch cookie for widget broadcast trampolines" into tm-dev
diff --git a/quickstep/res/layout/taskbar_all_apps.xml b/quickstep/res/layout/taskbar_all_apps.xml
index 11d75c7..7dc0cbe 100644
--- a/quickstep/res/layout/taskbar_all_apps.xml
+++ b/quickstep/res/layout/taskbar_all_apps.xml
@@ -14,12 +14,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.launcher3.taskbar.TaskbarAllAppsSlideInView
+<com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <com.android.launcher3.taskbar.TaskbarAllAppsContainerView
+    <com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView
         android:id="@+id/apps_view"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
@@ -52,5 +52,5 @@
         </com.android.launcher3.allapps.FloatingHeaderView>
 
         <include layout="@layout/all_apps_fast_scroller" />
-    </com.android.launcher3.taskbar.TaskbarAllAppsContainerView>
-</com.android.launcher3.taskbar.TaskbarAllAppsSlideInView>
+    </com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView>
+</com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 367c390..cb6094b 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -66,6 +66,7 @@
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ObjectWrapper;
+import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
 import com.android.launcher3.util.UiThreadHelper;
 import com.android.quickstep.OverviewCommandHelper;
@@ -498,6 +499,20 @@
     }
 
     @Override
+    public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
+        pendingTasks.add(() -> {
+            // This is added in pending task as we need to wait for views to be positioned
+            // correctly before registering them for the animation.
+            if (mLauncherUnfoldAnimationController != null) {
+                // This is needed in case items are rebound while the unfold animation is in
+                // progress.
+                mLauncherUnfoldAnimationController.updateRegisteredViewsIfNeeded();
+            }
+        });
+        super.onInitialBindComplete(boundPages, pendingTasks);
+    }
+
+    @Override
     public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
         Stream<SystemShortcut.Factory> base = Stream.of(WellbeingModel.SHORTCUT_FACTORY);
         if (ENABLE_SPLIT_FROM_WORKSPACE.get() && mDeviceProfile.isTablet) {
diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
index d54b9e7..3b02599 100644
--- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
+++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java
@@ -23,6 +23,7 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
+import android.app.WallpaperManager;
 import android.os.IBinder;
 import android.os.SystemProperties;
 import android.util.FloatProperty;
@@ -42,7 +43,6 @@
 import com.android.launcher3.statemanager.StateManager.StateHandler;
 import com.android.launcher3.states.StateAnimationConfig;
 import com.android.systemui.shared.system.BlurUtils;
-import com.android.systemui.shared.system.WallpaperManagerCompat;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
@@ -127,7 +127,7 @@
      */
     private int mMaxBlurRadius;
     private boolean mCrossWindowBlursEnabled;
-    private WallpaperManagerCompat mWallpaperManager;
+    private WallpaperManager mWallpaperManager;
     private SurfaceControl mSurface;
     /**
      * How visible the -1 overlay is, from 0 to 1.
@@ -168,7 +168,7 @@
     private void ensureDependencies() {
         if (mWallpaperManager == null) {
             mMaxBlurRadius = mLauncher.getResources().getInteger(R.integer.max_depth_blur_radius);
-            mWallpaperManager = new WallpaperManagerCompat(mLauncher);
+            mWallpaperManager = mLauncher.getSystemService(WallpaperManager.class);
         }
 
         if (mLauncher.getRootView() != null && mOnAttachListener == null) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
new file mode 100644
index 0000000..17635df
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/BaseTaskbarContext.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.taskbar;
+
+import android.content.Context;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
+import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.views.ActivityContext;
+
+import java.util.ArrayList;
+import java.util.List;
+
+// TODO(b/218912746): Share more behavior to avoid all apps context depending directly on taskbar.
+/** Base for common behavior between taskbar window contexts. */
+public abstract class BaseTaskbarContext extends ContextThemeWrapper implements ActivityContext,
+        DeviceProfileListenable {
+
+    protected final LayoutInflater mLayoutInflater;
+    private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
+
+    protected DeviceProfile mDeviceProfile;
+
+    public BaseTaskbarContext(Context windowContext) {
+        super(windowContext, Themes.getActivityThemeRes(windowContext));
+        mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
+    }
+
+    @Override
+    public final LayoutInflater getLayoutInflater() {
+        return mLayoutInflater;
+    }
+
+    @Override
+    public final DeviceProfile getDeviceProfile() {
+        return mDeviceProfile;
+    }
+
+    @Override
+    public final List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() {
+        return mDPChangeListeners;
+    }
+
+    /** Updates the {@link DeviceProfile} instance to the latest representation of the screen. */
+    public abstract void updateDeviceProfile(DeviceProfile dp);
+
+    /** Callback invoked when a drag is initiated within this context. */
+    public abstract void onDragStart();
+
+    /** Callback invoked when a popup is shown or closed within this context. */
+    public abstract void onPopupVisibilityChanged(boolean isVisible);
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 0f91aa2..af53ae2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -228,8 +228,7 @@
      * Whether the taskbar education should be shown.
      */
     public boolean shouldShowEdu() {
-        return FeatureFlags.ENABLE_TASKBAR_EDU.get()
-                && !Utilities.IS_RUNNING_IN_TEST_HARNESS
+        return !Utilities.IS_RUNNING_IN_TEST_HARNESS
                 && !mLauncher.getOnboardingPrefs().getBoolean(OnboardingPrefs.TASKBAR_EDU_SEEN);
     }
 
@@ -237,10 +236,6 @@
      * Manually ends the taskbar education flow.
      */
     public void hideEdu() {
-        if (!FeatureFlags.ENABLE_TASKBAR_EDU.get()) {
-            return;
-        }
-
         mControllers.taskbarEduController.hideEdu();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index fe091ef..8f0b934 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -42,10 +42,8 @@
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Log;
-import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.Gravity;
-import android.view.LayoutInflater;
 import android.view.RoundedCorner;
 import android.view.View;
 import android.view.WindowManager;
@@ -58,11 +56,8 @@
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.DeviceProfile.DeviceProfileListenable;
-import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
 import com.android.launcher3.dot.DotInfo;
 import com.android.launcher3.folder.Folder;
 import com.android.launcher3.folder.FolderIcon;
@@ -72,11 +67,10 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
 import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.util.OnboardingPrefs;
 import com.android.launcher3.util.PackageManagerHelper;
 import com.android.launcher3.util.SettingsCache;
-import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TraceHelper;
 import com.android.launcher3.util.ViewCache;
 import com.android.launcher3.views.ActivityContext;
@@ -89,16 +83,13 @@
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
  * that are used by both Launcher and Taskbar (such as Folder) to reference a generic
  * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer.
  */
-public class TaskbarActivityContext extends ContextThemeWrapper implements ActivityContext,
-        DeviceProfileListenable {
+public class TaskbarActivityContext extends BaseTaskbarContext {
 
     private static final boolean ENABLE_THREE_BUTTON_TASKBAR =
             SystemProperties.getBoolean("persist.debug.taskbar_three_button", false);
@@ -106,13 +97,8 @@
 
     private static final String WINDOW_TITLE = "Taskbar";
 
-    private final LayoutInflater mLayoutInflater;
     private final TaskbarDragLayer mDragLayer;
-    private final TaskbarAllAppsContainerView mAppsView;
     private final TaskbarControllers mControllers;
-    private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
-
-    private DeviceProfile mDeviceProfile;
 
     private final WindowManager mWindowManager;
     private final @Nullable RoundedCorner mLeftCorner, mRightCorner;
@@ -135,14 +121,12 @@
     private boolean mBindingItems = false;
 
     private final TaskbarShortcutMenuAccessibilityDelegate mAccessibilityDelegate;
-    private final OnboardingPrefs<TaskbarActivityContext> mOnboardingPrefs;
 
     public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
             TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
             unfoldTransitionProgressProvider) {
-        super(windowContext, Themes.getActivityThemeRes(windowContext));
+        super(windowContext);
         mDeviceProfile = dp;
-        mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));
 
         mNavMode = SysUINavigationMode.getMode(windowContext);
         mImeDrawsImeNavBar = SysUINavigationMode.getImeDrawsImeNavBar(windowContext);
@@ -158,8 +142,6 @@
 
         mTaskbarHeightForIme = resources.getDimensionPixelSize(R.dimen.taskbar_ime_size);
 
-        mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
-
         // Inflate views.
         mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
                 R.layout.taskbar, null, false);
@@ -168,11 +150,6 @@
         FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
         StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
 
-        TaskbarAllAppsSlideInView appsSlideInView =
-                (TaskbarAllAppsSlideInView) mLayoutInflater.inflate(R.layout.taskbar_all_apps,
-                        mDragLayer, false);
-        mAppsView = appsSlideInView.getAppsView();
-
         Display display = windowContext.getDisplay();
         Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
                 ? windowContext.getApplicationContext()
@@ -210,7 +187,7 @@
                 new TaskbarAutohideSuspendController(this),
                 new TaskbarPopupController(this),
                 new TaskbarForceVisibleImmersiveController(this),
-                new TaskbarAllAppsViewController(this, appsSlideInView));
+                new TaskbarAllAppsController(this));
     }
 
     public void init(TaskbarSharedState sharedState) {
@@ -236,7 +213,7 @@
         mWindowManager.addView(mDragLayer, mWindowLayoutParams);
     }
 
-    /** Updates the Device profile instance to the latest representation of the screen. */
+    @Override
     public void updateDeviceProfile(DeviceProfile dp) {
         mDeviceProfile = dp;
         updateIconSize(getResources());
@@ -297,31 +274,11 @@
     }
 
     @Override
-    public LayoutInflater getLayoutInflater() {
-        return mLayoutInflater;
-    }
-
-    @Override
     public TaskbarDragLayer getDragLayer() {
         return mDragLayer;
     }
 
     @Override
-    public TaskbarAllAppsContainerView getAppsView() {
-        return mAppsView;
-    }
-
-    @Override
-    public DeviceProfile getDeviceProfile() {
-        return mDeviceProfile;
-    }
-
-    @Override
-    public List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() {
-        return mDPChangeListeners;
-    }
-
-    @Override
     public Rect getFolderBoundingBox() {
         return mControllers.taskbarDragLayerController.getFolderBoundingBox();
     }
@@ -412,11 +369,6 @@
     }
 
     @Override
-    public OnboardingPrefs<TaskbarActivityContext> getOnboardingPrefs() {
-        return mOnboardingPrefs;
-    }
-
-    @Override
     public boolean isBindingItems() {
         return mBindingItems;
     }
@@ -425,6 +377,16 @@
         mBindingItems = bindingItems;
     }
 
+    @Override
+    public void onDragStart() {
+        setTaskbarWindowFullscreen(true);
+    }
+
+    @Override
+    public void onPopupVisibilityChanged(boolean isVisible) {
+        setTaskbarWindowFocusable(isVisible);
+    }
+
     /**
      * Sets a new data-source for this taskbar instance
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsViewController.java
deleted file mode 100644
index 2670200..0000000
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsViewController.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2022 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.taskbar;
-
-import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
-
-import com.android.launcher3.appprediction.AppsDividerView;
-import com.android.launcher3.appprediction.PredictionRowView;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.data.AppInfo;
-import com.android.launcher3.model.data.ItemInfo;
-
-import java.util.List;
-
-/** Handles the {@link TaskbarAllAppsContainerView} initialization and updates. */
-public final class TaskbarAllAppsViewController {
-
-    private final TaskbarActivityContext mContext;
-    private final TaskbarAllAppsSlideInView mSlideInView;
-    private final TaskbarAllAppsContainerView mAppsView;
-
-    public TaskbarAllAppsViewController(
-            TaskbarActivityContext context, TaskbarAllAppsSlideInView slideInView) {
-        mContext = context;
-        mSlideInView = slideInView;
-        mAppsView = mSlideInView.getAppsView();
-    }
-
-    /** Initialize the controller. */
-    public void init(TaskbarControllers controllers) {
-        if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
-            return;
-        }
-
-        mAppsView.setOnIconLongClickListener(
-                controllers.taskbarDragController::startDragOnLongClick);
-        mAppsView.getFloatingHeaderView().findFixedRowByType(
-                PredictionRowView.class).setOnIconLongClickListener(
-                controllers.taskbarDragController::startDragOnLongClick);
-    }
-
-    /** Binds the current {@link AppInfo} instances to the {@link TaskbarAllAppsContainerView}. */
-    public void setApps(AppInfo[] apps, int flags) {
-        if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
-            mAppsView.getAppsStore().setApps(apps, flags);
-        }
-    }
-
-    /** Binds the current app predictions to all apps {@link PredictionRowView}. */
-    public void setPredictedApps(List<ItemInfo> predictedApps) {
-        if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
-            PredictionRowView<?> predictionRowView =
-                    mAppsView.getFloatingHeaderView().findFixedRowByType(PredictionRowView.class);
-            predictionRowView.setPredictedApps(predictedApps);
-        }
-    }
-
-    /** Opens the {@link TaskbarAllAppsContainerView}. */
-    public void show() {
-        if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
-            return;
-        }
-
-        mAppsView.getFloatingHeaderView().findFixedRowByType(AppsDividerView.class)
-                .setShowAllAppsLabel(
-                        !mContext.getOnboardingPrefs().hasReachedMaxCount(ALL_APPS_VISITED_COUNT));
-        mContext.getOnboardingPrefs().incrementEventCount(ALL_APPS_VISITED_COUNT);
-        mContext.setTaskbarWindowFullscreen(true);
-        mSlideInView.show();
-    }
-}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
index 21fbb3b..d2e24e5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarControllers.java
@@ -20,6 +20,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
 import com.android.systemui.shared.rotation.RotationButtonController;
 
 import java.io.PrintWriter;
@@ -48,7 +49,7 @@
     public final TaskbarAutohideSuspendController taskbarAutohideSuspendController;
     public final TaskbarPopupController taskbarPopupController;
     public final TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController;
-    public final TaskbarAllAppsViewController taskbarAllAppsViewController;
+    public final TaskbarAllAppsController taskbarAllAppsController;
 
     @Nullable private LoggableTaskbarController[] mControllersToLog = null;
 
@@ -74,7 +75,7 @@
             TaskbarAutohideSuspendController taskbarAutoHideSuspendController,
             TaskbarPopupController taskbarPopupController,
             TaskbarForceVisibleImmersiveController taskbarForceVisibleImmersiveController,
-            TaskbarAllAppsViewController taskbarAllAppsViewController) {
+            TaskbarAllAppsController taskbarAllAppsController) {
         this.taskbarActivityContext = taskbarActivityContext;
         this.taskbarDragController = taskbarDragController;
         this.navButtonController = navButtonController;
@@ -91,7 +92,7 @@
         this.taskbarAutohideSuspendController = taskbarAutoHideSuspendController;
         this.taskbarPopupController = taskbarPopupController;
         this.taskbarForceVisibleImmersiveController = taskbarForceVisibleImmersiveController;
-        this.taskbarAllAppsViewController = taskbarAllAppsViewController;
+        this.taskbarAllAppsController = taskbarAllAppsController;
     }
 
     /**
@@ -115,7 +116,7 @@
         taskbarEduController.init(this);
         taskbarPopupController.init(this);
         taskbarForceVisibleImmersiveController.init(this);
-        taskbarAllAppsViewController.init(this);
+        taskbarAllAppsController.init(this);
 
         mControllersToLog = new LoggableTaskbarController[] {
                 taskbarDragController, navButtonController, navbarButtonsViewController,
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 6a2f622..12b1195 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -66,8 +66,6 @@
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ClipDescriptionCompat;
-import com.android.systemui.shared.system.LauncherAppsCompat;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -75,7 +73,7 @@
 /**
  * Handles long click on Taskbar items to start a system drag and drop operation.
  */
-public class TaskbarDragController extends DragController<TaskbarActivityContext> implements
+public class TaskbarDragController extends DragController<BaseTaskbarContext> implements
         TaskbarControllers.LoggableTaskbarController {
 
     private static boolean DEBUG_DRAG_SHADOW_SURFACE = false;
@@ -95,7 +93,7 @@
     // Animation for the drag shadow back into position after an unsuccessful drag
     private ValueAnimator mReturnAnimator;
 
-    public TaskbarDragController(TaskbarActivityContext activity) {
+    public TaskbarDragController(BaseTaskbarContext activity) {
         super(activity);
         Resources resources = mActivity.getResources();
         mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size);
@@ -110,7 +108,7 @@
      * generate the ClipDescription and Intent.
      * @return Whether {@link View#startDragAndDrop} started successfully.
      */
-    protected boolean startDragOnLongClick(View view) {
+    public boolean startDragOnLongClick(View view) {
         return startDragOnLongClick(view, null, null);
     }
 
@@ -131,8 +129,7 @@
         }
 
         BubbleTextView btv = (BubbleTextView) view;
-
-        mActivity.setTaskbarWindowFullscreen(true);
+        mActivity.onDragStart();
         btv.post(() -> {
             DragView dragView = startInternalDrag(btv, dragPreviewProvider);
             if (iconShift != null) {
@@ -185,7 +182,7 @@
             }
         };
         if (FeatureFlags.ENABLE_TASKBAR_POPUP_MENU.get()) {
-            PopupContainerWithArrow<TaskbarActivityContext> popupContainer =
+            PopupContainerWithArrow<BaseTaskbarContext> popupContainer =
                     mControllers.taskbarPopupController.showForIcon(btv);
             if (popupContainer != null) {
                 dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
@@ -315,13 +312,13 @@
             clipDescription = new ClipDescription(item.title,
                     new String[] {
                             item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
-                                    ? ClipDescriptionCompat.MIMETYPE_APPLICATION_SHORTCUT
-                                    : ClipDescriptionCompat.MIMETYPE_APPLICATION_ACTIVITY
+                                    ? ClipDescription.MIMETYPE_APPLICATION_SHORTCUT
+                                    : ClipDescription.MIMETYPE_APPLICATION_ACTIVITY
                     });
             intent = new Intent();
             if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
                 String deepShortcutId = ((WorkspaceItemInfo) item).getDeepShortcutId();
-                intent.putExtra(ClipDescriptionCompat.EXTRA_PENDING_INTENT,
+                intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT,
                         launcherApps.getShortcutIntent(
                                 item.getIntent().getPackage(),
                                 deepShortcutId,
@@ -330,19 +327,19 @@
                 intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
                 intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId);
             } else {
-                intent.putExtra(ClipDescriptionCompat.EXTRA_PENDING_INTENT,
-                        LauncherAppsCompat.getMainActivityLaunchIntent(launcherApps,
-                                item.getIntent().getComponent(), null, item.user));
+                intent.putExtra(ClipDescription.EXTRA_PENDING_INTENT,
+                        launcherApps.getMainActivityLaunchIntent(item.getIntent().getComponent(),
+                                null, item.user));
             }
             intent.putExtra(Intent.EXTRA_USER, item.user);
         } else if (tag instanceof Task) {
             Task task = (Task) tag;
             clipDescription = new ClipDescription(task.titleDescription,
                     new String[] {
-                            ClipDescriptionCompat.MIMETYPE_APPLICATION_TASK
+                            ClipDescription.MIMETYPE_APPLICATION_TASK
                     });
             intent = new Intent();
-            intent.putExtra(ClipDescriptionCompat.EXTRA_TASK_ID, task.key.id);
+            intent.putExtra(Intent.EXTRA_TASK_ID, task.key.id);
             intent.putExtra(Intent.EXTRA_USER, UserHandle.of(task.key.userId));
         }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
index cf28eff..7a42432 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragView.java
@@ -25,8 +25,8 @@
  * while the pre-drag is still in progress (i.e. when the long press popup is still open). After
  * that ends, we switch to a system drag and drop view instead.
  */
-public class TaskbarDragView extends DragView<TaskbarActivityContext> {
-    public TaskbarDragView(TaskbarActivityContext launcher, Drawable drawable, int registrationX,
+public class TaskbarDragView extends DragView<BaseTaskbarContext> {
+    public TaskbarDragView(BaseTaskbarContext launcher, Drawable drawable, int registrationX,
             int registrationY, float initialScale, float scaleOnDrop, float finalScaleDps) {
         super(launcher, drawable, registrationX, registrationY, initialScale, scaleOnDrop,
                 finalScaleDps);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 50be430..0f7abda 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -236,8 +236,7 @@
         DeviceProfile dp =
                 mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;
 
-        boolean isTaskBarEnabled =
-                FeatureFlags.ENABLE_TASKBAR.get() && dp != null && dp.isTaskbarPresent;
+        boolean isTaskBarEnabled = dp != null && dp.isTaskbarPresent;
 
         if (!isTaskBarEnabled) {
             SystemUiProxy.INSTANCE.get(mContext)
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
index 2e18a40..62392ee 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarModelCallbacks.java
@@ -158,7 +158,7 @@
             mPredictedItems = item.items;
             commitItemsToUI();
         } else if (item.containerId == Favorites.CONTAINER_PREDICTION) {
-            mControllers.taskbarAllAppsViewController.setPredictedApps(item.items);
+            mControllers.taskbarAllAppsController.setPredictedApps(item.items);
         }
     }
 
@@ -201,7 +201,7 @@
 
     @Override
     public void bindAllApplications(AppInfo[] apps, int flags) {
-        mControllers.taskbarAllAppsViewController.setApps(apps, flags);
+        mControllers.taskbarAllAppsController.setApps(apps, flags);
     }
 
     protected void dumpLogs(String prefix, PrintWriter pw) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
index 68459ab..ea4fe34 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPopupController.java
@@ -52,7 +52,7 @@
  */
 public class TaskbarPopupController implements TaskbarControllers.LoggableTaskbarController {
 
-    private static final SystemShortcut.Factory<TaskbarActivityContext>
+    private static final SystemShortcut.Factory<BaseTaskbarContext>
             APP_INFO = SystemShortcut.AppInfo::new;
 
     private final TaskbarActivityContext mContext;
@@ -121,8 +121,8 @@
      * Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}.
      * @return the container if shown or null.
      */
-    public PopupContainerWithArrow<TaskbarActivityContext> showForIcon(BubbleTextView icon) {
-        TaskbarActivityContext context = ActivityContext.lookupContext(icon.getContext());
+    public PopupContainerWithArrow<BaseTaskbarContext> showForIcon(BubbleTextView icon) {
+        BaseTaskbarContext context = ActivityContext.lookupContext(icon.getContext());
         if (PopupContainerWithArrow.getOpen(context) != null) {
             // There is already an items container open, so don't open this one.
             icon.clearFocus();
@@ -133,11 +133,11 @@
             return null;
         }
 
-        final PopupContainerWithArrow<TaskbarActivityContext> container =
-                (PopupContainerWithArrow) context.getLayoutInflater().inflate(
+        final PopupContainerWithArrow<BaseTaskbarContext> container =
+                (PopupContainerWithArrow<BaseTaskbarContext>) context.getLayoutInflater().inflate(
                         R.layout.popup_container, context.getDragLayer(), false);
         container.addOnAttachStateChangeListener(
-                new PopupLiveUpdateHandler<TaskbarActivityContext>(mContext, container) {
+                new PopupLiveUpdateHandler<BaseTaskbarContext>(context, container) {
                     @Override
                     protected void showPopupContainerForIcon(BubbleTextView originalIcon) {
                         showForIcon(originalIcon);
@@ -157,10 +157,9 @@
         container.requestFocus();
 
         // Make focusable to receive back events
-        mControllers.taskbarActivityContext.setTaskbarWindowFocusable(true);
+        context.onPopupVisibilityChanged(true);
         container.setOnCloseCallback(() -> {
-            mControllers.taskbarActivityContext.getDragLayer().post(
-                    () -> mControllers.taskbarActivityContext.setTaskbarWindowFocusable(false));
+            context.getDragLayer().post(() -> context.onPopupVisibilityChanged(false));
             container.setOnCloseCallback(null);
         });
 
@@ -207,7 +206,8 @@
             iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
             iconShift.y = mIconLastTouchPos.y - mContext.getDeviceProfile().iconSizePx;
 
-            mControllers.taskbarDragController.startDragOnLongClick(sv, iconShift);
+            ((TaskbarDragController) ActivityContext.lookupContext(
+                    v.getContext()).getDragController()).startDragOnLongClick(sv, iconShift);
 
             return false;
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
index 1589582..f131595 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarShortcutMenuAccessibilityDelegate.java
@@ -35,7 +35,6 @@
 import com.android.launcher3.notification.NotificationListener;
 import com.android.launcher3.util.ShortcutUtil;
 import com.android.quickstep.SystemUiProxy;
-import com.android.systemui.shared.system.LauncherAppsCompat;
 
 import java.util.List;
 
@@ -94,8 +93,7 @@
                         info.user);
             } else {
                 SystemUiProxy.INSTANCE.get(mContext).startIntent(
-                        LauncherAppsCompat.getMainActivityLaunchIntent(
-                                mLauncherApps,
+                        mLauncherApps.getMainActivityLaunchIntent(
                                 item.getIntent().getComponent(),
                                 /* startActivityOptions= */null,
                                 item.user),
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index 335b637..1d5fa4d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -51,11 +51,12 @@
     public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 4; // setup wizard and AllSetActivity
     public static final int FLAG_STASHED_IN_APP_IME = 1 << 5; // IME is visible
     public static final int FLAG_IN_STASHED_LAUNCHER_STATE = 1 << 6;
+    public static final int FLAG_STASHED_IN_APP_ALL_APPS = 1 << 7; // All apps is visible.
 
     // If we're in an app and any of these flags are enabled, taskbar should be stashed.
     private static final int FLAGS_STASHED_IN_APP = FLAG_STASHED_IN_APP_MANUAL
             | FLAG_STASHED_IN_APP_PINNED | FLAG_STASHED_IN_APP_EMPTY | FLAG_STASHED_IN_APP_SETUP
-            | FLAG_STASHED_IN_APP_IME;
+            | FLAG_STASHED_IN_APP_IME | FLAG_STASHED_IN_APP_ALL_APPS;
 
     // If any of these flags are enabled, inset apps by our stashed height instead of our unstashed
     // height. This way the reported insets are consistent even during transitions out of the app.
@@ -585,6 +586,7 @@
         str.add((flags & FLAG_STASHED_IN_APP_IME) != 0 ? "FLAG_STASHED_IN_APP_IME" : "");
         str.add((flags & FLAG_IN_STASHED_LAUNCHER_STATE) != 0
                 ? "FLAG_IN_STASHED_LAUNCHER_STATE" : "");
+        str.add((flags & FLAG_STASHED_IN_APP_ALL_APPS) != 0 ? "FLAG_STASHED_IN_APP_ALL_APPS" : "");
         return str.toString();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 8b6831b..153ed14 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -306,7 +306,7 @@
         }
 
         public View.OnClickListener getAllAppsButtonClickListener() {
-            return v -> mControllers.taskbarAllAppsViewController.show();
+            return v -> mControllers.taskbarAllAppsController.show();
         }
 
         public View.OnLongClickListener getIconOnLongClickListener() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsContainerView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
similarity index 83%
rename from quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsContainerView.java
rename to quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
index d6eb716..b36b9f1 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsContainerView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContainerView.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.taskbar;
+package com.android.launcher3.taskbar.allapps;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -29,10 +29,7 @@
 import com.android.launcher3.allapps.search.SearchAdapterProvider;
 
 /** All apps container accessible from taskbar. */
-public class TaskbarAllAppsContainerView extends BaseAllAppsContainerView<TaskbarActivityContext> {
-    public TaskbarAllAppsContainerView(Context context) {
-        this(context, null);
-    }
+public class TaskbarAllAppsContainerView extends BaseAllAppsContainerView<TaskbarAllAppsContext> {
 
     public TaskbarAllAppsContainerView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -44,8 +41,8 @@
 
     @Override
     protected SearchAdapterProvider<?> createMainAdapterProvider() {
-        // Task bar all apps does not yet support search, so this implementation is minimal.
-        return new SearchAdapterProvider<TaskbarActivityContext>(mActivityContext) {
+        // Taskbar all apps does not yet support search, so this implementation is minimal.
+        return new SearchAdapterProvider<TaskbarAllAppsContext>(mActivityContext) {
             @Override
             public boolean launchHighlightedItem() {
                 return false;
@@ -79,8 +76,7 @@
 
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-        // TODO(b/204696617): Switch to status bar insets once they work.
-        setInsets(insets.getInsets(WindowInsets.Type.tappableElement()).toRect());
+        setInsets(insets.getInsets(WindowInsets.Type.systemBars()).toRect());
         return super.onApplyWindowInsets(insets);
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
new file mode 100644
index 0000000..4245119
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsContext.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 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.taskbar.allapps;
+
+import static android.view.KeyEvent.ACTION_UP;
+import static android.view.KeyEvent.KEYCODE_BACK;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.content.Context;
+import android.view.KeyEvent;
+import android.view.View;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.dot.DotInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.taskbar.BaseTaskbarContext;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarDragController;
+import com.android.launcher3.taskbar.TaskbarStashController;
+import com.android.launcher3.util.OnboardingPrefs;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+
+/**
+ * Window context for the taskbar all apps overlay.
+ * <p>
+ * All apps has its own window and needs a window context. Some properties are delegated to the
+ * {@link TaskbarActivityContext} such as {@link DeviceProfile} and {@link PopupDataProvider}.
+ */
+class TaskbarAllAppsContext extends BaseTaskbarContext {
+    private final TaskbarActivityContext mTaskbarContext;
+    private final OnboardingPrefs<TaskbarAllAppsContext> mOnboardingPrefs;
+
+    private final TaskbarAllAppsViewController mAllAppsViewController;
+    private final TaskbarDragController mDragController;
+    private final TaskbarAllAppsDragLayer mDragLayer;
+    private final TaskbarAllAppsContainerView mAppsView;
+
+    TaskbarAllAppsContext(
+            TaskbarActivityContext taskbarContext,
+            TaskbarAllAppsController windowController,
+            TaskbarStashController taskbarStashController) {
+        super(taskbarContext.createWindowContext(TYPE_APPLICATION_OVERLAY, null));
+        mTaskbarContext = taskbarContext;
+        mDeviceProfile = taskbarContext.getDeviceProfile();
+        mDragController = new TaskbarDragController(this);
+        mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this));
+
+        mDragLayer = new TaskbarAllAppsDragLayer(this);
+        TaskbarAllAppsSlideInView slideInView = (TaskbarAllAppsSlideInView) mLayoutInflater.inflate(
+                R.layout.taskbar_all_apps, mDragLayer, false);
+        mAllAppsViewController = new TaskbarAllAppsViewController(
+                this,
+                slideInView,
+                windowController,
+                taskbarStashController);
+        mAppsView = slideInView.getAppsView();
+    }
+
+    TaskbarAllAppsViewController getAllAppsViewController() {
+        return mAllAppsViewController;
+    }
+
+    @Override
+    public TaskbarDragController getDragController() {
+        return mDragController;
+    }
+
+    @Override
+    public TaskbarAllAppsDragLayer getDragLayer() {
+        return mDragLayer;
+    }
+
+    @Override
+    public TaskbarAllAppsContainerView getAppsView() {
+        return mAppsView;
+    }
+
+    @Override
+    public OnboardingPrefs<TaskbarAllAppsContext> getOnboardingPrefs() {
+        return mOnboardingPrefs;
+    }
+
+    @Override
+    public boolean isBindingItems() {
+        return mTaskbarContext.isBindingItems();
+    }
+
+    @Override
+    public View.OnClickListener getItemOnClickListener() {
+        return mTaskbarContext.getItemOnClickListener();
+    }
+
+    @Override
+    public PopupDataProvider getPopupDataProvider() {
+        return mTaskbarContext.getPopupDataProvider();
+    }
+
+    @Override
+    public DotInfo getDotInfoForItem(ItemInfo info) {
+        return mTaskbarContext.getDotInfoForItem(info);
+    }
+
+    @Override
+    public void updateDeviceProfile(DeviceProfile dp) {
+        mDeviceProfile = dp;
+        dispatchDeviceProfileChanged();
+    }
+
+    @Override
+    public void onDragStart() {}
+
+    @Override
+    public void onPopupVisibilityChanged(boolean isVisible) {}
+
+    /** Root drag layer for this context. */
+    private static class TaskbarAllAppsDragLayer extends BaseDragLayer<TaskbarAllAppsContext> {
+
+        private TaskbarAllAppsDragLayer(Context context) {
+            super(context, null, 1);
+            setClipChildren(false);
+            recreateControllers();
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+            mActivity.mAllAppsViewController.show();
+        }
+
+        @Override
+        public void recreateControllers() {
+            mControllers = new TouchController[]{mActivity.mDragController};
+        }
+
+        @Override
+        public boolean dispatchKeyEvent(KeyEvent event) {
+            if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
+                AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
+                if (topView != null && topView.onBackPressed()) {
+                    return true;
+                }
+            }
+            return super.dispatchKeyEvent(event);
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
new file mode 100644
index 0000000..9302452
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsController.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 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.taskbar.allapps;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
+import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.taskbar.TaskbarActivityContext;
+import com.android.launcher3.taskbar.TaskbarControllers;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Handles the all apps overlay window initialization, updates, and its data.
+ * <p>
+ * All apps is in an application overlay window instead of taskbar's navigation bar panel window,
+ * because a navigation bar panel is higher than UI components that all apps should be below such as
+ * the notification tray.
+ * <p>
+ * The all apps window is created and destroyed upon opening and closing all apps, respectively.
+ * Application data may be bound while the window does not exist, so this controller will store
+ * the models for the next all apps session.
+ */
+public final class TaskbarAllAppsController implements OnDeviceProfileChangeListener {
+
+    private static final String WINDOW_TITLE = "Taskbar All Apps";
+
+    private final TaskbarActivityContext mTaskbarContext;
+    private final TaskbarAllAppsProxyView mProxyView;
+    private final LayoutParams mLayoutParams;
+
+    private TaskbarControllers mControllers;
+    /** Window context for all apps if it is open. */
+    private @Nullable TaskbarAllAppsContext mAllAppsContext;
+
+    // Application data models.
+    private AppInfo[] mApps;
+    private int mAppsModelFlags;
+    private List<ItemInfo> mPredictedApps;
+
+    public TaskbarAllAppsController(TaskbarActivityContext context) {
+        mTaskbarContext = context;
+        mProxyView = new TaskbarAllAppsProxyView(mTaskbarContext);
+        mLayoutParams = createLayoutParams();
+    }
+
+    /** Initialize the controller. */
+    public void init(TaskbarControllers controllers) {
+        if (FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
+            mControllers = controllers;
+        }
+    }
+
+    /** Updates the current {@link AppInfo} instances. */
+    public void setApps(AppInfo[] apps, int flags) {
+        if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
+            return;
+        }
+
+        mApps = apps;
+        mAppsModelFlags = flags;
+        if (mAllAppsContext != null) {
+            mAllAppsContext.getAppsView().getAppsStore().setApps(mApps, mAppsModelFlags);
+        }
+    }
+
+    /** Updates the current predictions. */
+    public void setPredictedApps(List<ItemInfo> predictedApps) {
+        if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
+            return;
+        }
+
+        mPredictedApps = predictedApps;
+        if (mAllAppsContext != null) {
+            mAllAppsContext.getAppsView().getFloatingHeaderView()
+                    .findFixedRowByType(PredictionRowView.class)
+                    .setPredictedApps(mPredictedApps);
+        }
+    }
+
+    /** Opens the {@link TaskbarAllAppsContainerView} in a new window. */
+    public void show() {
+        if (mProxyView.isOpen()) {
+            return;
+        }
+        mProxyView.show();
+
+        mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext,
+                this,
+                mControllers.taskbarStashController);
+        mAllAppsContext.getDragController().init(mControllers);
+        mTaskbarContext.addOnDeviceProfileChangeListener(this);
+        Optional.ofNullable(mAllAppsContext.getSystemService(WindowManager.class))
+                .ifPresent(m -> m.addView(mAllAppsContext.getDragLayer(), mLayoutParams));
+
+        mAllAppsContext.getAppsView().getAppsStore().setApps(mApps, mAppsModelFlags);
+        mAllAppsContext.getAppsView().getFloatingHeaderView()
+                .findFixedRowByType(PredictionRowView.class)
+                .setPredictedApps(mPredictedApps);
+    }
+
+    /**
+     * Removes the all apps window from the hierarchy.
+     * <p>
+     * This method should be called after an exit animation finishes, if applicable.
+     */
+    void closeWindow() {
+        mProxyView.close(false);
+        mTaskbarContext.removeOnDeviceProfileChangeListener(this);
+        Optional.ofNullable(mAllAppsContext)
+                .map(c -> c.getSystemService(WindowManager.class))
+                .ifPresent(m -> m.removeView(mAllAppsContext.getDragLayer()));
+        mAllAppsContext = null;
+    }
+
+    private LayoutParams createLayoutParams() {
+        LayoutParams layoutParams = new LayoutParams(
+                TYPE_APPLICATION_OVERLAY,
+                0,
+                PixelFormat.TRANSLUCENT);
+        layoutParams.setTitle(WINDOW_TITLE);
+        layoutParams.gravity = Gravity.BOTTOM;
+        layoutParams.packageName = mTaskbarContext.getPackageName();
+        layoutParams.setFitInsetsTypes(0); // Handled by container view.
+        layoutParams.setSystemApplicationOverlay(true);
+        return layoutParams;
+    }
+
+    @Override
+    public void onDeviceProfileChanged(DeviceProfile dp) {
+        Optional.ofNullable(mAllAppsContext).ifPresent(c -> c.updateDeviceProfile(dp));
+    }
+
+    /**
+     * Proxy view connecting taskbar drag layer to the all apps window.
+     * <p>
+     * The all apps view is in a separate window and has its own drag layer, but this proxy lets it
+     * behave as though its in the taskbar drag layer. For instance, when the taskbar closes all
+     * {@link AbstractFloatingView} instances, the all apps window will also close.
+     */
+    private class TaskbarAllAppsProxyView extends AbstractFloatingView {
+
+        private TaskbarAllAppsProxyView(Context context) {
+            super(context, null);
+        }
+
+        private void show() {
+            mIsOpen = true;
+            mTaskbarContext.getDragLayer().addView(this);
+        }
+
+        @Override
+        protected void handleClose(boolean animate) {
+            mTaskbarContext.getDragLayer().removeView(this);
+            Optional.ofNullable(mAllAppsContext)
+                    .map(TaskbarAllAppsContext::getAllAppsViewController)
+                    .ifPresent(v -> v.close(animate));
+        }
+
+        @Override
+        protected boolean isOfType(int type) {
+            return (type & TYPE_TASKBAR_ALL_APPS) != 0;
+        }
+
+        @Override
+        public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+            return false;
+        }
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsSlideInView.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
similarity index 61%
rename from quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsSlideInView.java
rename to quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
index 63690c4..02aa3f2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAllAppsSlideInView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsSlideInView.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.taskbar;
+package com.android.launcher3.taskbar.allapps;
 
 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
 
@@ -23,18 +23,22 @@
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 
+import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Insettable;
 import com.android.launcher3.R;
 import com.android.launcher3.views.AbstractSlideInView;
 
-/** Wrapper for taskbar all apps with slide-in behavior. */
-public class TaskbarAllAppsSlideInView extends
-        AbstractSlideInView<TaskbarActivityContext> implements Insettable {
+import java.util.Optional;
 
-    private static final int DEFAULT_OPEN_DURATION = 500;
-    private static final int DEFAULT_CLOSE_DURATION = 200;
+/** Wrapper for taskbar all apps with slide-in behavior. */
+public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarAllAppsContext>
+        implements Insettable, DeviceProfile.OnDeviceProfileChangeListener {
+    static final int DEFAULT_OPEN_DURATION = 500;
+    static final int DEFAULT_CLOSE_DURATION = 200;
 
     private TaskbarAllAppsContainerView mAppsView;
+    private OnCloseListener mOnCloseBeginListener;
+    private float mShiftRange;
 
     public TaskbarAllAppsSlideInView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
@@ -46,7 +50,7 @@
     }
 
     /** Opens the all apps view. */
-    public void show() {
+    void show() {
         if (mIsOpen || mOpenCloseAnimator.isRunning()) {
             return;
         }
@@ -60,12 +64,18 @@
     }
 
     /** The apps container inside this view. */
-    public TaskbarAllAppsContainerView getAppsView() {
+    TaskbarAllAppsContainerView getAppsView() {
         return mAppsView;
     }
 
+    /** Callback invoked when the view is beginning to close (e.g. close animation is started). */
+    void setOnCloseBeginListener(OnCloseListener onCloseBeginListener) {
+        mOnCloseBeginListener = onCloseBeginListener;
+    }
+
     @Override
     protected void handleClose(boolean animate) {
+        Optional.ofNullable(mOnCloseBeginListener).ifPresent(OnCloseListener::onSlideInViewClosed);
         handleClose(animate, DEFAULT_CLOSE_DURATION);
     }
 
@@ -79,6 +89,17 @@
         super.onFinishInflate();
         mAppsView = findViewById(R.id.apps_view);
         mContent = mAppsView;
+
+        DeviceProfile dp = mActivityContext.getDeviceProfile();
+        setShiftRange(dp.allAppsShiftRange);
+
+        mActivityContext.addOnDeviceProfileChangeListener(this);
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        setTranslationShift(mTranslationShift);
     }
 
     @Override
@@ -98,4 +119,24 @@
     public void setInsets(Rect insets) {
         mAppsView.setInsets(insets);
     }
+
+    @Override
+    public void onDeviceProfileChanged(DeviceProfile dp) {
+        setShiftRange(dp.allAppsShiftRange);
+        setTranslationShift(TRANSLATION_SHIFT_OPENED);
+    }
+
+    private void setShiftRange(float shiftRange) {
+        mShiftRange = shiftRange;
+    }
+
+    @Override
+    protected float getShiftRange() {
+        return mShiftRange;
+    }
+
+    @Override
+    protected boolean isEventOverContent(MotionEvent ev) {
+        return getPopupContainer().isEventOverView(mAppsView.getVisibleContainerView(), ev);
+    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
new file mode 100644
index 0000000..c1abaac
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/allapps/TaskbarAllAppsViewController.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.taskbar.allapps;
+
+import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_IN_APP_ALL_APPS;
+import static com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView.DEFAULT_CLOSE_DURATION;
+import static com.android.launcher3.taskbar.allapps.TaskbarAllAppsSlideInView.DEFAULT_OPEN_DURATION;
+import static com.android.launcher3.util.OnboardingPrefs.ALL_APPS_VISITED_COUNT;
+
+import com.android.launcher3.appprediction.AppsDividerView;
+import com.android.launcher3.appprediction.PredictionRowView;
+import com.android.launcher3.taskbar.TaskbarStashController;
+
+/**
+ * Handles the {@link TaskbarAllAppsContainerView} behavior and synchronizes its transitions with
+ * taskbar stashing.
+ */
+final class TaskbarAllAppsViewController {
+
+    private final TaskbarAllAppsContext mContext;
+    private final TaskbarAllAppsSlideInView mSlideInView;
+    private final TaskbarAllAppsContainerView mAppsView;
+    private final TaskbarStashController mTaskbarStashController;
+
+    TaskbarAllAppsViewController(
+            TaskbarAllAppsContext context,
+            TaskbarAllAppsSlideInView slideInView,
+            TaskbarAllAppsController windowController,
+            TaskbarStashController taskbarStashController) {
+
+        mContext = context;
+        mSlideInView = slideInView;
+        mAppsView = mSlideInView.getAppsView();
+        mTaskbarStashController = taskbarStashController;
+
+        setUpIconLongClick();
+        setUpAppDivider();
+        setUpTaskbarStashing();
+        mSlideInView.addOnCloseListener(windowController::closeWindow);
+    }
+
+    /** Starts the {@link TaskbarAllAppsSlideInView} enter transition. */
+    void show() {
+        mSlideInView.show();
+    }
+
+    /** Closes the {@link TaskbarAllAppsSlideInView}. */
+    void close(boolean animate) {
+        mSlideInView.close(animate);
+    }
+
+    private void setUpIconLongClick() {
+        mAppsView.setOnIconLongClickListener(
+                mContext.getDragController()::startDragOnLongClick);
+        mAppsView.getFloatingHeaderView()
+                .findFixedRowByType(PredictionRowView.class)
+                .setOnIconLongClickListener(
+                        mContext.getDragController()::startDragOnLongClick);
+    }
+
+    private void setUpAppDivider() {
+        mAppsView.getFloatingHeaderView()
+                .findFixedRowByType(AppsDividerView.class)
+                .setShowAllAppsLabel(!mContext.getOnboardingPrefs().hasReachedMaxCount(
+                        ALL_APPS_VISITED_COUNT));
+        mContext.getOnboardingPrefs().incrementEventCount(ALL_APPS_VISITED_COUNT);
+    }
+
+    private void setUpTaskbarStashing() {
+        mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_APP_ALL_APPS, true);
+        mTaskbarStashController.applyState(DEFAULT_OPEN_DURATION);
+        mSlideInView.setOnCloseBeginListener(() -> {
+            mTaskbarStashController.updateStateForFlag(
+                    FLAG_STASHED_IN_APP_ALL_APPS, false);
+            mTaskbarStashController.applyState(DEFAULT_CLOSE_DURATION);
+        });
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
index 26d8f30..4583fc3 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -53,18 +53,20 @@
 
     @Override
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
-        ScaleAndTranslation scaleAndTranslation = LauncherState.OVERVIEW
-                .getWorkspaceScaleAndTranslation(launcher);
-        scaleAndTranslation.scale = 1;
+        ScaleAndTranslation scaleAndTranslation =
+                new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
+        if (launcher.getDeviceProfile().isTablet) {
+            scaleAndTranslation.scale = 0.97f;
+        } else {
+            ScaleAndTranslation overviewScaleAndTranslation = LauncherState.OVERVIEW
+                    .getWorkspaceScaleAndTranslation(launcher);
+            scaleAndTranslation.translationX = overviewScaleAndTranslation.translationX;
+            scaleAndTranslation.translationY = overviewScaleAndTranslation.translationY;
+        }
         return scaleAndTranslation;
     }
 
     @Override
-    public boolean isTaskbarStashed(Launcher launcher) {
-        return true;
-    }
-
-    @Override
     protected float getDepthUnchecked(Context context) {
         // The scrim fades in at approximately 50% of the swipe gesture.
         // This means that the depth should be greater than 1, in order to fully zoom out.
@@ -86,7 +88,7 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return ALL_APPS_CONTENT;
+        return ALL_APPS_CONTENT | HOTSEAT_ICONS;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
index 75cf5cb..bd7e5de 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickstepAtomicAnimationFactory.java
@@ -22,7 +22,7 @@
 import static com.android.launcher3.LauncherState.HINT_STATE_TWO_BUTTON;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.OVERVIEW;
-import static com.android.launcher3.WorkspaceStateTransitionAnimation.getSpringScaleAnimator;
+import static com.android.launcher3.WorkspaceStateTransitionAnimation.getWorkspaceSpringScaleAnimator;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
@@ -172,7 +172,8 @@
         } else if (fromState == HINT_STATE && toState == NORMAL) {
             config.setInterpolator(ANIM_DEPTH, DEACCEL_3);
             if (mHintToNormalDuration == -1) {
-                ValueAnimator va = getSpringScaleAnimator(mActivity, mActivity.getWorkspace(),
+                ValueAnimator va = getWorkspaceSpringScaleAnimator(mActivity,
+                        mActivity.getWorkspace(),
                         toState.getWorkspaceScaleAndTranslation(mActivity).scale);
                 mHintToNormalDuration = (int) va.getDuration();
             }
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index 099915a..f93917f 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -23,8 +23,13 @@
 import static com.android.launcher3.LauncherState.OVERVIEW;
 import static com.android.launcher3.anim.Interpolators.ACCEL;
 import static com.android.launcher3.anim.Interpolators.DEACCEL;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.LINEAR_TELEPORT;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 
 import android.view.MotionEvent;
 
@@ -126,23 +131,31 @@
 
     private StateAnimationConfig getNormalToAllAppsAnimation() {
         StateAnimationConfig builder = new StateAnimationConfig();
-        builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(ACCEL,
-                ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD,
-                ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD));
+        boolean isTablet = mLauncher.getDeviceProfile().isTablet;
+        builder.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
+                ? INSTANT
+                : Interpolators.clampToProgress(ACCEL,
+                        ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD,
+                        ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD));
         builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(ACCEL,
                 ALL_APPS_SCRIM_VISIBLE_THRESHOLD,
                 ALL_APPS_SCRIM_OPAQUE_THRESHOLD));
+        builder.setInterpolator(ANIM_VERTICAL_PROGRESS, isTablet ? LINEAR_TELEPORT : LINEAR);
         return builder;
     }
 
     private StateAnimationConfig getAllAppsToNormalAnimation() {
         StateAnimationConfig builder = new StateAnimationConfig();
-        builder.setInterpolator(ANIM_ALL_APPS_FADE, Interpolators.clampToProgress(DEACCEL,
-                1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
-                1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
+        boolean isTablet = mLauncher.getDeviceProfile().isTablet;
+        builder.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
+                ? FINAL_FRAME
+                : Interpolators.clampToProgress(DEACCEL,
+                        1 - ALL_APPS_CONTENT_FADE_MAX_CLAMPING_THRESHOLD,
+                        1 - ALL_APPS_CONTENT_FADE_MIN_CLAMPING_THRESHOLD));
         builder.setInterpolator(ANIM_SCRIM_FADE, Interpolators.clampToProgress(DEACCEL,
                 1 - ALL_APPS_SCRIM_OPAQUE_THRESHOLD,
                 1 - ALL_APPS_SCRIM_VISIBLE_THRESHOLD));
+        builder.setInterpolator(ANIM_VERTICAL_PROGRESS, isTablet ? LINEAR_TELEPORT : LINEAR);
         return builder;
     }
 
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index 678372c..5521020 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -23,11 +23,11 @@
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.util.Log;
+import android.view.ThreadedRenderer;
 
 import com.android.launcher3.BuildConfig;
 import com.android.launcher3.MainProcessInitializer;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
-import com.android.systemui.shared.system.ThreadedRendererCompat;
 
 @SuppressWarnings("unused")
 @TargetApi(Build.VERSION_CODES.R)
@@ -60,8 +60,8 @@
         super.init(context);
 
         // Elevate GPU priority for Quickstep and Remote animations.
-        ThreadedRendererCompat.setContextPriority(
-                ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
+        ThreadedRenderer.setContextPriority(
+                ThreadedRenderer.EGL_CONTEXT_PRIORITY_HIGH_IMG);
 
         // Enable binder tracing on system server for calls originating from Launcher
         try {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 4d79202..ad57683 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -32,7 +32,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_GLOBAL_ACTIONS_SHOWING;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP;
@@ -453,8 +453,8 @@
     /**
      * @return whether the global actions dialog is showing
      */
-    public boolean isGlobalActionsShowing() {
-        return (mSystemUiStateFlags & SYSUI_STATE_GLOBAL_ACTIONS_SHOWING) != 0;
+    public boolean isSystemUiDialogShowing() {
+        return (mSystemUiStateFlags & SYSUI_STATE_DIALOG_SHOWING) != 0;
     }
 
     /**
@@ -550,15 +550,13 @@
 
     /**
      * @param ev An ACTION_DOWN motion event
-     * @param task Info for the currently running task
      * @return whether the given motion event can trigger the assistant over the current task.
      */
-    public boolean canTriggerAssistantAction(MotionEvent ev, ActivityManager.RunningTaskInfo task) {
+    public boolean canTriggerAssistantAction(MotionEvent ev) {
         return mAssistantAvailable
                 && !QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags)
                 && mRotationTouchHelper.touchInAssistantRegion(ev)
-                && !isLockToAppActive()
-                && !isGestureBlockedActivity(task);
+                && !isLockToAppActive();
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/TaskIconCache.java b/quickstep/src/com/android/quickstep/TaskIconCache.java
index 6c71da9..300f085 100644
--- a/quickstep/src/com/android/quickstep/TaskIconCache.java
+++ b/quickstep/src/com/android/quickstep/TaskIconCache.java
@@ -18,6 +18,7 @@
 import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
 
+import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -48,7 +49,6 @@
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.system.PackageManagerWrapper;
-import com.android.systemui.shared.system.TaskDescriptionCompat;
 
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -154,7 +154,7 @@
 
         // Load icon
         // TODO: Load icon resource (b/143363444)
-        Bitmap icon = TaskDescriptionCompat.getIcon(desc, key.userId);
+        Bitmap icon = getIcon(desc, key.userId);
         if (icon != null) {
             entry.icon = getBitmapInfo(
                     new BitmapDrawable(mContext.getResources(), icon),
@@ -193,6 +193,14 @@
         return entry;
     }
 
+    private Bitmap getIcon(ActivityManager.TaskDescription desc, int userId) {
+        if (desc.getInMemoryIcon() != null) {
+            return desc.getInMemoryIcon();
+        }
+        return ActivityManager.TaskDescription.loadTaskDescriptionIcon(
+                desc.getIconFilename(), userId);
+    }
+
     private String getBadgedContentDescription(ActivityInfo info, int userId, TaskDescription td) {
         PackageManager pm = mContext.getPackageManager();
         String taskLabel = td == null ? null : Utilities.trim(td.getLabel());
diff --git a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
index ecb30e5..e731b79 100644
--- a/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
+++ b/quickstep/src/com/android/quickstep/TaskShortcutFactory.java
@@ -258,9 +258,8 @@
 
         @Override
         protected ActivityOptions makeLaunchOptions(Activity activity) {
-            final ActivityCompat act = new ActivityCompat(activity);
             final int navBarPosition = WindowManagerWrapper.getInstance().getNavBarPosition(
-                    act.getDisplayId());
+                    activity.getDisplayId());
             if (navBarPosition == WindowManagerWrapper.NAV_BAR_POS_INVALID) {
                 return null;
             }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 0c34ead..2ed75b5 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -37,7 +37,6 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TRACING_ENABLED;
 
 import android.annotation.TargetApi;
-import android.app.ActivityManager;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.app.Service;
@@ -575,26 +574,14 @@
 
                 ActiveGestureLog.INSTANCE.addLog("setInputConsumer: " + mConsumer.getName());
                 mUncheckedConsumer = mConsumer;
-            } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()) {
+            } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()
+                    && mDeviceState.canTriggerAssistantAction(event)) {
                 mGestureState = createGestureState(mGestureState);
-                ActivityManager.RunningTaskInfo runningTask = mGestureState.getRunningTask();
-                if (mDeviceState.canTriggerAssistantAction(event, runningTask)) {
-                    // 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,
-                            mGestureState,
-                            InputConsumer.NO_OP, mInputMonitorCompat,
-                            mDeviceState,
-                            event);
-                } else if (mDeviceState.canTriggerOneHandedAction(event)) {
-                    // Consume gesture event for triggering one handed feature.
-                    mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
-                        InputConsumer.NO_OP, mInputMonitorCompat);
-                } else {
-                    mUncheckedConsumer = InputConsumer.NO_OP;
-                }
+                // 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 = tryCreateAssistantInputConsumer(
+                        InputConsumer.NO_OP, mGestureState, event);
             } else if (mDeviceState.canTriggerOneHandedAction(event)) {
                 // Consume gesture event for triggering one handed feature.
                 mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
@@ -641,6 +628,14 @@
         ProtoTracer.INSTANCE.get(this).scheduleFrameUpdate();
     }
 
+    private InputConsumer tryCreateAssistantInputConsumer(InputConsumer base,
+            GestureState gestureState, MotionEvent motionEvent) {
+        return mDeviceState.isGestureBlockedActivity(gestureState.getRunningTask())
+                ? base
+                : new AssistantInputConsumer(this, gestureState, base, mInputMonitorCompat,
+                        mDeviceState, motionEvent);
+    }
+
     public GestureState createGestureState(GestureState previousGestureState) {
         GestureState gestureState = new GestureState(mOverviewComponentObserver,
                 ActiveGestureLog.INSTANCE.generateAndSetLogId());
@@ -686,9 +681,8 @@
             handleOrientationSetup(base);
         }
         if (mDeviceState.isFullyGesturalNavMode()) {
-            if (mDeviceState.canTriggerAssistantAction(event, newGestureState.getRunningTask())) {
-                base = new AssistantInputConsumer(this, newGestureState, base, mInputMonitorCompat,
-                        mDeviceState, event);
+            if (mDeviceState.canTriggerAssistantAction(event)) {
+                base = tryCreateAssistantInputConsumer(base, newGestureState, event);
             }
 
             // If Taskbar is present, we listen for long press to unstash it.
@@ -701,7 +695,7 @@
 
             // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
             // instead of going all the way home when a swipe up is detected.
-            if (mDeviceState.isBubblesExpanded() || mDeviceState.isGlobalActionsShowing()) {
+            if (mDeviceState.isBubblesExpanded() || mDeviceState.isSystemUiDialogShowing()) {
                 base = new SysUiOverlayInputConsumer(
                         getBaseContext(), mDeviceState, mInputMonitorCompat);
             }
diff --git a/quickstep/src/com/android/quickstep/ViewUtils.java b/quickstep/src/com/android/quickstep/ViewUtils.java
index e290be8..ab26d15 100644
--- a/quickstep/src/com/android/quickstep/ViewUtils.java
+++ b/quickstep/src/com/android/quickstep/ViewUtils.java
@@ -21,10 +21,8 @@
 import android.view.ViewRootImpl;
 
 import com.android.launcher3.Utilities;
-import com.android.systemui.shared.system.ViewRootImplCompat;
 
 import java.util.function.BooleanSupplier;
-import java.util.function.LongConsumer;
 
 /**
  * Utility class for helpful methods related to {@link View} objects.
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
index 3f833c0..878f132 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/SysUiOverlayInputConsumer.java
@@ -15,9 +15,11 @@
  */
 package com.android.quickstep.inputconsumers;
 
+import android.app.ActivityManager;
 import android.content.Context;
-import android.content.Intent;
 import android.graphics.PointF;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.MotionEvent;
 
 import com.android.launcher3.testing.TestLogging;
@@ -36,6 +38,10 @@
  */
 public class SysUiOverlayInputConsumer implements InputConsumer,
         TriggerSwipeUpTouchTracker.OnSwipeUpListener {
+    private static final String TAG = "SysUiOverlayInputConsumer";
+
+    // Should match the values in PhoneWindowManager
+    private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav";
 
     private final Context mContext;
     private final InputMonitorCompat mInputMonitor;
@@ -76,7 +82,11 @@
     @Override
     public void onSwipeUp(boolean wasFling, PointF finalVelocity) {
         // Close system dialogs when a swipe up is detected.
-        mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+        try {
+            ActivityManager.getService().closeSystemDialogs(SYSTEM_DIALOG_REASON_GESTURE_NAV);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception calling closeSystemDialogs " + e.getMessage());
+        }
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
index 861ff96..143042f 100644
--- a/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/BaseUnfoldMoveFromCenterAnimator.java
@@ -36,6 +36,8 @@
     private final Map<ViewGroup, Boolean> mOriginalClipToPadding = new HashMap<>();
     private final Map<ViewGroup, Boolean> mOriginalClipChildren = new HashMap<>();
 
+    private boolean mAnimationInProgress = false;
+
     public BaseUnfoldMoveFromCenterAnimator(WindowManager windowManager) {
         mMoveFromCenterAnimation = new UnfoldMoveFromCenterAnimator(windowManager,
                 new LauncherViewsMoveFromCenterTranslationApplier());
@@ -44,6 +46,7 @@
     @CallSuper
     @Override
     public void onTransitionStarted() {
+        mAnimationInProgress = true;
         mMoveFromCenterAnimation.updateDisplayProperties();
         onPrepareViewsForAnimation();
         onTransitionProgress(0f);
@@ -58,7 +61,23 @@
     @CallSuper
     @Override
     public void onTransitionFinished() {
+        mAnimationInProgress = false;
         mMoveFromCenterAnimation.onTransitionFinished();
+        clearRegisteredViews();
+    }
+
+    /**
+     * Re-prepares views for animation. This is useful in case views are re-bound while the
+     * animation is in progress.
+     */
+    public void updateRegisteredViewsIfNeeded() {
+        if (mAnimationInProgress) {
+            clearRegisteredViews();
+            onPrepareViewsForAnimation();
+        }
+    }
+
+    private void clearRegisteredViews() {
         mMoveFromCenterAnimation.clearRegisteredViews();
 
         mOriginalClipChildren.clear();
diff --git a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
index 333df10..91ba909 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherUnfoldAnimationController.java
@@ -15,14 +15,14 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
+import static com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.Utilities.comp;
 
 import android.annotation.Nullable;
 import android.util.FloatProperty;
 import android.util.MathUtils;
-import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 
@@ -30,6 +30,7 @@
 
 import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
+import com.android.launcher3.Workspace;
 import com.android.launcher3.util.HorizontalInsettableView;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener;
@@ -44,17 +45,20 @@
     // Percentage of the width of the quick search bar that will be reduced
     // from the both sides of the bar when progress is 0
     private static final float MAX_WIDTH_INSET_FRACTION = 0.15f;
-    private static final FloatProperty<View> UNFOLD_SCALE_PROPERTY =
-            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_UNFOLD_ANIMATION);
+    private static final FloatProperty<Workspace> WORKSPACE_SCALE_PROPERTY =
+            WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_UNFOLD_ANIMATION);
+    private static final FloatProperty<Hotseat> HOTSEAT_SCALE_PROPERTY =
+            HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_UNFOLD_ANIMATION);
 
     private final Launcher mLauncher;
+    private final ScopedUnfoldTransitionProgressProvider mProgressProvider;
+    private final NaturalRotationUnfoldProgressProvider mNaturalOrientationProgressProvider;
+    private final UnfoldMoveFromCenterHotseatAnimator mUnfoldMoveFromCenterHotseatAnimator;
+    private final UnfoldMoveFromCenterWorkspaceAnimator mUnfoldMoveFromCenterWorkspaceAnimator;
 
     @Nullable
     private HorizontalInsettableView mQsbInsettable;
 
-    private final ScopedUnfoldTransitionProgressProvider mProgressProvider;
-    private final NaturalRotationUnfoldProgressProvider mNaturalOrientationProgressProvider;
-
     public LauncherUnfoldAnimationController(
             Launcher launcher,
             WindowManager windowManager,
@@ -62,21 +66,21 @@
         mLauncher = launcher;
         mProgressProvider = new ScopedUnfoldTransitionProgressProvider(
                 unfoldTransitionProgressProvider);
+        mUnfoldMoveFromCenterHotseatAnimator = new UnfoldMoveFromCenterHotseatAnimator(launcher,
+                windowManager);
+        mUnfoldMoveFromCenterWorkspaceAnimator = new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
+                windowManager);
         mNaturalOrientationProgressProvider = new NaturalRotationUnfoldProgressProvider(launcher,
                 WindowManagerGlobal.getWindowManagerService(), mProgressProvider);
         mNaturalOrientationProgressProvider.init();
 
         // Animated in all orientations
-        mProgressProvider.addCallback(new UnfoldMoveFromCenterWorkspaceAnimator(launcher,
-                windowManager));
-        mProgressProvider
-                .addCallback(new LauncherScaleAnimationListener());
+        mProgressProvider.addCallback(mUnfoldMoveFromCenterWorkspaceAnimator);
+        mProgressProvider.addCallback(new LauncherScaleAnimationListener());
 
         // Animated only in natural orientation
-        mNaturalOrientationProgressProvider
-                .addCallback(new QsbAnimationListener());
-        mNaturalOrientationProgressProvider
-                .addCallback(new UnfoldMoveFromCenterHotseatAnimator(launcher, windowManager));
+        mNaturalOrientationProgressProvider.addCallback(new QsbAnimationListener());
+        mNaturalOrientationProgressProvider.addCallback(mUnfoldMoveFromCenterHotseatAnimator);
     }
 
     /**
@@ -108,6 +112,12 @@
         mNaturalOrientationProgressProvider.destroy();
     }
 
+    /** Called when launcher finished binding its items. */
+    public void updateRegisteredViewsIfNeeded() {
+        mUnfoldMoveFromCenterHotseatAnimator.updateRegisteredViewsIfNeeded();
+        mUnfoldMoveFromCenterWorkspaceAnimator.updateRegisteredViewsIfNeeded();
+    }
+
     private class QsbAnimationListener implements TransitionProgressListener {
 
         @Override
@@ -147,8 +157,8 @@
         }
 
         private void setScale(float value) {
-            UNFOLD_SCALE_PROPERTY.setValue(mLauncher.getWorkspace(), value);
-            UNFOLD_SCALE_PROPERTY.setValue(mLauncher.getHotseat(), value);
+            WORKSPACE_SCALE_PROPERTY.setValue(mLauncher.getWorkspace(), value);
+            HOTSEAT_SCALE_PROPERTY.setValue(mLauncher.getHotseat(), value);
         }
     }
 }
diff --git a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
index 8659b68..5326d2b 100644
--- a/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
+++ b/quickstep/src/com/android/quickstep/util/WorkspaceRevealAnim.java
@@ -15,8 +15,9 @@
  */
 package com.android.quickstep.util;
 
+import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_REVEAL_ANIM;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
+import static com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
@@ -32,6 +33,7 @@
 import android.view.View;
 
 import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.Hotseat;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.R;
 import com.android.launcher3.Workspace;
@@ -51,8 +53,11 @@
 
     // Should be used for animations running alongside this WorkspaceRevealAnim.
     public static final int DURATION_MS = 350;
-    private static final FloatProperty<View> REVEAL_SCALE_PROPERTY =
-            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_REVEAL_ANIM);
+    private static final FloatProperty<Workspace> WORKSPACE_SCALE_PROPERTY =
+            WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_REVEAL_ANIM);
+
+    private static final FloatProperty<Hotseat> HOTSEAT_SCALE_PROPERTY =
+            HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_REVEAL_ANIM);
 
     private final float mScaleStart;
     private final AnimatorSet mAnimators = new AnimatorSet();
@@ -67,8 +72,8 @@
         workspace.setPivotToScaleWithSelf(launcher.getHotseat());
 
         // Add reveal animations.
-        addRevealAnimatorsForView(workspace);
-        addRevealAnimatorsForView(launcher.getHotseat());
+        addRevealAnimatorsForView(workspace, WORKSPACE_SCALE_PROPERTY);
+        addRevealAnimatorsForView(launcher.getHotseat(), HOTSEAT_SCALE_PROPERTY);
 
         // Add overview scrim animation.
         if (animateOverviewScrim) {
@@ -93,8 +98,8 @@
         mAnimators.setInterpolator(Interpolators.DECELERATED_EASE);
     }
 
-    private void addRevealAnimatorsForView(View v) {
-        ObjectAnimator scale = ObjectAnimator.ofFloat(v, REVEAL_SCALE_PROPERTY, mScaleStart, 1f);
+    private <T extends View>  void addRevealAnimatorsForView(T v, FloatProperty<T> scaleProperty) {
+        ObjectAnimator scale = ObjectAnimator.ofFloat(v, scaleProperty, mScaleStart, 1f);
         scale.setDuration(DURATION_MS);
         scale.setInterpolator(Interpolators.DECELERATED_EASE);
         mAnimators.play(scale);
@@ -107,7 +112,7 @@
         mAnimators.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
-                REVEAL_SCALE_PROPERTY.set(v, 1f);
+                scaleProperty.set(v, 1f);
                 v.setAlpha(1f);
             }
         });
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 4529217..6be2ce6 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -61,6 +61,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -205,6 +206,7 @@
 
     // b/143488140
     //@NavigationModeSwitch
+    @Ignore("b/218403080")
     @Test
     public void testOverview() {
         startAppFast(getAppPackageName());
diff --git a/res/color-night-v31/all_apps_button_color_2.xml b/res/color-night-v31/all_apps_button_color_2.xml
new file mode 100644
index 0000000..30b972f
--- /dev/null
+++ b/res/color-night-v31/all_apps_button_color_2.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:color="@android:color/system_accent2_50"
+        android:lStar="98" />
+</selector>
diff --git a/res/color-v31/all_apps_button_bg_color.xml b/res/color-v31/all_apps_button_bg_color.xml
new file mode 100644
index 0000000..3ad38bc
--- /dev/null
+++ b/res/color-v31/all_apps_button_bg_color.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:color="@android:color/system_neutral1_50"
+        android:lStar="98" />
+</selector>
diff --git a/res/color-v31/all_apps_button_color_1.xml b/res/color-v31/all_apps_button_color_1.xml
new file mode 100644
index 0000000..2d0895e
--- /dev/null
+++ b/res/color-v31/all_apps_button_color_1.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:color="@android:color/system_accent1_50"
+        android:lStar="40" />
+</selector>
diff --git a/res/color-v31/all_apps_button_color_2.xml b/res/color-v31/all_apps_button_color_2.xml
new file mode 100644
index 0000000..7674b43
--- /dev/null
+++ b/res/color-v31/all_apps_button_color_2.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:color="@android:color/system_accent2_50"
+        android:lStar="48" />
+</selector>
diff --git a/res/color-v31/all_apps_button_color_3.xml b/res/color-v31/all_apps_button_color_3.xml
new file mode 100644
index 0000000..17cb54f
--- /dev/null
+++ b/res/color-v31/all_apps_button_color_3.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:color="@android:color/system_accent1_50"
+        android:lStar="35" />
+</selector>
diff --git a/res/color-v31/all_apps_button_color_4.xml b/res/color-v31/all_apps_button_color_4.xml
new file mode 100644
index 0000000..a6150f1
--- /dev/null
+++ b/res/color-v31/all_apps_button_color_4.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" >
+    <item
+        android:color="@android:color/system_accent3_50"
+        android:lStar="48" />
+</selector>
diff --git a/res/drawable/bg_all_apps_bottom_sheet.xml b/res/drawable/bg_all_apps_bottom_sheet.xml
deleted file mode 100644
index dba2fee..0000000
--- a/res/drawable/bg_all_apps_bottom_sheet.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2022 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.
--->
-<ripple android:color="?android:attr/colorControlHighlight"
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
-    <item>
-        <shape android:shape="rectangle"
-            android:tint="?colorButtonNormal">
-            <corners
-                android:topLeftRadius="@dimen/dialogCornerRadius"
-                android:topRightRadius="@dimen/dialogCornerRadius"/>
-            <solid android:color="?attr/allAppsScrimColor" />
-        </shape>
-    </item>
-</ripple>
\ No newline at end of file
diff --git a/res/drawable/bg_rounded_corner_bottom_sheet.xml b/res/drawable/bg_rounded_corner_bottom_sheet.xml
index aa49bce..dfcd354 100644
--- a/res/drawable/bg_rounded_corner_bottom_sheet.xml
+++ b/res/drawable/bg_rounded_corner_bottom_sheet.xml
@@ -16,7 +16,7 @@
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle" >
-    <solid android:color="@color/surface" />
+    <solid android:color="?android:attr/colorBackground" />
     <corners
         android:topLeftRadius="@dimen/dialogCornerRadius"
         android:topRightRadius="@dimen/dialogCornerRadius" />
diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
new file mode 100644
index 0000000..c502178
--- /dev/null
+++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    android:shape="rectangle" >
+    <solid android:color="?androidprv:attr/colorSurfaceVariant"/>
+    <corners android:radius="@dimen/bottom_sheet_handle_corner_radius" />
+</shape>
diff --git a/res/drawable/ic_all_apps_button.xml b/res/drawable/ic_all_apps_button.xml
index 52b919b..5770d3c 100644
--- a/res/drawable/ic_all_apps_button.xml
+++ b/res/drawable/ic_all_apps_button.xml
@@ -18,26 +18,27 @@
     android:width="80dp"
     android:height="80dp"
     android:viewportWidth="80"
-    android:viewportHeight="80">
+    android:viewportHeight="80"
+    android:theme="@style/AllAppsTheme">
   <path
       android:pathData="M40,0.5L40,0.5c21.8,0 39.5,17.7 39.5,39.5l0,0c0,21.8 -17.7,39.5 -39.5,39.5l0,0C18.2,79.5 0.5,61.8 0.5,40l0,0C0.5,18.2 18.2,0.5 40,0.5z"
-      android:fillColor="#F7F9FA"/>
+      android:fillColor="?attr/allAppsButtonBgColor"/>
   <path
       android:pathData="M26.8,32.1m-5.3,0a5.3,5.3 0,1 1,10.6 0a5.3,5.3 0,1 1,-10.6 0"
-      android:fillColor="#00677E"/>
+      android:fillColor="?attr/allAppsButtonColor1"/>
   <path
       android:pathData="M26.8,47.9m-5.3,0a5.3,5.3 0,1 1,10.6 0a5.3,5.3 0,1 1,-10.6 0"
-      android:fillColor="#5F757E"/>
+      android:fillColor="?attr/allAppsButtonColor2"/>
   <path
       android:pathData="M40,32.1m-5.3,0a5.3,5.3 0,1 1,10.6 0a5.3,5.3 0,1 1,-10.6 0"
-      android:fillColor="#5F757E"/>
+      android:fillColor="?attr/allAppsButtonColor3"/>
   <path
       android:pathData="M40,47.9m-5.3,0a5.3,5.3 0,1 1,10.6 0a5.3,5.3 0,1 1,-10.6 0"
-      android:fillColor="#6C6F93"/>
+      android:fillColor="?attr/allAppsButtonColor2"/>
   <path
       android:pathData="M53.2,32.1m-5.3,0a5.3,5.3 0,1 1,10.6 0a5.3,5.3 0,1 1,-10.6 0"
-      android:fillColor="#005A6E"/>
+      android:fillColor="?attr/allAppsButtonColor4"/>
   <path
       android:pathData="M53.2,47.9m-5.3,0a5.3,5.3 0,1 1,10.6 0a5.3,5.3 0,1 1,-10.6 0"
-      android:fillColor="#5F757E"/>
+      android:fillColor="?attr/allAppsButtonColor2"/>
 </vector>
diff --git a/res/layout/all_apps_bottom_sheet_background.xml b/res/layout/all_apps_bottom_sheet_background.xml
index ad10d68..12b6b7b 100644
--- a/res/layout/all_apps_bottom_sheet_background.xml
+++ b/res/layout/all_apps_bottom_sheet_background.xml
@@ -17,19 +17,19 @@
     android:id="@+id/bottom_sheet_background"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:background="@drawable/bg_all_apps_bottom_sheet">
+    android:background="@drawable/bg_rounded_corner_bottom_sheet">
 
     <View
         android:id="@+id/bottom_sheet_handle_area"
         android:layout_width="match_parent"
-        android:layout_height="34dp" />
+        android:layout_height="36dp" />
 
     <View
         android:id="@+id/bottom_sheet_handle"
-        android:layout_width="48dp"
-        android:layout_height="2dp"
+        android:layout_width="@dimen/bottom_sheet_handle_width"
+        android:layout_height="@dimen/bottom_sheet_handle_height"
         android:layout_gravity="center_horizontal"
-        android:layout_marginTop="16dp"
-        android:layout_marginBottom="16dp"
-        android:background="?android:attr/textColorSecondary" />
+        android:layout_marginTop="@dimen/bottom_sheet_handle_margin"
+        android:layout_marginBottom="@dimen/bottom_sheet_handle_margin"
+        android:background="@drawable/bg_rounded_corner_bottom_sheet_handle" />
 </FrameLayout>
diff --git a/res/layout/widgets_bottom_sheet_content.xml b/res/layout/widgets_bottom_sheet_content.xml
index 1a2cfc6..a5f72ef 100644
--- a/res/layout/widgets_bottom_sheet_content.xml
+++ b/res/layout/widgets_bottom_sheet_content.xml
@@ -19,16 +19,16 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="@drawable/bg_rounded_corner_bottom_sheet"
-        android:paddingTop="16dp"
+        android:paddingTop="@dimen/bottom_sheet_handle_margin"
         android:orientation="vertical">
         <View
             android:id="@+id/collapse_handle"
-            android:layout_width="48dp"
-            android:layout_height="2dp"
+            android:layout_width="@dimen/bottom_sheet_handle_width"
+            android:layout_height="@dimen/bottom_sheet_handle_height"
             android:layout_gravity="center_horizontal"
-            android:layout_marginBottom="16dp"
+            android:layout_marginBottom="@dimen/bottom_sheet_handle_margin"
             android:visibility="gone"
-            android:background="?android:attr/textColorSecondary"/>
+            android:background="@drawable/bg_rounded_corner_bottom_sheet_handle"/>
         <TextView
             style="@style/TextHeadline"
             android:id="@+id/title"
diff --git a/res/layout/widgets_full_sheet.xml b/res/layout/widgets_full_sheet.xml
index 309dc42..505ecb1 100644
--- a/res/layout/widgets_full_sheet.xml
+++ b/res/layout/widgets_full_sheet.xml
@@ -31,11 +31,11 @@
 
         <View
             android:id="@+id/collapse_handle"
-            android:layout_width="48dp"
-            android:layout_height="2dp"
-            android:layout_marginTop="16dp"
+            android:layout_width="@dimen/bottom_sheet_handle_width"
+            android:layout_height="@dimen/bottom_sheet_handle_height"
+            android:layout_marginTop="@dimen/bottom_sheet_handle_margin"
             android:layout_centerHorizontal="true"
-            android:background="?android:attr/textColorSecondary"/>
+            android:background="@drawable/bg_rounded_corner_bottom_sheet_handle"/>
 
         <TextView
             android:id="@+id/no_widgets_text"
diff --git a/res/values-night-v31/colors.xml b/res/values-night-v31/colors.xml
index 2c1bc90..eefe8c5 100644
--- a/res/values-night-v31/colors.xml
+++ b/res/values-night-v31/colors.xml
@@ -24,4 +24,9 @@
     <color name="home_settings_thumb_off_color">@android:color/system_neutral2_300</color>
     <color name="home_settings_track_on_color">@android:color/system_accent2_700</color>
     <color name="home_settings_track_off_color">@android:color/system_neutral1_700</color>
+
+    <color name="all_apps_button_bg_color">@android:color/system_neutral1_800</color>
+    <color name="all_apps_button_color_1">@android:color/system_accent1_300</color>
+    <color name="all_apps_button_color_3">@android:color/system_accent1_100</color>
+    <color name="all_apps_button_color_4">@android:color/system_accent2_100</color>
 </resources>
\ No newline at end of file
diff --git a/res/values-night/colors.xml b/res/values-night/colors.xml
new file mode 100644
index 0000000..ce272ce
--- /dev/null
+++ b/res/values-night/colors.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+/*
+* Copyright (C) 2022 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.
+*/
+-->
+
+<resources>
+    <color name="all_apps_button_bg_color">#2E3132</color>
+    <color name="all_apps_button_color_1">#33B9DB</color>
+    <color name="all_apps_button_color_2">#EFFBFF</color>
+    <color name="all_apps_button_color_3">#B1EBFF</color>
+    <color name="all_apps_button_color_4">#DEE0FF</color>
+</resources>
\ No newline at end of file
diff --git a/res/values-sw600dp/dimens.xml b/res/values-sw600dp/dimens.xml
index 3dde3f6..92f806e 100644
--- a/res/values-sw600dp/dimens.xml
+++ b/res/values-sw600dp/dimens.xml
@@ -22,7 +22,8 @@
     <dimen name="widget_list_horizontal_margin">32dp</dimen>
 
 <!-- AllApps -->
-    <dimen name="all_apps_bottom_sheet_horizontal_padding">32dp</dimen>
+    <dimen name="all_apps_search_bar_content_overlap">0dp</dimen>
+    <dimen name="all_apps_bottom_sheet_horizontal_padding">46dp</dimen>
 
 <!-- Fast scroll -->
     <dimen name="fastscroll_popup_width">75dp</dimen>
diff --git a/res/values-sw720dp-land/dimens.xml b/res/values-sw720dp-land/dimens.xml
new file mode 100644
index 0000000..a9e0fb8
--- /dev/null
+++ b/res/values-sw720dp-land/dimens.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<resources>
+<!-- AllApps -->
+    <dimen name="all_apps_top_padding">0dp</dimen>
+</resources>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 9d7941f..5c314d5 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -16,5 +16,6 @@
 
 <resources>
 <!-- AllApps -->
-    <dimen name="all_apps_bottom_sheet_horizontal_padding">41dp</dimen>
+    <dimen name="all_apps_top_padding">300dp</dimen>
+    <dimen name="all_apps_bottom_sheet_horizontal_padding">65dp</dimen>
 </resources>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 0b04b86..99a337e 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -54,6 +54,12 @@
     <attr name="workspaceAccentColor" format="color" />
     <attr name="dropTargetHoverTextColor" format="color" />
 
+    <attr name="allAppsButtonBgColor" format="color" />
+    <attr name="allAppsButtonColor1" format="color" />
+    <attr name="allAppsButtonColor2" format="color" />
+    <attr name="allAppsButtonColor3" format="color" />
+    <attr name="allAppsButtonColor4" format="color" />
+
     <!-- BubbleTextView specific attributes. -->
     <declare-styleable name="BubbleTextView">
         <attr name="layoutHorizontal" format="boolean" />
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 0b1b451..2bc9239 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -79,4 +79,10 @@
 
     <color name="workspace_accent_color_light">#ff8df5e3</color>
     <color name="workspace_accent_color_dark">#ff3d665f</color>
+
+    <color name="all_apps_button_bg_color">#F7F9FA</color>
+    <color name="all_apps_button_color_1">#00677E</color>
+    <color name="all_apps_button_color_2">#00677E</color>
+    <color name="all_apps_button_color_3">#5F757E</color>
+    <color name="all_apps_button_color_4">#005A6E</color>
 </resources>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index ddc7d10..3e666fc 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -93,8 +93,11 @@
     <dimen name="fastscroll_end_margin">-26dp</dimen>
 
     <!-- All Apps -->
-    <dimen name="all_apps_open_vertical_translate">320dp</dimen>
+    <dimen name="all_apps_starting_vertical_translate">320dp</dimen>
+    <dimen name="all_apps_top_padding">0dp</dimen>
     <dimen name="all_apps_search_bar_field_height">48dp</dimen>
+    <!-- all_apps_search_bar_field_height / 2 -->
+    <dimen name="all_apps_search_bar_content_overlap">24dp</dimen>
     <dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
     <dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
     <dimen name="all_apps_empty_search_bg_top_offset">144dp</dimen>
@@ -363,4 +366,9 @@
     <dimen name="search_row_small_icon_size">32dp</dimen>
     <dimen name="padded_rounded_button_padding">8dp</dimen>
 
+<!-- Bottom sheet related parameters -->
+    <dimen name="bottom_sheet_handle_width">32dp</dimen>
+    <dimen name="bottom_sheet_handle_height">4dp</dimen>
+    <dimen name="bottom_sheet_handle_margin">16dp</dimen>
+    <dimen name="bottom_sheet_handle_corner_radius">2dp</dimen>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 818a032..864bb58 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -203,6 +203,14 @@
         <item name="android:importantForAccessibility">no</item>
     </style>
 
+    <style name="AllAppsButtonTheme">
+        <item name="allAppsButtonBgColor">@color/all_apps_button_bg_color</item>
+        <item name="allAppsButtonColor1">@color/all_apps_button_color_1</item>
+        <item name="allAppsButtonColor2">@color/all_apps_button_color_2</item>
+        <item name="allAppsButtonColor3">@color/all_apps_button_color_3</item>
+        <item name="allAppsButtonColor4">@color/all_apps_button_color_4</item>
+    </style>
+
     <style name="AllAppsTheme">
         <item name="disabledIconAlpha">.54</item>
     </style>
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 11f58e7..ad4cae4 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -1169,9 +1169,7 @@
         // Apply local extracted color if the DragView is an AppWidgetHostViewDrawable.
         View view = dragObject.dragView.getContentView();
         if (view instanceof LauncherAppWidgetHostView) {
-            Launcher launcher = Launcher.getLauncher(getContext());
-            Workspace workspace = launcher.getWorkspace();
-            int screenId = workspace.getIdForScreen(this);
+            int screenId = getWorkspace().getIdForScreen(this);
             cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect);
 
             ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, this, screenId);
@@ -1184,11 +1182,24 @@
             return getContext().getString(R.string.move_to_hotseat_position,
                     Math.max(cellX, cellY) + 1);
         } else {
-            return getContext().getString(R.string.move_to_empty_cell,
-                    cellY + 1, cellX + 1);
+            Workspace workspace = getWorkspace();
+            int row = cellY + 1;
+            int col = workspace.mIsRtl ? mCountX - cellX : cellX + 1;
+            int panelCount = workspace.getPanelCount();
+            if (panelCount > 1) {
+                // Increment the column if the target is on the right side of a two panel home
+                int screenId = workspace.getIdForScreen(this);
+                int pageIndex = workspace.getPageIndexForScreenId(screenId);
+                col += (pageIndex % panelCount) * mCountX;
+            }
+            return getContext().getString(R.string.move_to_empty_cell, row, col);
         }
     }
 
+    private Workspace getWorkspace() {
+        return Launcher.cast(mActivity).getWorkspace();
+    }
+
     public void clearDragOutlines() {
         final int oldIndex = mDragOutlineCurrent;
         mDragOutlineAnims[oldIndex].animateOut();
@@ -2243,7 +2254,7 @@
     private void commitTempPlacement(View dragView) {
         mTmpOccupied.copyTo(mOccupied);
 
-        int screenId = Launcher.cast(mActivity).getWorkspace().getIdForScreen(this);
+        int screenId = getWorkspace().getIdForScreen(this);
         int container = Favorites.CONTAINER_DESKTOP;
 
         if (mContainerType == HOTSEAT) {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 62703ad..97c0f38 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -171,7 +171,8 @@
 
     // All apps
     public Point allAppsBorderSpacePx;
-    public int allAppsOpenVerticalTranslate;
+    public int allAppsShiftRange;
+    public int allAppsTopPadding;
     public int allAppsCellHeightPx;
     public int allAppsCellWidthPx;
     public int allAppsIconSizePx;
@@ -244,8 +245,7 @@
         isTablet = info.isTablet(windowBounds);
         isPhone = !isTablet;
         isTwoPanels = isTablet && useTwoPanels;
-        isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS
-                && FeatureFlags.ENABLE_TASKBAR.get();
+        isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS;
 
         // Some more constants.
         context = getContext(context, info, isVerticalBarLayout() || (isTablet && isLandscape)
@@ -288,8 +288,11 @@
         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
         desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
 
-        allAppsOpenVerticalTranslate = res.getDimensionPixelSize(
-                R.dimen.all_apps_open_vertical_translate);
+        allAppsTopPadding = res.getDimensionPixelSize(R.dimen.all_apps_top_padding)
+                + (isTablet ? heightPx - availableHeightPx : 0);
+        allAppsShiftRange = isTablet
+                ? heightPx - allAppsTopPadding
+                : res.getDimensionPixelSize(R.dimen.all_apps_starting_vertical_translate);
 
         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
         folderContentPaddingLeftRight =
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 4300392..c43172c 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -67,18 +67,16 @@
             };
 
     /**
-     * Property to set the scale of workspace and hotseat. The value is based on a combination
+     * Property to set the scale of workspace. The value is based on a combination
      * of all the ones set, to have a smooth experience even in the case of overlapping scaling
      * animation.
      */
-    public static final MultiScalePropertyFactory<View> SCALE_PROPERTY_FACTORY =
-            new MultiScalePropertyFactory<View>("scale_property") {
-                @Override
-                protected void apply(View view, float scale) {
-                    view.setScaleX(scale);
-                    view.setScaleY(scale);
-                }
-            };
+    public static final MultiScalePropertyFactory<Workspace> WORKSPACE_SCALE_PROPERTY_FACTORY =
+            new MultiScalePropertyFactory<Workspace>("workspace_scale_property");
+
+    /** Property to set the scale of hotseat. */
+    public static final MultiScalePropertyFactory<Hotseat> HOTSEAT_SCALE_PROPERTY_FACTORY =
+            new MultiScalePropertyFactory<Hotseat>("hotseat_scale_property");
 
     public static final int SCALE_INDEX_UNFOLD_ANIMATION = 1;
     public static final int SCALE_INDEX_UNLOCK_ANIMATION = 2;
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 16fecde..b3f5c03 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -3398,7 +3398,10 @@
             // When the workspace is not loaded, we do not know how many screen will be bound.
             return getContext().getString(R.string.home_screen);
         }
-        return getContext().getString(R.string.workspace_scroll_format, page + 1, nScreens);
+        int panelCount = getPanelCount();
+        int currentPage = (page / panelCount) + 1;
+        int totalPages = nScreens / panelCount + nScreens % panelCount;
+        return getContext().getString(R.string.workspace_scroll_format, currentPage, totalPages);
     }
 
     /**
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 98e785f..d94e84c 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -18,11 +18,12 @@
 
 import static androidx.dynamicanimation.animation.DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE;
 
+import static com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_WORKSPACE_STATE;
-import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY_FACTORY;
 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.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY;
 import static com.android.launcher3.LauncherState.FLAG_HAS_SYS_UI_SCRIM;
 import static com.android.launcher3.LauncherState.HINT_STATE;
 import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
@@ -64,8 +65,11 @@
  */
 public class WorkspaceStateTransitionAnimation {
 
-    private static final FloatProperty<View> WORKSPACE_STATE_SCALE_PROPERTY =
-            SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WORKSPACE_STATE);
+    private static final FloatProperty<Workspace> WORKSPACE_SCALE_PROPERTY =
+            WORKSPACE_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WORKSPACE_STATE);
+
+    private static final FloatProperty<Hotseat> HOTSEAT_SCALE_PROPERTY =
+            HOTSEAT_SCALE_PROPERTY_FACTORY.get(SCALE_INDEX_WORKSPACE_STATE);
 
     private final Launcher mLauncher;
     private final Workspace mWorkspace;
@@ -120,9 +124,9 @@
                 && fromState == HINT_STATE && state == NORMAL;
         if (shouldSpring) {
             ((PendingAnimation) propertySetter).add(getSpringScaleAnimator(mLauncher,
-                    mWorkspace, mNewScale));
+                    mWorkspace, mNewScale, WORKSPACE_SCALE_PROPERTY));
         } else {
-            propertySetter.setFloat(mWorkspace, WORKSPACE_STATE_SCALE_PROPERTY, mNewScale,
+            propertySetter.setFloat(mWorkspace, WORKSPACE_SCALE_PROPERTY, mNewScale,
                     scaleInterpolator);
         }
 
@@ -130,11 +134,12 @@
         float hotseatScale = hotseatScaleAndTranslation.scale;
         if (shouldSpring) {
             PendingAnimation pa = (PendingAnimation) propertySetter;
-            pa.add(getSpringScaleAnimator(mLauncher, hotseat, hotseatScale));
+            pa.add(getSpringScaleAnimator(mLauncher, hotseat, hotseatScale,
+                    HOTSEAT_SCALE_PROPERTY));
         } else {
             Interpolator hotseatScaleInterpolator = config.getInterpolator(ANIM_HOTSEAT_SCALE,
                     scaleInterpolator);
-            propertySetter.setFloat(hotseat, WORKSPACE_STATE_SCALE_PROPERTY, hotseatScale,
+            propertySetter.setFloat(hotseat, HOTSEAT_SCALE_PROPERTY, hotseatScale,
                     hotseatScaleInterpolator);
         }
 
@@ -198,9 +203,18 @@
     }
 
     /**
+     * Returns a spring based animator for the scale property of {@param workspace}.
+     */
+    public static ValueAnimator getWorkspaceSpringScaleAnimator(Launcher launcher,
+            Workspace workspace, float scale) {
+        return getSpringScaleAnimator(launcher, workspace, scale, WORKSPACE_SCALE_PROPERTY);
+    }
+
+    /**
      * Returns a spring based animator for the scale property of {@param v}.
      */
-    public static ValueAnimator getSpringScaleAnimator(Launcher launcher, View v, float scale) {
+    public static <T extends View> ValueAnimator getSpringScaleAnimator(Launcher launcher, T v,
+            float scale, FloatProperty<T> property) {
         ResourceProvider rp = DynamicResource.provider(launcher);
         float damping = rp.getFloat(R.dimen.hint_scale_damping_ratio);
         float stiffness = rp.getFloat(R.dimen.hint_scale_stiffness);
@@ -211,9 +225,9 @@
                 .setDampingRatio(damping)
                 .setMinimumVisibleChange(MIN_VISIBLE_CHANGE_SCALE)
                 .setEndValue(scale)
-                .setStartValue(WORKSPACE_STATE_SCALE_PROPERTY.get(v))
+                .setStartValue(property.get(v))
                 .setStartVelocity(velocityPxPerS)
-                .build(v, WORKSPACE_STATE_SCALE_PROPERTY);
+                .build(v, property);
 
     }
 }
\ No newline at end of file
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index b6a2459..cdc313f 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -89,15 +89,15 @@
     private float mShiftRange;      // changes depending on the orientation
     private float mProgress;        // [0, 1], mShiftRange * mProgress = shiftCurrent
 
-    private float mScrollRangeDelta = 0;
     private ScrimView mScrimView;
 
     public AllAppsTransitionController(Launcher l) {
         mLauncher = l;
-        mShiftRange = mLauncher.getDeviceProfile().heightPx;
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        setShiftRange(dp.allAppsShiftRange);
         mProgress = 1f;
 
-        mIsVerticalLayout = mLauncher.getDeviceProfile().isVerticalBarLayout();
+        mIsVerticalLayout = dp.isVerticalBarLayout();
         mLauncher.addOnDeviceProfileChangeListener(this);
     }
 
@@ -108,7 +108,7 @@
     @Override
     public void onDeviceProfileChanged(DeviceProfile dp) {
         mIsVerticalLayout = dp.isVerticalBarLayout();
-        setScrollRangeDelta(mScrollRangeDelta);
+        setShiftRange(dp.allAppsShiftRange);
 
         if (mIsVerticalLayout) {
             mLauncher.getHotseat().setTranslationY(0);
@@ -160,12 +160,14 @@
         }
 
         // need to decide depending on the release velocity
-        Interpolator interpolator = (config.userControlled ? LINEAR : DEACCEL_1_7);
-
+        Interpolator verticalProgressInterpolator = config.getInterpolator(ANIM_VERTICAL_PROGRESS,
+                config.userControlled ? LINEAR : DEACCEL_1_7);
         Animator anim = createSpringAnimation(mProgress, targetProgress);
-        anim.setInterpolator(config.getInterpolator(ANIM_VERTICAL_PROGRESS, interpolator));
+        anim.setInterpolator(verticalProgressInterpolator);
         anim.addListener(getProgressAnimatorListener());
         builder.add(anim);
+        // Use ANIM_VERTICAL_PROGRESS's interpolator to determine state transition threshold.
+        builder.setInterpolator(verticalProgressInterpolator);
 
         setAlphas(toState, config, builder);
 
@@ -215,9 +217,8 @@
     /**
      * Updates the total scroll range but does not update the UI.
      */
-    public void setScrollRangeDelta(float delta) {
-        mScrollRangeDelta = delta;
-        mShiftRange = mLauncher.getDeviceProfile().heightPx - mScrollRangeDelta;
+    public void setShiftRange(float shiftRange) {
+        mShiftRange = shiftRange;
     }
 
     /**
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index 59e21c0..bfc7515 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -408,7 +408,7 @@
         if (grid.isVerticalBarLayout()) {
             setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
         } else {
-            setPadding(0, grid.isTablet ? insets.top : 0, 0, 0);
+            setPadding(0, grid.allAppsTopPadding, 0, 0);
         }
 
         InsettableFrameLayout.dispatchInsets(this, insets);
@@ -765,4 +765,11 @@
                     && mVerticalFadingEdge);
         }
     }
+
+    /**
+     * Returns a view that denotes the visible part of all apps container view.
+     */
+    public View getVisibleContainerView() {
+        return mActivityContext.getDeviceProfile().isTablet ? mBottomSheetBackground : this;
+    }
 }
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
index 30d33f9..8601819 100644
--- a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -16,7 +16,6 @@
 package com.android.launcher3.allapps;
 
 import android.content.Context;
-import android.graphics.Rect;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 
@@ -59,12 +58,4 @@
         }
         return super.onTouchEvent(ev);
     }
-
-    @Override
-    public void setInsets(Rect insets) {
-        super.setInsets(insets);
-        int allAppsStartingPositionY = mActivityContext.getDeviceProfile().availableHeightPx
-                - mActivityContext.getDeviceProfile().allAppsOpenVerticalTranslate;
-        mActivityContext.getAllAppsController().setScrollRangeDelta(allAppsStartingPositionY);
-    }
 }
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index ffc049b..4a886a4 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -82,7 +82,7 @@
         setHint(prefixTextWithIcon(getContext(), R.drawable.ic_allapps_search, getHint()));
 
         mContentOverlap =
-                getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_field_height) / 2;
+                getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_content_overlap);
     }
 
     @Override
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index 1e7b224..9c12abd 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -130,6 +130,23 @@
         }
     };
 
+    public static final Interpolator LINEAR_TELEPORT = t -> {
+        float startTeleport = 0.2f;
+        float endTeleport = 0.4f;
+        float teleportProgress = 0.5f;
+        float v;
+        if (t < startTeleport) {
+            v = LINEAR.getInterpolation(t);
+        } else if (t < endTeleport) {
+            v = Utilities.mapToRange(t, startTeleport, endTeleport, startTeleport,
+                    endTeleport + teleportProgress, ACCEL_DEACCEL);
+        } else {
+            v = LINEAR.getInterpolation(t) + teleportProgress;
+        }
+        v = Utilities.boundToRange(v, 0f, 1f);
+        return v;
+    };
+
     private static final float FAST_FLING_PX_MS = 10;
 
     public static Interpolator scrollInterpolatorForVelocity(float velocity) {
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 3ab893b..1300ce7 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -77,6 +77,13 @@
         addAnimationHoldersRecur(a, mDuration, springProperty, mAnimHolders);
     }
 
+    /**
+     * Configures interpolator of the underlying AnimatorSet.
+     */
+    public void setInterpolator(TimeInterpolator interpolator) {
+        mAnim.setInterpolator(interpolator);
+    }
+
     @Override
     public void setViewAlpha(View view, float alpha, TimeInterpolator interpolator) {
         if (view == null || view.getAlpha() == alpha) {
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index a949e11..8b2184d 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -197,12 +197,6 @@
             "ENABLE_APP_PREDICTIONS_WHILE_VISIBLE", true, "Allows app "
             + "predictions to be updated while they are visible to the user.");
 
-    public static final BooleanFlag ENABLE_TASKBAR = getDebugFlag(
-            "ENABLE_TASKBAR", true, "Allows a system Taskbar to be shown on larger devices.");
-
-    public static final BooleanFlag ENABLE_TASKBAR_EDU = getDebugFlag("ENABLE_TASKBAR_EDU", true,
-            "Enables showing taskbar education the first time an app is opened.");
-
     public static final BooleanFlag ENABLE_TASKBAR_POPUP_MENU = getDebugFlag(
             "ENABLE_TASKBAR_POPUP_MENU", true, "Enables long pressing taskbar icons to show the"
                     + " popup menu.");
@@ -256,6 +250,10 @@
             "ENABLE_SPLIT_FROM_WORKSPACE", true,
             "Enable initiating split screen from workspace.");
 
+    public static final BooleanFlag ENABLE_NEW_MIGRATION_LOGIC = getDebugFlag(
+            "ENABLE_NEW_MIGRATION_LOGIC", true,
+            "Enable the new grid migration logic, keeping pages when src < dest");
+
     public static void initialize(Context context) {
         synchronized (sDebugFlags) {
             for (DebugFlag flag : sDebugFlags) {
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 2f3d5d8..fd11b37 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -47,7 +47,6 @@
 import com.android.launcher3.model.BgDataModel;
 import com.android.launcher3.model.GridSizeMigrationTaskV2;
 import com.android.launcher3.model.LoaderTask;
-import com.android.launcher3.model.ModelDelegate;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.RunnableList;
 import com.android.launcher3.util.Themes;
@@ -156,9 +155,10 @@
             PreviewContext previewContext = new PreviewContext(inflationContext, mIdp);
             new LoaderTask(
                     LauncherAppState.getInstance(previewContext),
-                    null,
+                    /* bgAllAppsList= */ null,
                     new BgDataModel(),
-                    new ModelDelegate(), null) {
+                    LauncherAppState.getInstance(previewContext).getModel().getModelDelegate(),
+                    /* results= */ null) {
 
                 @Override
                 public void run() {
diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java
index 3129e2a..308a8f2 100644
--- a/src/com/android/launcher3/icons/IconCache.java
+++ b/src/com/android/launcher3/icons/IconCache.java
@@ -41,9 +41,9 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 
 import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
 
 import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherFiles;
@@ -339,6 +339,16 @@
             List<IconRequestInfo<T>> iconRequestInfos) {
         Map<Pair<UserHandle, Boolean>, List<IconRequestInfo<T>>> iconLoadSubsectionsMap =
                 iconRequestInfos.stream()
+                        .filter(iconRequest -> {
+                            if (iconRequest.itemInfo.getTargetComponent() != null) {
+                                return true;
+                            }
+                            Log.i(TAG,
+                                    "Skipping Item info with null component name: "
+                                            + iconRequest.itemInfo);
+                            iconRequest.itemInfo.bitmap = getDefaultIcon(iconRequest.itemInfo.user);
+                            return false;
+                        })
                         .collect(groupingBy(iconRequest ->
                                 Pair.create(iconRequest.itemInfo.user, iconRequest.useLowResIcon)));
 
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 08c3149..3e49d79 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -38,7 +38,7 @@
 /**
  * Utility class representing persisted grid properties.
  */
-public class DeviceGridState {
+public class DeviceGridState implements Comparable<DeviceGridState> {
 
     public static final String KEY_WORKSPACE_SIZE = "migration_src_workspace_size";
     public static final String KEY_HOTSEAT_COUNT = "migration_src_hotseat_count";
@@ -84,16 +84,16 @@
      */
     public LauncherEvent getWorkspaceSizeEvent() {
         if (!TextUtils.isEmpty(mGridSizeString)) {
-            switch (mGridSizeString.charAt(0)) {
-                case '6':
+            switch (getColumns()) {
+                case 6:
                     return LAUNCHER_GRID_SIZE_6;
-                case '5':
+                case 5:
                     return LAUNCHER_GRID_SIZE_5;
-                case '4':
+                case 4:
                     return LAUNCHER_GRID_SIZE_4;
-                case '3':
+                case 3:
                     return LAUNCHER_GRID_SIZE_3;
-                case '2':
+                case 2:
                     return LAUNCHER_GRID_SIZE_2;
             }
         }
@@ -119,4 +119,21 @@
         return mNumHotseat == other.mNumHotseat
                 && Objects.equals(mGridSizeString, other.mGridSizeString);
     }
+
+    public Integer getColumns() {
+        return Integer.parseInt(String.valueOf(mGridSizeString.charAt(0)));
+    }
+
+    public Integer getRows() {
+        return Integer.parseInt(String.valueOf(mGridSizeString.charAt(2)));
+    }
+
+    @Override
+    public int compareTo(DeviceGridState other) {
+        Integer size = getColumns() * getRows();
+        Integer otherSize = other.getColumns() * other.getRows();
+
+        return size.compareTo(otherSize);
+    }
+
 }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index ca680b7..74b0a6f 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.graphics.LauncherPreviewRenderer;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.InstallSessionHelper;
@@ -225,13 +226,21 @@
             screens.add(screenId);
         }
 
+        boolean preservePages = false;
+        if (screens.isEmpty() && FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.get()) {
+            DeviceGridState srcDeviceState = new DeviceGridState(mContext);
+            DeviceGridState destDeviceState = new DeviceGridState(idp);
+            preservePages = destDeviceState.compareTo(srcDeviceState) >= 0
+                    && destDeviceState.getColumns() - srcDeviceState.getColumns() <= 2;
+        }
+
         // Then we place the items on the screens
         for (int screenId : screens) {
             if (DEBUG) {
                 Log.d(TAG, "Migrating " + screenId);
             }
             GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
-                    mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+                    mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff, false);
             workspaceSolution.find();
             if (mWorkspaceDiff.isEmpty()) {
                 break;
@@ -243,10 +252,12 @@
         int screenId = mDestReader.mLastScreenId + 1;
         while (!mWorkspaceDiff.isEmpty()) {
             GridPlacementSolution workspaceSolution = new GridPlacementSolution(mDb, mSrcReader,
-                    mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff);
+                    mDestReader, mContext, screenId, mTrgX, mTrgY, mWorkspaceDiff,
+                    preservePages);
             workspaceSolution.find();
             screenId++;
         }
+
         return true;
     }
 
@@ -363,13 +374,15 @@
         private final int mScreenId;
         private final int mTrgX;
         private final int mTrgY;
-        private final List<DbEntry> mItemsToPlace;
+        private final List<DbEntry> mSortedItemsToPlace;
+        private final boolean mMatchingScreenIdOnly;
 
         private int mNextStartX;
         private int mNextStartY;
 
         GridPlacementSolution(SQLiteDatabase db, DbReader srcReader, DbReader destReader,
-                Context context, int screenId, int trgX, int trgY, List<DbEntry> itemsToPlace) {
+                Context context, int screenId, int trgX, int trgY, List<DbEntry> sortedItemsToPlace,
+                boolean matchingScreenIdOnly) {
             mDb = db;
             mSrcReader = srcReader;
             mDestReader = destReader;
@@ -386,13 +399,16 @@
                     mOccupied.markCells(entry, true);
                 }
             }
-            mItemsToPlace = itemsToPlace;
+            mSortedItemsToPlace = sortedItemsToPlace;
+            mMatchingScreenIdOnly = matchingScreenIdOnly;
         }
 
         public void find() {
-            Iterator<DbEntry> iterator = mItemsToPlace.iterator();
+            Iterator<DbEntry> iterator = mSortedItemsToPlace.iterator();
             while (iterator.hasNext()) {
                 final DbEntry entry = iterator.next();
+                if (mMatchingScreenIdOnly && entry.screenId < mScreenId) continue;
+                if (mMatchingScreenIdOnly && entry.screenId > mScreenId) break;
                 if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
                     iterator.remove();
                     continue;
@@ -494,7 +510,7 @@
         private final SQLiteDatabase mDb;
         private final String mTableName;
         private final Context mContext;
-        private final HashSet<String> mValidPackages;
+        private final Set<String> mValidPackages;
         private int mLastScreenId = -1;
 
         private final ArrayList<DbEntry> mHotseatEntries = new ArrayList<>();
@@ -503,7 +519,7 @@
                 new ArrayMap<>();
 
         DbReader(SQLiteDatabase db, String tableName, Context context,
-                HashSet<String> validPackages) {
+                Set<String> validPackages) {
             mDb = db;
             mTableName = tableName;
             mContext = context;
diff --git a/src/com/android/launcher3/model/ModelDelegate.java b/src/com/android/launcher3/model/ModelDelegate.java
index 60ca63b..cc42258 100644
--- a/src/com/android/launcher3/model/ModelDelegate.java
+++ b/src/com/android/launcher3/model/ModelDelegate.java
@@ -44,11 +44,7 @@
             boolean isPrimaryInstance) {
         ModelDelegate delegate = Overrides.getObject(
                 ModelDelegate.class, context, R.string.model_delegate_class);
-        delegate.mApp = app;
-        delegate.mAppsList = appsList;
-        delegate.mDataModel = dataModel;
-        delegate.mIsPrimaryInstance = isPrimaryInstance;
-        delegate.mContext = context;
+        delegate.init(context, app, appsList, dataModel, isPrimaryInstance);
         return delegate;
     }
 
@@ -61,6 +57,18 @@
     public ModelDelegate() { }
 
     /**
+     * Initializes the object with the given params.
+     */
+    private void init(Context context, LauncherAppState app, AllAppsList appsList,
+            BgDataModel dataModel, boolean isPrimaryInstance) {
+        this.mApp = app;
+        this.mAppsList = appsList;
+        this.mDataModel = dataModel;
+        this.mIsPrimaryInstance = isPrimaryInstance;
+        this.mContext = context;
+    }
+
+    /**
      * Called periodically to validate and update any data
      */
     @WorkerThread
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 989a9e4..f7d3492 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -17,9 +17,13 @@
 
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.anim.Interpolators.FINAL_FRAME;
+import static com.android.launcher3.anim.Interpolators.INSTANT;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.launcher3.anim.Interpolators.LINEAR_TELEPORT;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_ALL_APPS_FADE;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_SCRIM_FADE;
+import static com.android.launcher3.states.StateAnimationConfig.ANIM_VERTICAL_PROGRESS;
 
 import android.view.MotionEvent;
 import android.view.animation.Interpolator;
@@ -94,9 +98,9 @@
             LauncherState toState) {
         StateAnimationConfig config = super.getConfigForStates(fromState, toState);
         if (fromState == NORMAL && toState == ALL_APPS) {
-            applyNormalToAllAppsAnimConfig(config);
+            applyNormalToAllAppsAnimConfig(mLauncher, config);
         } else if (fromState == ALL_APPS && toState == NORMAL) {
-            applyAllAppsToNormalConfig(config);
+            applyAllAppsToNormalConfig(mLauncher, config);
         }
         return config;
     }
@@ -104,17 +108,24 @@
     /**
      * Applies Animation config values for transition from all apps to home
      */
-    public static void applyAllAppsToNormalConfig(StateAnimationConfig config) {
+    public static void applyAllAppsToNormalConfig(Launcher launcher, StateAnimationConfig config) {
+        boolean isTablet = launcher.getDeviceProfile().isTablet;
         config.setInterpolator(ANIM_SCRIM_FADE, ALLAPPS_STAGGERED_FADE_LATE_RESPONDER);
-        config.setInterpolator(ANIM_ALL_APPS_FADE, ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
+        config.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
+                ? FINAL_FRAME : ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
+        config.setInterpolator(ANIM_VERTICAL_PROGRESS, isTablet ? LINEAR_TELEPORT : LINEAR);
     }
 
     /**
      * Applies Animation config values for transition from home to all apps
      */
-    public static void applyNormalToAllAppsAnimConfig(StateAnimationConfig config) {
+    public static void applyNormalToAllAppsAnimConfig(Launcher launcher,
+            StateAnimationConfig config) {
+        boolean isTablet = launcher.getDeviceProfile().isTablet;
         config.setInterpolator(ANIM_SCRIM_FADE, ALLAPPS_STAGGERED_FADE_EARLY_RESPONDER);
-        config.setInterpolator(ANIM_ALL_APPS_FADE, ALLAPPS_STAGGERED_FADE_LATE_RESPONDER);
+        config.setInterpolator(ANIM_ALL_APPS_FADE, isTablet
+                ? INSTANT : ALLAPPS_STAGGERED_FADE_LATE_RESPONDER);
+        config.setInterpolator(ANIM_VERTICAL_PROGRESS, isTablet ? LINEAR_TELEPORT : LINEAR);
     }
 
 
diff --git a/src/com/android/launcher3/util/MultiScalePropertyFactory.java b/src/com/android/launcher3/util/MultiScalePropertyFactory.java
index f27d0f0..a7e6cc8 100644
--- a/src/com/android/launcher3/util/MultiScalePropertyFactory.java
+++ b/src/com/android/launcher3/util/MultiScalePropertyFactory.java
@@ -18,6 +18,8 @@
 
 import android.util.ArrayMap;
 import android.util.FloatProperty;
+import android.util.Log;
+import android.view.View;
 
 import com.android.launcher3.Utilities;
 
@@ -33,8 +35,10 @@
  *
  * @param <T> Type where to apply the property.
  */
-public abstract class MultiScalePropertyFactory<T> {
+public class MultiScalePropertyFactory<T extends View> {
 
+    private static final boolean DEBUG = false;
+    private static final String TAG = "MultiScaleProperty";
     private final String mName;
     private final ArrayMap<Integer, MultiScaleProperty> mProperties =
             new ArrayMap<Integer, MultiScaleProperty>();
@@ -56,7 +60,6 @@
                 (k) -> new MultiScaleProperty(index, mName + "_" + index));
     }
 
-
     /**
      * Each [setValue] will be aggregated with the other properties values created by the
      * corresponding factory.
@@ -91,11 +94,22 @@
             mLastAggregatedValue = Utilities.boundToRange(multValue, minValue, maxValue);
             mValue = newValue;
             apply(obj, mLastAggregatedValue);
+
+            if (DEBUG) {
+                Log.d(TAG, "name=" + mName
+                        + " newValue=" + newValue + " mInx=" + mInx
+                        + " aggregated=" + mLastAggregatedValue + " others= " + mProperties);
+            }
         }
 
         @Override
-        public Float get(T t) {
-            return mLastAggregatedValue;
+        public Float get(T view) {
+            // The scale of the view should match mLastAggregatedValue. Still, if it has been
+            // changed without using this property, it can differ. As this get method is usually
+            // used to set the starting point on an animation, this would result in some jumps
+            // when the view scale is different than the last aggregated value. To stay on the
+            // safe side, let's return the real view scale.
+            return view.getScaleX();
         }
 
         @Override
@@ -104,6 +118,8 @@
         }
     }
 
-    /** Applies value to object after setValue method is called. */
-    protected abstract void apply(T obj, float value);
+    protected void apply(View view, float value) {
+        view.setScaleX(value);
+        view.setScaleY(value);
+    }
 }
diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java
index c22d60d..5d88884 100644
--- a/src/com/android/launcher3/views/AbstractSlideInView.java
+++ b/src/com/android/launcher3/views/AbstractSlideInView.java
@@ -113,9 +113,16 @@
         return -1;
     }
 
+    /**
+     * Returns the range in height that the slide in view can be dragged.
+     */
+    protected float getShiftRange() {
+        return mContent.getHeight();
+    }
+
     protected void setTranslationShift(float translationShift) {
         mTranslationShift = translationShift;
-        mContent.setTranslationY(mTranslationShift * mContent.getHeight());
+        mContent.setTranslationY(mTranslationShift * getShiftRange());
         if (mColorScrim != null) {
             mColorScrim.setAlpha(1 - mTranslationShift);
         }
@@ -132,8 +139,7 @@
         mSwipeDetector.setDetectableScrollConditions(
                 directionsToDetectScroll, false);
         mSwipeDetector.onTouchEvent(ev);
-        return mSwipeDetector.isDraggingOrSettling()
-                || !getPopupContainer().isEventOverView(mContent, ev);
+        return mSwipeDetector.isDraggingOrSettling() || !isEventOverContent(ev);
     }
 
     @Override
@@ -142,13 +148,23 @@
         if (ev.getAction() == MotionEvent.ACTION_UP && mSwipeDetector.isIdleState()
                 && !isOpeningAnimationRunning()) {
             // If we got ACTION_UP without ever starting swipe, close the panel.
-            if (!getPopupContainer().isEventOverView(mContent, ev)) {
+            if (!isEventOverContent(ev)) {
                 close(true);
             }
         }
         return true;
     }
 
+    /**
+     * Returns {@code true} if the touch event is over the visible area of the bottom sheet.
+     *
+     * By default will check if the touch event is over {@code mContent}, subclasses should override
+     * this method if the visible area of the bottom sheet is different from {@code mContent}.
+     */
+    protected boolean isEventOverContent(MotionEvent ev) {
+        return getPopupContainer().isEventOverView(mContent, ev);
+    }
+
     private boolean isOpeningAnimationRunning() {
         return mIsOpen && mOpenCloseAnimator.isRunning();
     }
@@ -160,7 +176,7 @@
 
     @Override
     public boolean onDrag(float displacement) {
-        float range = mContent.getHeight();
+        float range = getShiftRange();
         displacement = Utilities.boundToRange(displacement, 0, range);
         setTranslationShift(displacement / range);
         return true;
diff --git a/src/com/android/launcher3/views/AllAppsButton.java b/src/com/android/launcher3/views/AllAppsButton.java
index f502d46..b1e69c7 100644
--- a/src/com/android/launcher3/views/AllAppsButton.java
+++ b/src/com/android/launcher3/views/AllAppsButton.java
@@ -18,6 +18,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.util.AttributeSet;
+import android.view.ContextThemeWrapper;
 
 import com.android.launcher3.BubbleTextView;
 import com.android.launcher3.LauncherAppState;
@@ -40,8 +41,9 @@
     public AllAppsButton(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
+        Context theme = new ContextThemeWrapper(context, R.style.AllAppsButtonTheme);
         Bitmap bitmap = LauncherAppState.getInstance(context).getIconCache().getIconFactory()
-                .createScaledBitmapWithShadow(context.getDrawable(R.drawable.ic_all_apps_button));
+                .createScaledBitmapWithShadow(theme.getDrawable(R.drawable.ic_all_apps_button));
         setIcon(new FastBitmapDrawable(bitmap));
     }
 }
diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
index 5543cc2..8a435c9 100644
--- a/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
+++ b/src_ui_overrides/com/android/launcher3/uioverrides/states/AllAppsState.java
@@ -55,8 +55,15 @@
 
     @Override
     public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
-        return new ScaleAndTranslation(1f, 0,
-                -launcher.getAllAppsController().getShiftRange() * PARALLAX_COEFFICIENT);
+        ScaleAndTranslation scaleAndTranslation =
+                new ScaleAndTranslation(NO_SCALE, NO_OFFSET, NO_OFFSET);
+        if (launcher.getDeviceProfile().isTablet) {
+            scaleAndTranslation.scale = 0.97f;
+        } else {
+            scaleAndTranslation.translationY =
+                    -launcher.getAllAppsController().getShiftRange() * PARALLAX_COEFFICIENT;
+        }
+        return scaleAndTranslation;
     }
 
     @Override
diff --git a/tests/Android.bp b/tests/Android.bp
index c2c545b..7542d04 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -67,7 +67,8 @@
         "androidx.test.uiautomator_uiautomator",
         "mockito-target-inline-minus-junit4",
         "launcher_log_protos_lite",
-        "truth-prebuilt"
+        "truth-prebuilt",
+        "platform-test-rules",
     ],
     manifest: "AndroidManifest-common.xml",
     platform_apis: true,
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
deleted file mode 100644
index 005389e..0000000
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.java
+++ /dev/null
@@ -1,278 +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.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.content.Intent;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
-import android.os.Process;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.HashSet;
-
-/** Unit tests for {@link GridSizeMigrationTaskV2} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class GridSizeMigrationTaskV2Test {
-
-    private LauncherModelHelper mModelHelper;
-    private Context mContext;
-    private SQLiteDatabase mDb;
-
-    private HashSet<String> mValidPackages;
-    private InvariantDeviceProfile mIdp;
-
-    private final String testPackage1 = "com.android.launcher3.validpackage1";
-    private final String testPackage2 = "com.android.launcher3.validpackage2";
-    private final String testPackage3 = "com.android.launcher3.validpackage3";
-    private final String testPackage4 = "com.android.launcher3.validpackage4";
-    private final String testPackage5 = "com.android.launcher3.validpackage5";
-    private final String testPackage6 = "com.android.launcher3.validpackage6";
-    private final String testPackage7 = "com.android.launcher3.validpackage7";
-    private final String testPackage8 = "com.android.launcher3.validpackage8";
-    private final String testPackage9 = "com.android.launcher3.validpackage9";
-    private final String testPackage10 = "com.android.launcher3.validpackage10";
-
-    @Before
-    public void setUp() {
-        mModelHelper = new LauncherModelHelper();
-        mContext = mModelHelper.sandboxContext;
-        mDb = mModelHelper.provider.getDb();
-
-        mValidPackages = new HashSet<>();
-        mValidPackages.add(TEST_PACKAGE);
-        mValidPackages.add(testPackage1);
-        mValidPackages.add(testPackage2);
-        mValidPackages.add(testPackage3);
-        mValidPackages.add(testPackage4);
-        mValidPackages.add(testPackage5);
-        mValidPackages.add(testPackage6);
-        mValidPackages.add(testPackage7);
-        mValidPackages.add(testPackage8);
-        mValidPackages.add(testPackage9);
-        mValidPackages.add(testPackage10);
-
-        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);
-    }
-
-    @After
-    public void tearDown() {
-        mModelHelper.destroy();
-    }
-
-    @Test
-    public void testMigration() throws Exception {
-        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);
-        mModelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage6, 6, TMP_CONTENT_URI);
-        mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage8, 8, TMP_CONTENT_URI);
-        mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage9, 9, TMP_CONTENT_URI);
-        mModelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 3, testPackage10, 10, 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.numDatabaseHotseatIcons = 4;
-        mIdp.numColumns = 4;
-        mIdp.numRows = 4;
-        GridSizeMigrationTaskV2.DbReader srcReader = new GridSizeMigrationTaskV2.DbReader(mDb,
-                LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages);
-        GridSizeMigrationTaskV2.DbReader destReader = new GridSizeMigrationTaskV2.DbReader(mDb,
-                LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages);
-        GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
-                destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
-        task.migrate(mIdp);
-
-        // Check hotseat items
-        Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
-                "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
-        assertEquals(c.getCount(), mIdp.numDatabaseHotseatIcons);
-        int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
-        int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 0);
-        assertTrue(c.getString(intentIndex).contains(testPackage1));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 1);
-        assertTrue(c.getString(intentIndex).contains(testPackage2));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 2);
-        assertTrue(c.getString(intentIndex).contains(testPackage3));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 3);
-        assertTrue(c.getString(intentIndex).contains(testPackage4));
-        c.close();
-
-        // Check workspace items
-        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);
-        intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
-        int cellXIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLX);
-        int cellYIndex = c.getColumnIndex(LauncherSettings.Favorites.CELLY);
-
-        HashMap<String, Point> locMap = new HashMap<>();
-        while (c.moveToNext()) {
-            locMap.put(
-                    Intent.parseUri(c.getString(intentIndex), 0).getPackage(),
-                    new Point(c.getInt(cellXIndex), c.getInt(cellYIndex)));
-        }
-        c.close();
-
-        assertEquals(locMap.size(), 6);
-        assertEquals(new Point(0, 2), locMap.get(testPackage8));
-        assertEquals(new Point(0, 3), locMap.get(testPackage6));
-        assertEquals(new Point(1, 3), locMap.get(testPackage10));
-        assertEquals(new Point(2, 3), locMap.get(testPackage5));
-        assertEquals(new Point(3, 3), locMap.get(testPackage9));
-    }
-
-    @Test
-    public void migrateToLargerHotseat() {
-        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),
-                mModelHelper.addItem(APP_ICON, 2, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
-                mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI),
-        };
-
-        int numSrcDatabaseHotseatIcons = srcHotseatItems.length;
-        mIdp.numDatabaseHotseatIcons = 6;
-        mIdp.numColumns = 4;
-        mIdp.numRows = 4;
-        GridSizeMigrationTaskV2.DbReader srcReader = new GridSizeMigrationTaskV2.DbReader(mDb,
-                LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages);
-        GridSizeMigrationTaskV2.DbReader destReader = new GridSizeMigrationTaskV2.DbReader(mDb,
-                LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages);
-        GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
-                destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
-        task.migrate(mIdp);
-
-        // Check hotseat items
-        Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
-                "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
-        assertEquals(c.getCount(), numSrcDatabaseHotseatIcons);
-        int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
-        int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 0);
-        assertTrue(c.getString(intentIndex).contains(testPackage1));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 1);
-        assertTrue(c.getString(intentIndex).contains(testPackage2));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 2);
-        assertTrue(c.getString(intentIndex).contains(testPackage3));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 3);
-        assertTrue(c.getString(intentIndex).contains(testPackage4));
-
-        c.close();
-    }
-
-    @Test
-    public void migrateFromLargerHotseat() {
-        int[] srcHotseatItems = {
-                mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
-                -1,
-                mModelHelper.addItem(SHORTCUT, 2, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
-                mModelHelper.addItem(APP_ICON, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
-                mModelHelper.addItem(SHORTCUT, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI),
-                mModelHelper.addItem(APP_ICON, 5, HOTSEAT, 0, 0, testPackage5, 5, TMP_CONTENT_URI),
-        };
-
-        mIdp.numDatabaseHotseatIcons = 4;
-        mIdp.numColumns = 4;
-        mIdp.numRows = 4;
-        GridSizeMigrationTaskV2.DbReader srcReader = new GridSizeMigrationTaskV2.DbReader(mDb,
-                LauncherSettings.Favorites.TMP_TABLE, mContext, mValidPackages);
-        GridSizeMigrationTaskV2.DbReader destReader = new GridSizeMigrationTaskV2.DbReader(mDb,
-                LauncherSettings.Favorites.TABLE_NAME, mContext, mValidPackages);
-        GridSizeMigrationTaskV2 task = new GridSizeMigrationTaskV2(mContext, mDb, srcReader,
-                destReader, mIdp.numDatabaseHotseatIcons, new Point(mIdp.numColumns, mIdp.numRows));
-        task.migrate(mIdp);
-
-        // Check hotseat items
-        Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[]{LauncherSettings.Favorites.SCREEN, LauncherSettings.Favorites.INTENT},
-                "container=" + CONTAINER_HOTSEAT, null, LauncherSettings.Favorites.SCREEN, null);
-        assertEquals(c.getCount(), mIdp.numDatabaseHotseatIcons);
-        int screenIndex = c.getColumnIndex(LauncherSettings.Favorites.SCREEN);
-        int intentIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 0);
-        assertTrue(c.getString(intentIndex).contains(testPackage1));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 1);
-        assertTrue(c.getString(intentIndex).contains(testPackage2));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 2);
-        assertTrue(c.getString(intentIndex).contains(testPackage3));
-        c.moveToNext();
-        assertEquals(c.getInt(screenIndex), 3);
-        assertTrue(c.getString(intentIndex).contains(testPackage4));
-
-        c.close();
-    }
-}
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt
new file mode 100644
index 0000000..239e092
--- /dev/null
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationTaskV2Test.kt
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2022 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 android.content.Context
+import android.content.Intent
+import android.database.sqlite.SQLiteDatabase
+import android.graphics.Point
+import android.os.Process
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.launcher3.InvariantDeviceProfile
+import com.android.launcher3.LauncherFiles
+import com.android.launcher3.LauncherSettings.Favorites.*
+import com.android.launcher3.config.FeatureFlags
+import com.android.launcher3.model.GridSizeMigrationTaskV2.DbReader
+import com.android.launcher3.pm.UserCache
+import com.android.launcher3.provider.LauncherDbUtils
+import com.android.launcher3.util.LauncherModelHelper
+import com.android.launcher3.util.LauncherModelHelper.*
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Unit tests for [GridSizeMigrationTaskV2]  */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class GridSizeMigrationTaskV2Test {
+    private lateinit var modelHelper: LauncherModelHelper
+    private lateinit var context: Context
+    private lateinit var db: SQLiteDatabase
+    private lateinit var validPackages: Set<String>
+    private lateinit var idp: InvariantDeviceProfile
+    private val testPackage1 = "com.android.launcher3.validpackage1"
+    private val testPackage2 = "com.android.launcher3.validpackage2"
+    private val testPackage3 = "com.android.launcher3.validpackage3"
+    private val testPackage4 = "com.android.launcher3.validpackage4"
+    private val testPackage5 = "com.android.launcher3.validpackage5"
+    private val testPackage6 = "com.android.launcher3.validpackage6"
+    private val testPackage7 = "com.android.launcher3.validpackage7"
+    private val testPackage8 = "com.android.launcher3.validpackage8"
+    private val testPackage9 = "com.android.launcher3.validpackage9"
+    private val testPackage10 = "com.android.launcher3.validpackage10"
+
+    @Before
+    fun setUp() {
+        modelHelper = LauncherModelHelper()
+        context = modelHelper.sandboxContext
+        db = modelHelper.provider.db
+
+        validPackages = setOf(
+            TEST_PACKAGE,
+            testPackage1,
+            testPackage2,
+            testPackage3,
+            testPackage4,
+            testPackage5,
+            testPackage6,
+            testPackage7,
+            testPackage8,
+            testPackage9,
+            testPackage10
+        )
+
+        idp = InvariantDeviceProfile.INSTANCE[context]
+        val userSerial = UserCache.INSTANCE[context].getSerialNumberForUser(Process.myUserHandle())
+        LauncherDbUtils.dropTable(db, TMP_TABLE)
+        addTableToDb(db, userSerial, false, TMP_TABLE)
+    }
+
+    @After
+    fun tearDown() {
+        modelHelper.destroy()
+    }
+
+    /**
+     * Old migration logic, should be modified once [FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC] is
+     * not needed anymore
+     */
+    @Test
+    @Throws(Exception::class)
+    fun testMigration() {
+        // Src Hotseat icons
+        modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
+        modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
+        modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+        // Src grid icons
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage5, 5, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage6, 6, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 1, testPackage8, 8, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 2, testPackage9, 9, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 4, 3, testPackage10, 10, TMP_CONTENT_URI)
+
+        // Dest hotseat icons
+        modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2)
+        // Dest grid icons
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage7)
+
+        idp.numDatabaseHotseatIcons = 4
+        idp.numColumns = 4
+        idp.numRows = 4
+        val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+        val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+        val task = GridSizeMigrationTaskV2(
+            context,
+            db,
+            srcReader,
+            destReader,
+            idp.numDatabaseHotseatIcons,
+            Point(idp.numColumns, idp.numRows)
+        )
+        task.migrate(idp)
+
+        // Check hotseat items
+        var c = context.contentResolver.query(
+            CONTENT_URI,
+            arrayOf(SCREEN, INTENT),
+            "container=$CONTAINER_HOTSEAT",
+            null,
+            SCREEN,
+            null
+        ) ?: throw IllegalStateException()
+
+        assertThat(c.count).isEqualTo(idp.numDatabaseHotseatIcons)
+
+        val screenIndex = c.getColumnIndex(SCREEN)
+        var intentIndex = c.getColumnIndex(INTENT)
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex).toLong()).isEqualTo(0)
+        assertThat(c.getString(intentIndex)).contains(testPackage1)
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex).toLong()).isEqualTo(1)
+        assertThat(c.getString(intentIndex)).contains(testPackage2)
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex).toLong()).isEqualTo(2)
+        assertThat(c.getString(intentIndex)).contains(testPackage3)
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex).toLong()).isEqualTo(3)
+        assertThat(c.getString(intentIndex)).contains(testPackage4)
+        c.close()
+
+        // Check workspace items
+        c = context.contentResolver.query(
+            CONTENT_URI,
+            arrayOf(CELLX, CELLY, INTENT),
+            "container=$CONTAINER_DESKTOP",
+            null,
+            null,
+            null
+        ) ?: throw IllegalStateException()
+
+        intentIndex = c.getColumnIndex(INTENT)
+        val cellXIndex = c.getColumnIndex(CELLX)
+        val cellYIndex = c.getColumnIndex(CELLY)
+        val locMap = HashMap<String, Point>()
+        while (c.moveToNext()) {
+            locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+                Point(c.getInt(cellXIndex), c.getInt(cellYIndex))
+        }
+        c.close()
+        assertThat(locMap.size.toLong()).isEqualTo(6)
+        assertThat(locMap[testPackage8]).isEqualTo(Point(0, 2))
+        assertThat(locMap[testPackage6]).isEqualTo(Point(0, 3))
+        assertThat(locMap[testPackage10]).isEqualTo(Point(1, 3))
+        assertThat(locMap[testPackage7]).isEqualTo(Point(2, 2))
+        assertThat(locMap[testPackage5]).isEqualTo(Point(2, 3))
+        assertThat(locMap[testPackage9]).isEqualTo(Point(3, 3))
+    }
+
+    @Test
+    fun migrateToLargerHotseat() {
+        val srcHotseatItems = intArrayOf(
+            modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI),
+            modelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI),
+            modelHelper.addItem(APP_ICON, 2, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI),
+            modelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+        )
+        val numSrcDatabaseHotseatIcons = srcHotseatItems.size
+        idp.numDatabaseHotseatIcons = 6
+        idp.numColumns = 4
+        idp.numRows = 4
+        val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+        val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+        val task = GridSizeMigrationTaskV2(
+            context,
+            db,
+            srcReader,
+            destReader,
+            idp.numDatabaseHotseatIcons,
+            Point(idp.numColumns, idp.numRows)
+        )
+        task.migrate(idp)
+
+        // Check hotseat items
+        val c = context.contentResolver.query(
+            CONTENT_URI,
+            arrayOf(SCREEN, INTENT),
+            "container=$CONTAINER_HOTSEAT",
+            null,
+            SCREEN,
+            null
+        ) ?: throw IllegalStateException()
+
+        assertThat(c.count.toLong()).isEqualTo(numSrcDatabaseHotseatIcons.toLong())
+        val screenIndex = c.getColumnIndex(SCREEN)
+        val intentIndex = c.getColumnIndex(INTENT)
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex)).isEqualTo(0)
+        assertThat(c.getString(intentIndex)).contains(testPackage1)
+
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex)).isEqualTo(1)
+        assertThat(c.getString(intentIndex)).contains(testPackage2)
+
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex)).isEqualTo(2)
+        assertThat(c.getString(intentIndex)).contains(testPackage3)
+
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex)).isEqualTo(3)
+        assertThat(c.getString(intentIndex)).contains(testPackage4)
+
+        c.close()
+    }
+
+    @Test
+    fun migrateFromLargerHotseat() {
+        modelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0, testPackage1, 1, TMP_CONTENT_URI)
+        modelHelper.addItem(SHORTCUT, 2, HOTSEAT, 0, 0, testPackage2, 2, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 3, HOTSEAT, 0, 0, testPackage3, 3, TMP_CONTENT_URI)
+        modelHelper.addItem(SHORTCUT, 4, HOTSEAT, 0, 0, testPackage4, 4, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 5, HOTSEAT, 0, 0, testPackage5, 5, TMP_CONTENT_URI)
+
+        idp.numDatabaseHotseatIcons = 4
+        idp.numColumns = 4
+        idp.numRows = 4
+        val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+        val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+        val task = GridSizeMigrationTaskV2(
+            context,
+            db,
+            srcReader,
+            destReader,
+            idp.numDatabaseHotseatIcons,
+            Point(idp.numColumns, idp.numRows)
+        )
+        task.migrate(idp)
+
+        // Check hotseat items
+        val c = context.contentResolver.query(
+            CONTENT_URI,
+            arrayOf(SCREEN, INTENT),
+            "container=$CONTAINER_HOTSEAT",
+            null,
+            SCREEN,
+            null
+        ) ?: throw IllegalStateException()
+
+        assertThat(c.count.toLong()).isEqualTo(idp.numDatabaseHotseatIcons.toLong())
+        val screenIndex = c.getColumnIndex(SCREEN)
+        val intentIndex = c.getColumnIndex(INTENT)
+
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex)).isEqualTo(0)
+        assertThat(c.getString(intentIndex)).contains(testPackage1)
+
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex)).isEqualTo(1)
+        assertThat(c.getString(intentIndex)).contains(testPackage2)
+
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex)).isEqualTo(2)
+        assertThat(c.getString(intentIndex)).contains(testPackage3)
+
+        c.moveToNext()
+        assertThat(c.getInt(screenIndex)).isEqualTo(3)
+        assertThat(c.getString(intentIndex)).contains(testPackage4)
+
+        c.close()
+    }
+
+    /**
+     * Migrating from a smaller grid to a large one should keep the pages
+     * if the column difference is less than 2
+     */
+    @Test
+    @Throws(Exception::class)
+    fun migrateFromSmallerGridSmallDifference() {
+        enableNewMigrationLogic("4,4")
+
+        // Setup src grid
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 2, testPackage1, 5, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 2, 3, testPackage2, 6, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 1, DESKTOP, 3, 1, testPackage3, 7, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 1, DESKTOP, 3, 2, testPackage4, 8, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 2, DESKTOP, 3, 3, testPackage5, 9, TMP_CONTENT_URI)
+
+        idp.numDatabaseHotseatIcons = 4
+        idp.numColumns = 6
+        idp.numRows = 5
+
+        val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+        val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+        val task = GridSizeMigrationTaskV2(
+            context,
+            db,
+            srcReader,
+            destReader,
+            idp.numDatabaseHotseatIcons,
+            Point(idp.numColumns, idp.numRows)
+        )
+        task.migrate(idp)
+
+        // Get workspace items
+        val c = context.contentResolver.query(
+            CONTENT_URI,
+            arrayOf(INTENT, SCREEN),
+            "container=$CONTAINER_DESKTOP",
+            null,
+            null,
+            null
+        ) ?: throw IllegalStateException()
+        val intentIndex = c.getColumnIndex(INTENT)
+        val screenIndex = c.getColumnIndex(SCREEN)
+
+        // Get in which screen the icon is
+        val locMap = HashMap<String, Int>()
+        while (c.moveToNext()) {
+            locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+                c.getInt(screenIndex)
+        }
+        c.close()
+        assertThat(locMap.size).isEqualTo(5)
+        assertThat(locMap[testPackage1]).isEqualTo(0)
+        assertThat(locMap[testPackage2]).isEqualTo(0)
+        assertThat(locMap[testPackage3]).isEqualTo(1)
+        assertThat(locMap[testPackage4]).isEqualTo(1)
+        assertThat(locMap[testPackage5]).isEqualTo(2)
+
+        disableNewMigrationLogic()
+    }
+
+    /**
+     * Migrating from a smaller grid to a large one should reflow the pages
+     * if the column difference is more than 2
+     */
+    @Test
+    @Throws(Exception::class)
+    fun migrateFromSmallerGridBigDifference() {
+        enableNewMigrationLogic("2,2")
+
+        // Setup src grid
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 1, testPackage1, 5, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 1, testPackage2, 6, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 1, DESKTOP, 0, 0, testPackage3, 7, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 1, DESKTOP, 1, 0, testPackage4, 8, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 2, DESKTOP, 0, 0, testPackage5, 9, TMP_CONTENT_URI)
+
+        idp.numDatabaseHotseatIcons = 4
+        idp.numColumns = 5
+        idp.numRows = 5
+        val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+        val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+        val task = GridSizeMigrationTaskV2(
+            context,
+            db,
+            srcReader,
+            destReader,
+            idp.numDatabaseHotseatIcons,
+            Point(idp.numColumns, idp.numRows)
+        )
+        task.migrate(idp)
+
+        // Get workspace items
+        val c = context.contentResolver.query(
+            CONTENT_URI,
+            arrayOf(INTENT, SCREEN),
+            "container=$CONTAINER_DESKTOP",
+            null,
+            null,
+            null
+        ) ?: throw IllegalStateException()
+
+        val intentIndex = c.getColumnIndex(INTENT)
+        val screenIndex = c.getColumnIndex(SCREEN)
+
+        // Get in which screen the icon is
+        val locMap = HashMap<String, Int>()
+        while (c.moveToNext()) {
+            locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+                c.getInt(screenIndex)
+        }
+        c.close()
+
+        // All icons fit the first screen
+        assertThat(locMap.size).isEqualTo(5)
+        assertThat(locMap[testPackage1]).isEqualTo(0)
+        assertThat(locMap[testPackage2]).isEqualTo(0)
+        assertThat(locMap[testPackage3]).isEqualTo(0)
+        assertThat(locMap[testPackage4]).isEqualTo(0)
+        assertThat(locMap[testPackage5]).isEqualTo(0)
+        disableNewMigrationLogic()
+    }
+
+    /**
+     * Migrating from a larger grid to a smaller, we reflow from page 0
+     */
+    @Test
+    @Throws(Exception::class)
+    fun migrateFromLargerGrid() {
+        enableNewMigrationLogic("5,5")
+
+        // Setup src grid
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 0, 1, testPackage1, 5, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 0, DESKTOP, 1, 1, testPackage2, 6, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 1, DESKTOP, 0, 0, testPackage3, 7, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 1, DESKTOP, 1, 0, testPackage4, 8, TMP_CONTENT_URI)
+        modelHelper.addItem(APP_ICON, 2, DESKTOP, 0, 0, testPackage5, 9, TMP_CONTENT_URI)
+
+        idp.numDatabaseHotseatIcons = 4
+        idp.numColumns = 4
+        idp.numRows = 4
+        val srcReader = DbReader(db, TMP_TABLE, context, validPackages)
+        val destReader = DbReader(db, TABLE_NAME, context, validPackages)
+        val task = GridSizeMigrationTaskV2(
+            context,
+            db,
+            srcReader,
+            destReader,
+            idp.numDatabaseHotseatIcons,
+            Point(idp.numColumns, idp.numRows)
+        )
+        task.migrate(idp)
+
+        // Get workspace items
+        val c = context.contentResolver.query(
+            CONTENT_URI,
+            arrayOf(INTENT, SCREEN),
+            "container=$CONTAINER_DESKTOP",
+            null,
+            null,
+            null
+        ) ?: throw IllegalStateException()
+        val intentIndex = c.getColumnIndex(INTENT)
+        val screenIndex = c.getColumnIndex(SCREEN)
+
+        // Get in which screen the icon is
+        val locMap = HashMap<String, Int>()
+        while (c.moveToNext()) {
+            locMap[Intent.parseUri(c.getString(intentIndex), 0).getPackage()] =
+                c.getInt(screenIndex)
+        }
+        c.close()
+
+        // All icons fit the first screen
+        assertThat(locMap.size).isEqualTo(5)
+        assertThat(locMap[testPackage1]).isEqualTo(0)
+        assertThat(locMap[testPackage2]).isEqualTo(0)
+        assertThat(locMap[testPackage3]).isEqualTo(0)
+        assertThat(locMap[testPackage4]).isEqualTo(0)
+        assertThat(locMap[testPackage5]).isEqualTo(0)
+
+        disableNewMigrationLogic()
+    }
+
+    private fun enableNewMigrationLogic(srcGridSize: String) {
+        context.getSharedPreferences(FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+            .edit()
+            .putBoolean(FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.key, true)
+            .commit()
+        context.getSharedPreferences(LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
+            .edit()
+            .putString(DeviceGridState.KEY_WORKSPACE_SIZE, srcGridSize)
+            .commit()
+        FeatureFlags.initialize(context)
+    }
+
+    private fun disableNewMigrationLogic() {
+        context.getSharedPreferences(FeatureFlags.FLAGS_PREF_NAME, Context.MODE_PRIVATE)
+            .edit()
+            .putBoolean(FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.key, false)
+            .commit()
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
index c4a8db6..6099987 100644
--- a/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
+++ b/tests/src/com/android/launcher3/util/MultiScalePropertyTest.kt
@@ -1,5 +1,6 @@
 package com.android.launcher3.util
 
+import android.view.View
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
@@ -14,8 +15,8 @@
     private val received = mutableListOf<Float>()
 
     private val factory =
-        object : MultiScalePropertyFactory<Int?>("Test") {
-            override fun apply(obj: Int?, value: Float) {
+        object : MultiScalePropertyFactory<View?>("Test") {
+            override fun apply(obj: View?, value: Float) {
                 received.add(value)
             }
         }