Merge "Enable input window rotation flag" into sc-v2-dev
diff --git a/go/quickstep/res/values/styles.xml b/go/quickstep/res/values/styles.xml
index 561531b..442c413 100644
--- a/go/quickstep/res/values/styles.xml
+++ b/go/quickstep/res/values/styles.xml
@@ -16,14 +16,14 @@
 -->
 <resources>
     <!-- App themes -->
-    <style name="AppTheme" parent="@style/LauncherTheme">
+    <style name="LauncherTheme" parent="@style/BaseLauncherTheme">
         <item name="overviewButtonTextColor">@color/go_overview_text_color</item>
         <item name="overviewButtonIconColor">@color/go_overview_text_color</item>
         <item name="overviewButtonBackgroundColor">@color/go_overview_button_color</item>
         <item name="modalDialogBackground">@color/go_modal_dialog_background</item>
     </style>
 
-    <style name="AppTheme.Dark" parent="@style/LauncherTheme.Dark">
+    <style name="LauncherTheme.Dark" parent="@style/LauncherTheme">
         <item name="overviewButtonTextColor">@color/go_overview_text_color_dark</item>
         <item name="overviewButtonIconColor">@color/go_overview_text_color_dark</item>
         <item name="overviewButtonBackgroundColor">@color/go_overview_button_color_dark</item>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index e1d0574..b4b29aa 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -64,6 +64,7 @@
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.ObjectWrapper;
 import com.android.launcher3.util.UiThreadHelper;
+import com.android.quickstep.OverviewCommandHelper;
 import com.android.quickstep.RecentsModel;
 import com.android.quickstep.SysUINavigationMode;
 import com.android.quickstep.SysUINavigationMode.Mode;
@@ -109,6 +110,7 @@
     private OverviewActionsView mActionsView;
 
     private @Nullable TaskbarManager mTaskbarManager;
+    private @Nullable OverviewCommandHelper mOverviewCommandHelper;
     private @Nullable LauncherTaskbarUIController mTaskbarUIController;
     private final ServiceConnection mTisBinderConnection = new ServiceConnection() {
         @Override
@@ -117,6 +119,8 @@
             mTaskbarManager.setLauncher(BaseQuickstepLauncher.this);
             Log.d(TAG, "TIS service connected");
             resetServiceBindRetryState();
+
+            mOverviewCommandHelper = ((TISBinder) iBinder).getOverviewCommandHelper();
         }
 
         @Override
@@ -159,6 +163,15 @@
         super.onDestroy();
     }
 
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+
+        if (mOverviewCommandHelper != null) {
+            mOverviewCommandHelper.clearPendingCommands();
+        }
+    }
+
     public QuickstepTransitionManager getAppTransitionManager() {
         return mAppTransitionManager;
     }
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 97f46a5..6249e6a 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1174,6 +1174,7 @@
             boolean isTranslucent = runningTaskTarget != null && runningTaskTarget.isTranslucent;
             boolean appCanEnterPip = !mDeviceState.isPipActive()
                     && runningTaskTarget != null
+                    && runningTaskTarget.allowEnterPip
                     && runningTaskTarget.taskInfo.pictureInPictureParams != null
                     && runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
             HomeAnimationFactory homeAnimFactory =
diff --git a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
index f29d68a..7fb8e16 100644
--- a/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/FallbackActivityInterface.java
@@ -107,10 +107,9 @@
     public RecentsView getVisibleRecentsView() {
         RecentsActivity activity = getCreatedActivity();
         if (activity != null) {
-            RecentsView recentsView = activity.getOverviewPanel();
-            if (activity.hasBeenResumed() || (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode()
-                    && recentsView.getRunningTaskId() == -1)) {
-                return recentsView;
+            if (activity.hasBeenResumed()
+                    || (ENABLE_QUICKSTEP_LIVE_TILE.get() && isInLiveTileMode())) {
+                return activity.getOverviewPanel();
             }
         }
         return null;
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index fd44e02..e2f198c 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -173,7 +173,9 @@
     @Override
     protected void notifyGestureAnimationStartToRecents() {
         if (mRunningOverHome) {
-            mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
+            if (SysUINavigationMode.getMode(mContext).hasGestures) {
+                mRecentsView.onGestureAnimationStartOnHome(mGestureState.getRunningTask());
+            }
         } else {
             super.notifyGestureAnimationStartToRecents();
         }
diff --git a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
index 5deb75e..d91d5b0 100644
--- a/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/LauncherActivityInterface.java
@@ -189,7 +189,7 @@
                 launcher != null && launcher.getStateManager().getState().overviewUi
                         ? launcher.getOverviewPanel() : null;
         if (recentsView == null || (!launcher.hasBeenResumed()
-                && recentsView.getRunningTaskId() == -1)) {
+                && recentsView.getRunningTaskViewId() == -1)) {
             // If live tile has ended, return null.
             return null;
         }
diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
index 89c2ed8..5d1f908 100644
--- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
+++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java
@@ -109,6 +109,11 @@
         MAIN_EXECUTOR.execute(() -> addCommand(cmd));
     }
 
+    @UiThread
+    public void clearPendingCommands() {
+        mPendingCommands.clear();
+    }
+
     private TaskView getNextTask(RecentsView view) {
         final TaskView runningTaskView = view.getRunningTaskView();
 
diff --git a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
index 0efe666..780032e 100644
--- a/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
+++ b/quickstep/src/com/android/quickstep/OverviewComponentObserver.java
@@ -33,8 +33,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.SystemClock;
+import android.util.Log;
 import android.util.SparseIntArray;
 
+import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.OverviewComponentObserverProto;
 import com.android.launcher3.tracing.TouchInteractionServiceProto;
 import com.android.launcher3.util.SimpleBroadcastReceiver;
@@ -129,6 +132,16 @@
     private void updateOverviewTargets() {
         ComponentName defaultHome = PackageManagerWrapper.getInstance()
                 .getHomeActivities(new ArrayList<>());
+        if (TestProtocol.sDebugTracing && defaultHome == null) {
+            Log.d(TestProtocol.THIRD_PARTY_LAUNCHER_NOT_SET, "getHomeActivities returned null");
+            while ((defaultHome =
+                    PackageManagerWrapper.getInstance().getHomeActivities(new ArrayList<>()))
+                    == null) {
+                SystemClock.sleep(10);
+            }
+            Log.d(TestProtocol.THIRD_PARTY_LAUNCHER_NOT_SET,
+                    "getHomeActivities returned non-null: " + defaultHome);
+        }
 
         mIsHomeDisabled = mDeviceState.isHomeDisabled();
         mIsDefaultHome = Objects.equals(mMyHomeIntent.getComponent(), defaultHome);
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 95be45a..03e2395 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -22,8 +22,8 @@
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_DURATION;
 import static com.android.launcher3.QuickstepTransitionManager.STATUS_BAR_TRANSITION_PRE_DELAY;
 import static com.android.launcher3.Utilities.createHomeIntent;
-import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
+import static com.android.launcher3.graphics.SysUiScrim.SYSUI_PROGRESS;
 import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
 import static com.android.quickstep.TaskViewUtils.createRecentsWindowAnimator;
@@ -75,7 +75,6 @@
 import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
-import com.android.quickstep.views.SplitPlaceholderView;
 import com.android.quickstep.views.TaskView;
 import com.android.systemui.shared.system.ActivityOptionsCompat;
 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat;
@@ -287,7 +286,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        mStateManager = new StateManager<>(this, RecentsState.DEFAULT);
+        mStateManager = new StateManager<>(this, RecentsState.BG_LAUNCHER);
 
         mOldConfig = new Configuration(getResources().getConfiguration());
         initDeviceProfile();
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 07d221c..b5da097 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -139,7 +139,7 @@
 
         // If the opening task id is not currently visible in overview, then fall back to normal app
         // icon launch animation
-        TaskView taskView = recentsView.getTaskView(openingTaskId);
+        TaskView taskView = recentsView.getTaskViewByTaskId(openingTaskId);
         if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
             return null;
         }
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 69217c2..8155244 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -297,6 +297,10 @@
         public TaskbarManager getTaskbarManager() {
             return mTaskbarManager;
         }
+
+        public OverviewCommandHelper getOverviewCommandHelper() {
+            return mOverviewCommandHelper;
+        }
     }
 
     private static boolean sConnected = false;
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 540a9ca..64a428f 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -95,7 +95,7 @@
             TaskViewSimulator taskViewSimulator) {
         super.onPrepareGestureEndAnimation(animatorSet, endTarget, taskViewSimulator);
         if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
-            TaskView tv = getTaskView(mHomeTaskInfo.taskId);
+            TaskView tv = getTaskViewByTaskId(mHomeTaskInfo.taskId);
             if (tv != null) {
                 PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
                         false /* dismissingForSplitSelection*/);
@@ -117,8 +117,9 @@
     }
 
     @Override
-    public void setCurrentTask(int runningTaskId) {
-        super.setCurrentTask(runningTaskId);
+    public void setCurrentTask(int runningTaskViewId) {
+        super.setCurrentTask(runningTaskViewId);
+        int runningTaskId = getTaskIdsForRunningTaskView()[0];
         if (mHomeTaskInfo != null && mHomeTaskInfo.taskId != runningTaskId) {
             mHomeTaskInfo = null;
             setRunningTaskHidden(false);
@@ -128,7 +129,7 @@
     @Nullable
     @Override
     protected TaskView getHomeTaskView() {
-        return mHomeTaskInfo != null ? getTaskView(mHomeTaskInfo.taskId) : null;
+        return mHomeTaskInfo != null ? getTaskViewByTaskId(mHomeTaskInfo.taskId) : null;
     }
 
     @Override
@@ -148,11 +149,12 @@
         // When quick-switching on 3p-launcher, we add a "stub" tile corresponding to Launcher
         // as well. This tile is never shown as we have setCurrentTaskHidden, but allows use to
         // track the index of the next task appropriately, as if we are switching on any other app.
-        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == mRunningTaskId && !tasks.isEmpty()) {
+        int runningTaskId = getTaskIdsForRunningTaskView()[0];
+        if (mHomeTaskInfo != null && mHomeTaskInfo.taskId == runningTaskId && !tasks.isEmpty()) {
             // Check if the task list has running task
             boolean found = false;
             for (Task t : tasks) {
-                if (t.key.id == mRunningTaskId) {
+                if (t.key.id == runningTaskId) {
                     found = true;
                     break;
                 }
diff --git a/quickstep/src/com/android/quickstep/fallback/RecentsState.java b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
index d06d87c..917b58a 100644
--- a/quickstep/src/com/android/quickstep/fallback/RecentsState.java
+++ b/quickstep/src/com/android/quickstep/fallback/RecentsState.java
@@ -42,8 +42,8 @@
     private static final int FLAG_OVERVIEW_UI = BaseState.getFlag(7);
 
     public static final RecentsState DEFAULT = new RecentsState(0,
-            FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID | FLAG_SCRIM
-                    | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
+            FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_SHOW_AS_GRID
+                    | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
     public static final RecentsState MODAL_TASK = new ModalState(1,
             FLAG_DISABLE_RESTORE | FLAG_CLEAR_ALL_BUTTON | FLAG_OVERVIEW_ACTIONS | FLAG_MODAL
                     | FLAG_SHOW_AS_GRID | FLAG_SCRIM | FLAG_LIVE_TILE | FLAG_OVERVIEW_UI);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index d00db2d..b077ca6 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -398,6 +398,9 @@
 
     private final InvariantDeviceProfile mIdp;
 
+    /**
+     * Getting views should be done via {@link #getTaskViewFromPool()}
+     */
     private final ViewPool<TaskView> mTaskViewPool;
 
     private final TaskOverlayFactory mTaskOverlayFactory;
@@ -441,7 +444,7 @@
             }
 
             // Remove the task immediately from the task list
-            TaskView taskView = getTaskView(taskId);
+            TaskView taskView = getTaskViewByTaskId(taskId);
             if (taskView != null) {
                 removeView(taskView);
             }
@@ -463,7 +466,7 @@
                 return;
             }
 
-            TaskView taskView = getTaskView(taskId);
+            TaskView taskView = getTaskViewByTaskId(taskId);
             if (taskView == null) {
                 return;
             }
@@ -496,10 +499,16 @@
     private int mTaskListChangeId = -1;
 
     // Only valid until the launcher state changes to NORMAL
-    protected int mRunningTaskId = -1;
+    /**
+     * ID for the current running TaskView view, unique amongst TaskView instances. ID's are set
+     * through {@link #getTaskViewFromPool()} and incremented by {@link #mTaskViewIdCount}
+     */
+    protected int mRunningTaskViewId = -1;
+    private int mTaskViewIdCount;
+    private final int[] INVALID_TASK_IDS = new int[]{-1, -1};
     protected boolean mRunningTaskTileHidden;
     private Task mTmpRunningTask;
-    protected int mFocusedTaskId = -1;
+    protected int mFocusedTaskViewId = -1;
 
     private boolean mTaskIconScaledDown = false;
     private boolean mRunningTaskShowScreenshot = false;
@@ -734,7 +743,7 @@
     @Override
     public Task onTaskThumbnailChanged(int taskId, ThumbnailData thumbnailData) {
         if (mHandleTaskStackChanges) {
-            TaskView taskView = getTaskView(taskId);
+            TaskView taskView = getTaskViewByTaskId(taskId);
             if (taskView != null) {
                 Task task = taskView.getTask();
                 taskView.getThumbnail().setThumbnail(task, thumbnailData);
@@ -764,7 +773,7 @@
      * @param refreshNow Refresh immediately if it's true.
      */
     public TaskView updateThumbnail(int taskId, ThumbnailData thumbnailData, boolean refreshNow) {
-        TaskView taskView = getTaskView(taskId);
+        TaskView taskView = getTaskViewByTaskId(taskId);
         if (taskView != null) {
             taskView.getThumbnail().setThumbnail(taskView.getTask(), thumbnailData, refreshNow);
         }
@@ -838,7 +847,8 @@
         if (child instanceof TaskView && child != mSplitHiddenTaskView
                 && child != mMovingTaskView) {
             TaskView taskView = (TaskView) child;
-            mHasVisibleTaskData.delete(taskView.getTaskId());
+            mHasVisibleTaskData.delete(taskView.getTaskIds()[0]);
+            taskView.setTaskViewId(-1);
             mTaskViewPool.recycle(taskView);
             mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
         }
@@ -878,7 +888,8 @@
     }
 
     public void launchSideTaskInLiveTileModeForRestartedApp(int taskId) {
-        if (mRunningTaskId != -1 && mRunningTaskId == taskId) {
+        int runningTaskViewId = getTaskViewIdFromTaskId(taskId);
+        if (mRunningTaskViewId != -1 && mRunningTaskViewId == runningTaskViewId) {
             RemoteAnimationTargets targets = getLiveTileParams().getTargetSet();
             if (targets != null && targets.findTask(taskId) != null) {
                 launchSideTaskInLiveTileMode(taskId, targets.apps, targets.wallpapers,
@@ -890,7 +901,7 @@
     public void launchSideTaskInLiveTileMode(int taskId, RemoteAnimationTargetCompat[] apps,
             RemoteAnimationTargetCompat[] wallpaper, RemoteAnimationTargetCompat[] nonApps) {
         AnimatorSet anim = new AnimatorSet();
-        TaskView taskView = getTaskView(taskId);
+        TaskView taskView = getTaskViewByTaskId(taskId);
         if (taskView == null || !isTaskViewVisible(taskView)) {
             // TODO: Refine this animation.
             SurfaceTransactionApplier surfaceApplier =
@@ -967,10 +978,11 @@
                 == getPagedOrientationHandler().getPrimaryScroll(this);
     }
 
-    public TaskView getTaskView(int taskId) {
+    public TaskView getTaskViewByTaskId(int taskId) {
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = getTaskViewAt(i);
-            if (taskView.getTaskId() == taskId) {
+            int[] taskIds = taskView.getTaskIds();
+            if (taskIds[0] == taskId || taskIds[1] == taskId) {
                 return taskView;
             }
         }
@@ -1187,7 +1199,7 @@
         unloadVisibleTaskData(TaskView.FLAG_UPDATE_ALL);
 
         TaskView ignoreResetTaskView =
-                mIgnoreResetTaskId == -1 ? null : getTaskView(mIgnoreResetTaskId);
+                mIgnoreResetTaskId == -1 ? null : getTaskViewByTaskId(mIgnoreResetTaskId);
 
         final int requiredTaskCount = tasks.size();
         if (getTaskViewCount() != requiredTaskCount) {
@@ -1195,7 +1207,7 @@
                 removeView(mClearAllButton);
             }
             for (int i = getTaskViewCount(); i < requiredTaskCount; i++) {
-                addView(mTaskViewPool.getView());
+                addView(getTaskViewFromPool());
             }
             while (getTaskViewCount() > requiredTaskCount) {
                 removeView(getChildAt(getChildCount() - 1));
@@ -1205,6 +1217,12 @@
             }
         }
 
+        // Save running task ID if it exists before rebinding all taskViews, otherwise the task from
+        // the runningTaskView currently bound could get assigned to another TaskView
+        // TODO set these type to array and check all taskIDs? Maybe we can get away w/ only one
+        int runningTaskId = getTaskIdsForTaskViewId(mRunningTaskViewId)[0];
+        int focusedTaskId = getTaskIdsForTaskViewId(mFocusedTaskViewId)[0];
+
         // Rebind and reset all task views
         for (int i = requiredTaskCount - 1; i >= 0; i--) {
             final int pageIndex = requiredTaskCount - i - 1 + mTaskViewStartIndex;
@@ -1213,28 +1231,40 @@
             taskView.bind(task, mOrientationState);
         }
 
+        // Keep same previous focused task
+        TaskView newFocusedTaskView = getTaskViewByTaskId(focusedTaskId);
         // If the list changed, maybe the focused task doesn't exist anymore
-        if (getFocusedTaskView() == null && getTaskViewCount() > 0) {
-            mFocusedTaskId = getTaskViewAt(0).getTaskId();
+        if (newFocusedTaskView == null && getTaskViewCount() > 0) {
+            newFocusedTaskView = getTaskViewAt(0);
         }
+        mFocusedTaskViewId = newFocusedTaskView != null ?
+                newFocusedTaskView.getTaskViewId() : -1;
         updateTaskSize();
 
+        TaskView newRunningTaskView = null;
+        if (runningTaskId != -1) {
+            // Update mRunningTaskViewId to be the new TaskView that was assigned by binding
+            // the full list of tasks to taskViews
+            newRunningTaskView = getTaskViewByTaskId(runningTaskId);
+            mRunningTaskViewId = newRunningTaskView.getTaskViewId();
+        }
+
         if (mNextPage == INVALID_PAGE) {
             // Set the current page to the running task, but not if settling on new task.
-            TaskView runningTaskView = getRunningTaskView();
-            if (runningTaskView != null) {
-                setCurrentPage(indexOfChild(runningTaskView));
+            if (runningTaskId != -1) {
+                setCurrentPage(indexOfChild(newRunningTaskView));
             } else if (getTaskViewCount() > 0) {
                 setCurrentPage(indexOfChild(getTaskViewAt(0)));
             }
         } else if (currentTaskId != -1) {
-            currentTaskView = getTaskView(currentTaskId);
+            currentTaskView = getTaskViewByTaskId(currentTaskId);
             if (currentTaskView != null) {
                 setCurrentPage(indexOfChild(currentTaskView));
             }
         }
 
-        if (mIgnoreResetTaskId != -1 && getTaskView(mIgnoreResetTaskId) != ignoreResetTaskView) {
+        if (mIgnoreResetTaskId != -1 &&
+                getTaskViewByTaskId(mIgnoreResetTaskId) != ignoreResetTaskView) {
             // If the taskView mapping is changing, do not preserve the visuals. Since we are
             // mostly preserving the first task, and new taskViews are added to the end, it should
             // generally map to the same task.
@@ -1278,7 +1308,7 @@
     public void resetTaskVisuals() {
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
             TaskView taskView = getTaskViewAt(i);
-            if (mIgnoreResetTaskId != taskView.getTaskId()) {
+            if (mIgnoreResetTaskId != taskView.getTaskIds()[0]) {
                 taskView.resetViewTransforms();
                 taskView.setIconScaleAndDim(mTaskIconScaledDown ? 0 : 1);
                 taskView.setStableAlpha(mContentAlpha);
@@ -1632,7 +1662,7 @@
     private void unloadVisibleTaskData(@TaskView.TaskDataChanges int dataChanges) {
         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
             if (mHasVisibleTaskData.valueAt(i)) {
-                TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
+                TaskView taskView = getTaskViewByTaskId(mHasVisibleTaskData.keyAt(i));
                 if (taskView != null) {
                     taskView.onTaskListVisibilityChanged(false /* visible */, dataChanges);
                 }
@@ -1647,7 +1677,7 @@
         // they want to updated their thumbnail state
         for (int i = 0; i < mHasVisibleTaskData.size(); i++) {
             if (mHasVisibleTaskData.valueAt(i)) {
-                TaskView taskView = getTaskView(mHasVisibleTaskData.keyAt(i));
+                TaskView taskView = getTaskViewByTaskId(mHasVisibleTaskData.keyAt(i));
                 if (taskView != null) {
                     // Poke the view again, which will trigger it to load high res if the state
                     // is enabled
@@ -1668,7 +1698,7 @@
         setCurrentTask(-1);
         mIgnoreResetTaskId = -1;
         mTaskListChangeId = -1;
-        mFocusedTaskId = getTaskViewCount() > 0 ? getTaskViewAt(0).getTaskId() : -1;
+        mFocusedTaskViewId = getTaskViewCount() > 0 ? getTaskViewAt(0).getTaskViewId() : -1;
 
         if (mRecentsAnimationController != null) {
             if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile) {
@@ -1693,20 +1723,49 @@
         });
     }
 
-    public int getRunningTaskId() {
-        return mRunningTaskId;
+    public int getRunningTaskViewId() {
+        return mRunningTaskViewId;
+    }
+
+    protected int[] getTaskIdsForRunningTaskView() {
+        return getTaskIdsForTaskViewId(mRunningTaskViewId);
+    }
+
+    private int[] getTaskIdsForTaskViewId(int taskViewId) {
+        // For now 2 distinct task IDs is max for split screen
+        TaskView runningTaskView = getTaskViewFromTaskViewId(taskViewId);
+        if (runningTaskView == null) {
+            return INVALID_TASK_IDS;
+        }
+
+        return runningTaskView.getTaskIds();
     }
 
     public @Nullable TaskView getRunningTaskView() {
-        return getTaskView(mRunningTaskId);
-    }
-
-    public int getRunningTaskIndex() {
-        return getTaskIndexForId(mRunningTaskId);
+        return getTaskViewFromTaskViewId(mRunningTaskViewId);
     }
 
     public @Nullable TaskView getFocusedTaskView() {
-        return getTaskView(mFocusedTaskId);
+        return getTaskViewFromTaskViewId(mFocusedTaskViewId);
+    }
+
+    private TaskView getTaskViewFromTaskViewId(int taskViewId) {
+        if (taskViewId == -1) {
+            return null;
+        }
+
+        for (int i = 0; i < getTaskViewCount(); i++) {
+            TaskView taskView = getTaskViewAt(i);
+            if (taskView.getTaskViewId() == taskViewId) {
+                return taskView;
+            }
+        }
+        return null;
+    }
+
+    public int getRunningTaskIndex() {
+        TaskView taskView = getRunningTaskView();
+        return taskView == null ? -1 : indexOfChild(taskView);
     }
 
     protected @Nullable TaskView getHomeTaskView() {
@@ -1714,11 +1773,27 @@
     }
 
     /**
+     * Handle the edge case where Recents could increment task count very high over long
+     * period of device usage. Probably will never happen, but meh.
+     */
+    private TaskView getTaskViewFromPool() {
+        TaskView taskView = mTaskViewPool.getView();
+        taskView.setTaskViewId(mTaskViewIdCount);
+        if (mTaskViewIdCount == Integer.MAX_VALUE) {
+            mTaskViewIdCount = 0;
+        } else {
+            mTaskViewIdCount++;
+        }
+
+        return taskView;
+    }
+
+    /**
      * Get the index of the task view whose id matches {@param taskId}.
      * @return -1 if there is no task view for the task id, else the index of the task view.
      */
     public int getTaskIndexForId(int taskId) {
-        TaskView tv = getTaskView(taskId);
+        TaskView tv = getTaskViewByTaskId(taskId);
         return tv == null ? -1 : indexOfChild(tv);
     }
 
@@ -1858,7 +1933,7 @@
      * Returns true if we should add a stub taskView for the running task id
      */
     protected boolean shouldAddStubTaskView(RunningTaskInfo runningTaskInfo) {
-        return runningTaskInfo != null && getTaskView(runningTaskInfo.taskId) == null;
+        return runningTaskInfo != null && getTaskViewByTaskId(runningTaskInfo.taskId) == null;
     }
 
     /**
@@ -1868,10 +1943,12 @@
      * is called.  Also scrolls the view to this task.
      */
     public void showCurrentTask(RunningTaskInfo runningTaskInfo) {
+        int runningTaskViewId = -1;
         if (shouldAddStubTaskView(runningTaskInfo)) {
             boolean wasEmpty = getChildCount() == 0;
             // Add an empty view for now until the task plan is loaded and applied
-            final TaskView taskView = mTaskViewPool.getView();
+            final TaskView taskView = getTaskViewFromPool();
+            runningTaskViewId = taskView.getTaskViewId();
             addView(taskView, mTaskViewStartIndex);
             if (wasEmpty) {
                 addView(mClearAllButton);
@@ -1886,12 +1963,13 @@
             measure(makeMeasureSpec(getMeasuredWidth(), EXACTLY),
                     makeMeasureSpec(getMeasuredHeight(), EXACTLY));
             layout(getLeft(), getTop(), getRight(), getBottom());
+        } else if (getTaskViewByTaskId(runningTaskInfo.taskId) != null) {
+            runningTaskViewId = getTaskViewByTaskId(runningTaskInfo.taskId).getTaskViewId();
         }
 
         boolean runningTaskTileHidden = mRunningTaskTileHidden;
-        int runningTaskId = runningTaskInfo == null ? -1 : runningTaskInfo.taskId;
-        setCurrentTask(runningTaskId);
-        mFocusedTaskId = runningTaskId;
+        setCurrentTask(runningTaskViewId);
+        mFocusedTaskViewId = runningTaskViewId;
         setCurrentPage(getRunningTaskIndex());
         setRunningTaskViewShowScreenshot(false);
         setRunningTaskHidden(runningTaskTileHidden);
@@ -1905,22 +1983,27 @@
     /**
      * Sets the running task id, cleaning up the old running task if necessary.
      */
-    public void setCurrentTask(int runningTaskId) {
-        if (mRunningTaskId == runningTaskId) {
+    public void setCurrentTask(int runningTaskViewId) {
+        if (mRunningTaskViewId == runningTaskViewId) {
             return;
         }
 
-        if (mRunningTaskId != -1) {
+        if (mRunningTaskViewId != -1) {
             // Reset the state on the old running task view
             setTaskIconScaledDown(false);
             setRunningTaskViewShowScreenshot(true);
             setRunningTaskHidden(false);
         }
-        mRunningTaskId = runningTaskId;
+        mRunningTaskViewId = runningTaskViewId;
+    }
+
+    private int getTaskViewIdFromTaskId(int taskId) {
+        TaskView taskView = getTaskViewByTaskId(taskId);
+        return taskView != null ? taskView.getTaskViewId() : -1;
     }
 
     /**
-     * Hides the tile associated with {@link #mRunningTaskId}
+     * Hides the tile associated with {@link #mRunningTaskViewId}
      */
     public void setRunningTaskHidden(boolean isHidden) {
         mRunningTaskTileHidden = isHidden;
@@ -2001,7 +2084,7 @@
         float bottomAccumulatedTranslationX = 0;
 
         // Contains whether the child index is in top or bottom of grid (for non-focused task)
-        // Different from mTopRowIdSet, which contains the taskId of what task is in top row
+        // Different from mTopRowIdSet, which contains the taskViewId of what task is in top row
         IntSet topSet = new IntSet();
         IntSet bottomSet = new IntSet();
 
@@ -2053,8 +2136,8 @@
                     // calculate the distance focused task need to shift.
                     focusedTaskShift += mIsRtl ? taskWidthAndSpacing : -taskWidthAndSpacing;
                 }
-                int taskId = taskView.getTaskId();
-                boolean isTopRow = isTaskDismissal ? mTopRowIdSet.contains(taskId)
+                int taskViewId = taskView.getTaskViewId();
+                boolean isTopRow = isTaskDismissal ? mTopRowIdSet.contains(taskViewId)
                         : topRowWidth <= bottomRowWidth;
                 if (isTopRow) {
                     if (homeTaskView != null && nextFocusedTaskView == null) {
@@ -2064,7 +2147,7 @@
                         topRowWidth += taskWidthAndSpacing;
                     }
                     topSet.add(i);
-                    mTopRowIdSet.add(taskId);
+                    mTopRowIdSet.add(taskViewId);
 
                     taskView.setGridTranslationY(mTaskGridVerticalDiff);
 
@@ -2189,13 +2272,13 @@
         if (taskView1 == null || taskView2 == null) {
             return false;
         }
-        int taskId1 = taskView1.getTaskId();
-        int taskId2 = taskView2.getTaskId();
-        if (taskId1 == mFocusedTaskId || taskId2 == mFocusedTaskId) {
+        int taskViewId1 = taskView1.getTaskViewId();
+        int taskViewId2 = taskView2.getTaskViewId();
+        if (taskViewId1 == mFocusedTaskViewId || taskViewId2 == mFocusedTaskViewId) {
             return false;
         }
-        return (mTopRowIdSet.contains(taskId1) && mTopRowIdSet.contains(taskId2)) || (
-                !mTopRowIdSet.contains(taskId1) && !mTopRowIdSet.contains(taskId2));
+        return (mTopRowIdSet.contains(taskViewId1) && mTopRowIdSet.contains(taskViewId2)) || (
+                !mTopRowIdSet.contains(taskViewId1) && !mTopRowIdSet.contains(taskViewId2));
     }
 
     /**
@@ -2342,7 +2425,8 @@
         boolean showAsGrid = showAsGrid();
         int taskCount = getTaskViewCount();
         int dismissedIndex = indexOfChild(dismissedTaskView);
-        int dismissedTaskId = dismissedTaskView.getTaskId();
+        int dismissedTaskId = dismissedTaskView.getTaskIds()[0];
+        int dismissedTaskViewId = dismissedTaskView.getTaskViewId();
 
         // Grid specific properties.
         boolean isFocusedTaskDismissed = false;
@@ -2359,7 +2443,7 @@
 
         if (showAsGrid) {
             dismissedTaskWidth = dismissedTaskView.getLayoutParams().width + mPageSpacing;
-            isFocusedTaskDismissed = dismissedTaskId == mFocusedTaskId;
+            isFocusedTaskDismissed = dismissedTaskViewId == mFocusedTaskViewId;
             if (isFocusedTaskDismissed) {
                 nextFocusedTaskFromTop =
                         mTopRowIdSet.size() > 0 && mTopRowIdSet.size() >= (taskCount - 1) / 2f;
@@ -2369,7 +2453,7 @@
                     if (taskView == dismissedTaskView) {
                         continue;
                     }
-                    boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskId());
+                    boolean isTopRow = mTopRowIdSet.contains(taskView.getTaskViewId());
                     if ((nextFocusedTaskFromTop && isTopRow
                             || (!nextFocusedTaskFromTop && !isTopRow))) {
                         nextFocusedTaskView = taskView;
@@ -2548,7 +2632,7 @@
                         mCurrentPageScrollDiff = primaryScroll - currentPageScroll;
                     }
                     removeViewInLayout(dismissedTaskView);
-                    mTopRowIdSet.remove(dismissedTaskId);
+                    mTopRowIdSet.remove(dismissedTaskViewId);
 
                     if (taskCount == 1) {
                         removeViewInLayout(mClearAllButton);
@@ -2556,8 +2640,8 @@
                     } else {
                         // Update focus task and its size.
                         if (finalNextFocusedTaskView != null) {
-                            mFocusedTaskId = finalNextFocusedTaskView.getTaskId();
-                            mTopRowIdSet.remove(mFocusedTaskId);
+                            mFocusedTaskViewId = finalNextFocusedTaskView.getTaskViewId();
+                            mTopRowIdSet.remove(mFocusedTaskViewId);
                             finalNextFocusedTaskView.animateIconScaleAndDimIntoView();
                         }
                         updateTaskSize(true);
@@ -2676,7 +2760,7 @@
 
     @UiThread
     private void dismissTask(int taskId) {
-        TaskView taskView = getTaskView(taskId);
+        TaskView taskView = getTaskViewByTaskId(taskId);
         if (taskView == null) {
             return;
         }
@@ -2755,9 +2839,12 @@
         }
         alpha = Utilities.boundToRange(alpha, 0, 1);
         mContentAlpha = alpha;
+        int runningTaskId = getTaskIdsForRunningTaskView()[0];
         for (int i = getTaskViewCount() - 1; i >= 0; i--) {
             TaskView child = getTaskViewAt(i);
-            if (!mRunningTaskTileHidden || child.getTaskId() != mRunningTaskId) {
+            int[] childTaskIds = child.getTaskIds();
+            if (!mRunningTaskTileHidden ||
+                    (childTaskIds[0] != runningTaskId && childTaskIds[1] != runningTaskId)) {
                 child.setStableAlpha(alpha);
             }
         }
@@ -2916,8 +3003,8 @@
         float modalOffset = ACCEL_0_75.getInterpolation(mTaskModalness);
         int count = getChildCount();
 
-        TaskView runningTask = mRunningTaskId == -1 || !mRunningTaskTileHidden
-                ? null : getTaskView(mRunningTaskId);
+        TaskView runningTask = mRunningTaskViewId == -1 || !mRunningTaskTileHidden
+                ? null : getRunningTaskView();
         int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
         int modalMidpoint = getCurrentPage();
 
@@ -3884,8 +3971,9 @@
             }
             return;
         }
-        switchToScreenshot(mRunningTaskId == -1 ? null
-                : mRecentsAnimationController.screenshotTask(mRunningTaskId), onFinishRunnable);
+        int runningTaskId = getTaskIdsForRunningTaskView()[0];
+        switchToScreenshot(mRunningTaskViewId == -1 ? null
+                : mRecentsAnimationController.screenshotTask(runningTaskId), onFinishRunnable);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 5608fc7..ec08280 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -366,6 +366,9 @@
     private float mModalness = 0;
     private float mStableAlpha = 1;
 
+    private int mTaskViewId = -1;
+    private final int[] mTaskIdContainer = new int[]{-1, -1};
+
     private boolean mShowScreenshot;
 
     // The current background requests to load the task thumbnail and icon
@@ -401,6 +404,14 @@
         setOutlineProvider(mOutlineProvider);
     }
 
+    public void setTaskViewId(int id) {
+        this.mTaskViewId = id;
+    }
+
+    public int getTaskViewId() {
+        return mTaskViewId;
+    }
+
     /**
      * Builds proto for logging
      */
@@ -506,6 +517,7 @@
     public void bind(Task task, RecentsOrientedState orientedState) {
         cancelPendingLoadTasks();
         mTask = task;
+        mTaskIdContainer[0] = mTask.key.id;
         mSnapshotView.bind(task);
         setOrientationState(orientedState);
     }
@@ -514,8 +526,12 @@
         return mTask;
     }
 
-    public int getTaskId() {
-        return mTask != null && mTask.key != null ? mTask.key.id : -1;
+    /**
+     * @return integer array of two elements to be size consistent with max number of tasks possible
+     *         index 0 will contain the taskId, index 1 will be -1 indicating a null taskID value
+     */
+    public int[] getTaskIds() {
+        return mTaskIdContainer;
     }
 
     public TaskThumbnailView getThumbnail() {
@@ -598,7 +614,8 @@
             ActivityOptionsWrapper opts =  mActivity.getActivityLaunchOptions(this, null);
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
-                if (ENABLE_QUICKSTEP_LIVE_TILE.get() && getRecentsView().getRunningTaskId() != -1) {
+                if (ENABLE_QUICKSTEP_LIVE_TILE.get() &&
+                        getRecentsView().getRunningTaskViewId() != -1) {
                     // Return a fresh callback in the live tile case, so that it's not accidentally
                     // triggered by QuickstepTransitionManager.AppLaunchAnimationRunner.
                     RunnableList callbackList = new RunnableList();
diff --git a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
index dc73a9a..ca47de3 100644
--- a/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
+++ b/quickstep/tests/src/com/android/quickstep/AbstractQuickStepTest.java
@@ -82,6 +82,6 @@
 
         RecentsView recentsView = launcher.getOverviewPanel();
         return recentsView.getSizeStrategy().isInLiveTileMode()
-                && recentsView.getRunningTaskId() != -1;
+                && recentsView.getRunningTaskViewId() != -1;
     }
 }
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index fef9304..3e84a76 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -16,7 +16,6 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.launcher3.Launcher;
-import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.quickstep.views.DigitalWellBeingToast;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
@@ -33,7 +32,6 @@
             resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR);
 
     @Test
-    @ScreenRecord //b/193440212
     public void testToast() throws Exception {
         startAppFast(CALCULATOR_PACKAGE);
 
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index 20b4715..e4f5a19 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -99,6 +99,7 @@
         mDevice = UiDevice.getInstance(instrumentation);
         mDevice.setOrientationNatural();
         mLauncher = new LauncherInstrumentation();
+        mLauncher.enableDebugTracing();
         // b/143488140
         //mLauncher.enableCheckEventsForSuccessfulGestures();
 
@@ -214,7 +215,7 @@
     // b/143488140
     //@NavigationModeSwitch
     @Test
-    @ScreenRecord // b/194484556
+    @ScreenRecord // b/187080582
     public void testOverview() {
         startAppFast(getAppPackageName());
         startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index ea69b94..164e755 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -93,7 +93,6 @@
 
     @Test
     @PortraitLandscape
-    @ScreenRecord //b/193440212
     public void testOverview() throws Exception {
         startTestAppsWithCheck();
         // mLauncher.pressHome() also tests an important case of pressing home while in background.
@@ -159,6 +158,7 @@
     @Test
     @NavigationModeSwitch
     @PortraitLandscape
+    @ScreenRecord //b/193125090
     public void testOverviewActions() throws Exception {
         // Experimenting for b/165029151:
         final Overview overview = mLauncher.pressHome().switchToOverview();
diff --git a/res/layout/system_shortcut.xml b/res/layout/system_shortcut.xml
index de091c5..89895e5 100644
--- a/res/layout/system_shortcut.xml
+++ b/res/layout/system_shortcut.xml
@@ -16,36 +16,12 @@
 
 <com.android.launcher3.shortcuts.DeepShortcutView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:launcher="http://schemas.android.com/apk/res-auto"
     android:layout_width="@dimen/bg_popup_item_width"
     android:layout_height="@dimen/bg_popup_item_height"
     android:elevation="@dimen/deep_shortcuts_elevation"
     android:background="@drawable/middle_item_primary"
     android:theme="@style/PopupItem" >
 
-    <com.android.launcher3.BubbleTextView
-        style="@style/BaseIconUnBounded"
-        android:id="@+id/bubble_text"
-        android:background="?android:attr/selectableItemBackground"
-        android:gravity="start|center_vertical"
-        android:textAlignment="viewStart"
-        android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
-        android:paddingEnd="@dimen/popup_padding_end"
-        android:textSize="14sp"
-        android:minLines="1"
-        android:maxLines="2"
-        android:ellipsize="end"
-        android:textColor="?android:attr/textColorPrimary"
-        launcher:iconDisplay="shortcut_popup"
-        launcher:layoutHorizontal="true"
-        android:focusable="false" />
-
-    <View
-        android:id="@+id/icon"
-        android:layout_width="@dimen/system_shortcut_icon_size"
-        android:layout_height="@dimen/system_shortcut_icon_size"
-        android:layout_marginStart="@dimen/system_shortcut_margin_start"
-        android:layout_gravity="start|center_vertical"
-        android:backgroundTint="?android:attr/textColorPrimary"/>
+    <include layout="@layout/system_shortcut_content" />
 
 </com.android.launcher3.shortcuts.DeepShortcutView>
diff --git a/res/layout/system_shortcut_content.xml b/res/layout/system_shortcut_content.xml
new file mode 100644
index 0000000..8b39202
--- /dev/null
+++ b/res/layout/system_shortcut_content.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<merge
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:launcher="http://schemas.android.com/apk/res-auto" >
+
+    <com.android.launcher3.BubbleTextView
+        style="@style/BaseIconUnBounded"
+        android:id="@+id/bubble_text"
+        android:background="?android:attr/selectableItemBackground"
+        android:gravity="start|center_vertical"
+        android:textAlignment="viewStart"
+        android:paddingStart="@dimen/deep_shortcuts_text_padding_start"
+        android:paddingEnd="@dimen/popup_padding_end"
+        android:textSize="14sp"
+        android:minLines="1"
+        android:maxLines="2"
+        android:ellipsize="end"
+        android:textColor="?android:attr/textColorPrimary"
+        launcher:iconDisplay="shortcut_popup"
+        launcher:layoutHorizontal="true"
+        android:focusable="false" />
+
+    <View
+        android:id="@+id/icon"
+        android:layout_width="@dimen/system_shortcut_icon_size"
+        android:layout_height="@dimen/system_shortcut_icon_size"
+        android:layout_marginStart="@dimen/system_shortcut_margin_start"
+        android:layout_gravity="start|center_vertical"
+        android:backgroundTint="?android:attr/textColorPrimary"/>
+</merge>
\ No newline at end of file
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index b5b8b62..2b8b9fa 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -122,7 +122,7 @@
     <string name="allow_rotation_title" msgid="7728578836261442095">"السماح بتدوير الشاشة الرئيسية"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"عند تدوير الهاتف"</string>
     <string name="notification_dots_title" msgid="9062440428204120317">"نقاط الإشعارات"</string>
-    <string name="notification_dots_desc_on" msgid="1679848116452218908">"مفعّل"</string>
+    <string name="notification_dots_desc_on" msgid="1679848116452218908">"مفعّلة"</string>
     <string name="notification_dots_desc_off" msgid="1760796511504341095">"غير مفعّل"</string>
     <string name="title_missing_notification_access" msgid="7503287056163941064">"يلزم تمكين الوصول إلى الإشعارات"</string>
     <string name="msg_missing_notification_access" msgid="281113995110910548">"لعرض نقاط الإشعارات، يجب تفعيل إشعارات التطبيق في <xliff:g id="NAME">%1$s</xliff:g>"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index d3558ec..673a446 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -105,7 +105,7 @@
     <string name="folder_name_format_overflow" msgid="4270108890534995199">"Folder: <xliff:g id="NAME">%1$s</xliff:g>, <xliff:g id="SIZE">%2$d</xliff:g> item atau lebih"</string>
     <string name="wallpaper_button_text" msgid="8404103075899945851">"Wallpaper"</string>
     <string name="styles_wallpaper_button_text" msgid="8216961355289236794">"Wallpaper &amp; gaya"</string>
-    <string name="settings_button_text" msgid="8873672322605444408">"Setelan layar utama"</string>
+    <string name="settings_button_text" msgid="8873672322605444408">"Setelan Layar utama"</string>
     <string name="msg_disabled_by_admin" msgid="6898038085516271325">"Dinonaktifkan oleh admin"</string>
     <string name="allow_rotation_title" msgid="7728578836261442095">"Izinkan Layar utama diputar"</string>
     <string name="allow_rotation_desc" msgid="8662546029078692509">"Saat ponsel diputar"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 85d7db6..2415542 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -75,7 +75,7 @@
     <string name="all_apps_button_label" msgid="8130441508702294465">"Список приложений"</string>
     <string name="all_apps_button_personal_label" msgid="1315764287305224468">"Открыть список личных приложений"</string>
     <string name="all_apps_button_work_label" msgid="7270707118948892488">"Открыть список приложений для работы"</string>
-    <string name="remove_drop_target_label" msgid="7812859488053230776">"Убрать"</string>
+    <string name="remove_drop_target_label" msgid="7812859488053230776">"Удалить"</string>
     <string name="uninstall_drop_target_label" msgid="4722034217958379417">"Удалить"</string>
     <string name="app_info_drop_target_label" msgid="692894985365717661">"О приложении"</string>
     <string name="install_drop_target_label" msgid="2539096853673231757">"Установить"</string>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 010d15b..838d0ec 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -29,7 +29,8 @@
     <dimen name="dynamic_grid_cell_layout_padding">5.5dp</dimen>
     <dimen name="dynamic_grid_cell_padding_x">8dp</dimen>
 
-    <dimen name="two_panel_home_side_padding">18dp</dimen>
+    <dimen name="two_panels_home_side_padding_landscape">36dp</dimen>
+    <dimen name="two_panels_home_side_padding_portrait">9dp</dimen>
 
     <!-- Hotseat -->
     <dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java b/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
deleted file mode 100644
index 56ce215..0000000
--- a/robolectric_tests/src/com/android/launcher3/model/GridBackupTableTest.java
+++ /dev/null
@@ -1,135 +0,0 @@
-package com.android.launcher3.model;
-
-
-import static android.database.DatabaseUtils.queryNumEntries;
-
-import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
-import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
-import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
-import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
-import static com.android.launcher3.util.LauncherModelHelper.DESKTOP;
-import static com.android.launcher3.util.LauncherModelHelper.NO__ICON;
-import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
-
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-/**
- * Unit tests for {@link GridBackupTable}
- */
-@RunWith(RobolectricTestRunner.class)
-public class GridBackupTableTest {
-
-    private static final int BACKUP_ITEM_COUNT = 12;
-
-    private LauncherModelHelper mModelHelper;
-    private Context mContext;
-    private SQLiteDatabase mDb;
-
-    @Before
-    public void setUp() {
-        mModelHelper = new LauncherModelHelper();
-        mContext = RuntimeEnvironment.application;
-        mDb = mModelHelper.provider.getDb();
-
-        setupGridData();
-    }
-
-    private void setupGridData() {
-        mModelHelper.createGrid(new int[][][]{{
-                { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
-                { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
-                { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
-                { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
-        }});
-        assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
-    }
-
-    @Test
-    public void backupTableCreated() {
-        GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 4, 4, 4);
-        assertFalse(backupTable.backupOrRestoreAsNeeded());
-        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
-
-        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
-
-        // One extra entry for properties
-        assertEquals(BACKUP_ITEM_COUNT + 1, queryNumEntries(mDb, BACKUP_TABLE_NAME));
-    }
-
-    @Test
-    public void backupTableRestored() {
-        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
-        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
-
-        // Delete entries
-        mDb.delete(TABLE_NAME, null, null);
-        assertEquals(0, queryNumEntries(mDb, TABLE_NAME));
-
-        GridBackupTable backupTable = new GridBackupTable(mContext, mDb, 3, 3, 3);
-        assertTrue(backupTable.backupOrRestoreAsNeeded());
-
-        // Items have been restored
-        assertEquals(BACKUP_ITEM_COUNT, queryNumEntries(mDb, TABLE_NAME));
-
-        Point outSize = new Point();
-        assertEquals(4, backupTable.getRestoreHotseatAndGridSize(outSize));
-        assertEquals(4, outSize.x);
-        assertEquals(4, outSize.y);
-    }
-
-    @Test
-    public void backupTableRemovedOnAdd() {
-        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
-        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
-
-        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
-
-        mModelHelper.addItem(1, 2, DESKTOP, 1, 1);
-        assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
-    }
-
-    @Test
-    public void backupTableRemovedOnDelete() {
-        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
-        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
-
-        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
-
-        mContext.getContentResolver().delete(Favorites.CONTENT_URI, null, null);
-        assertFalse(tableExists(mDb, BACKUP_TABLE_NAME));
-    }
-
-    @Test
-    public void backupTableRetainedOnUpdate() {
-        assertFalse(new GridBackupTable(mContext, mDb, 4, 4, 4).backupOrRestoreAsNeeded());
-        Settings.call(mContext.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
-
-        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
-
-        ContentValues values = new ContentValues();
-        values.put(Favorites.RANK, 4);
-        // Something was updated
-        assertTrue(mContext.getContentResolver()
-                .update(Favorites.CONTENT_URI, values, null, null) > 0);
-
-        // Backup table remains
-        assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
-    }
-}
diff --git a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java b/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
deleted file mode 100644
index d544a0b..0000000
--- a/robolectric_tests/src/com/android/launcher3/model/GridSizeMigrationTaskTest.java
+++ /dev/null
@@ -1,373 +0,0 @@
-package com.android.launcher3.model;
-
-import static com.android.launcher3.model.GridSizeMigrationTask.getWorkspaceScreenIds;
-import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
-import static com.android.launcher3.util.LauncherModelHelper.HOTSEAT;
-import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
-import static com.android.launcher3.util.LauncherModelHelper.TEST_PACKAGE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.GridSizeMigrationTask.MultiStepMigrationTask;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.LauncherModelHelper;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
-
-import java.util.HashSet;
-import java.util.LinkedList;
-
-/**
- * Unit tests for {@link GridSizeMigrationTask}
- */
-@RunWith(RobolectricTestRunner.class)
-public class GridSizeMigrationTaskTest {
-
-    private LauncherModelHelper mModelHelper;
-    private Context mContext;
-    private SQLiteDatabase mDb;
-
-    private HashSet<String> mValidPackages;
-    private InvariantDeviceProfile mIdp;
-
-    @Before
-    public void setUp() {
-        mModelHelper = new LauncherModelHelper();
-        mContext = RuntimeEnvironment.application;
-        mDb = mModelHelper.provider.getDb();
-
-        mValidPackages = new HashSet<>();
-        mValidPackages.add(TEST_PACKAGE);
-        mIdp = InvariantDeviceProfile.INSTANCE.get(mContext);
-    }
-
-    @Test
-    public void testHotseatMigration_apps_dropped() throws Exception {
-        int[] hotseatItems = {
-                mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0),
-                mModelHelper.addItem(SHORTCUT, 1, HOTSEAT, 0, 0),
-                -1,
-                mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
-                mModelHelper.addItem(APP_ICON, 4, HOTSEAT, 0, 0),
-        };
-
-        mIdp.numDatabaseHotseatIcons = 3;
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
-                .migrateHotseat();
-        // First item is dropped as it has the least weight.
-        verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
-    }
-
-    @Test
-    public void testHotseatMigration_shortcuts_dropped() throws Exception {
-        int[] hotseatItems = {
-                mModelHelper.addItem(APP_ICON, 0, HOTSEAT, 0, 0),
-                mModelHelper.addItem(30, 1, HOTSEAT, 0, 0),
-                -1,
-                mModelHelper.addItem(SHORTCUT, 3, HOTSEAT, 0, 0),
-                mModelHelper.addItem(10, 4, HOTSEAT, 0, 0),
-        };
-
-        mIdp.numDatabaseHotseatIcons = 3;
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false, 5, 3)
-                .migrateHotseat();
-        // First item is dropped as it has the least weight.
-        verifyHotseat(hotseatItems[1], hotseatItems[3], hotseatItems[4]);
-    }
-
-    private void verifyHotseat(int... sortedIds) {
-        int screenId = 0;
-        int total = 0;
-
-        for (int id : sortedIds) {
-            Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
-                    new String[]{LauncherSettings.Favorites._ID},
-                    "container=-101 and screen=" + screenId, null, null, null);
-
-            if (id == -1) {
-                assertEquals(0, c.getCount());
-            } else {
-                assertEquals(1, c.getCount());
-                c.moveToNext();
-                assertEquals(id, c.getLong(0));
-                total ++;
-            }
-            c.close();
-
-            screenId++;
-        }
-
-        // Verify that not other entry exist in the DB.
-        Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[]{LauncherSettings.Favorites._ID},
-                "container=-101", null, null, null);
-        assertEquals(total, c.getCount());
-        c.close();
-    }
-
-    @Test
-    public void testWorkspace_empty_row_column_removed() throws Exception {
-        int[][][] ids = mModelHelper.createGrid(new int[][][]{{
-                {  0,  0, -1,  1},
-                {  3,  1, -1,  4},
-                { -1, -1, -1, -1},
-                {  5,  2, -1,  6},
-        }});
-
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
-                new Point(4, 4), new Point(3, 3)).migrateWorkspace();
-
-        // Column 2 and row 2 got removed.
-        verifyWorkspace(new int[][][] {{
-                {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
-                {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
-                {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
-        }});
-    }
-
-    @Test
-    public void testWorkspace_new_screen_created() throws Exception {
-        int[][][] ids = mModelHelper.createGrid(new int[][][]{{
-                {  0,  0,  0,  1},
-                {  3,  1,  0,  4},
-                { -1, -1, -1, -1},
-                {  5,  2, -1,  6},
-        }});
-
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
-                new Point(4, 4), new Point(3, 3)).migrateWorkspace();
-
-        // Items in the second column get moved to new screen
-        verifyWorkspace(new int[][][] {{
-                {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
-                {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
-                {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
-        }, {
-                {ids[0][0][2], ids[0][1][2], -1},
-        }});
-    }
-
-    @Test
-    public void testWorkspace_items_merged_in_next_screen() throws Exception {
-        int[][][] ids = mModelHelper.createGrid(new int[][][]{{
-                {  0,  0,  0,  1},
-                {  3,  1,  0,  4},
-                { -1, -1, -1, -1},
-                {  5,  2, -1,  6},
-        },{
-                {  0,  0, -1,  1},
-                {  3,  1, -1,  4},
-        }});
-
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
-                new Point(4, 4), new Point(3, 3)).migrateWorkspace();
-
-        // Items in the second column of the first screen should get placed on the 3rd
-        // row of the second screen
-        verifyWorkspace(new int[][][] {{
-                {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
-                {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
-                {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
-        }, {
-                {ids[1][0][0], ids[1][0][1], ids[1][0][3]},
-                {ids[1][1][0], ids[1][1][1], ids[1][1][3]},
-                {ids[0][0][2], ids[0][1][2], -1},
-        }});
-    }
-
-    @Test
-    public void testWorkspace_items_not_merged_in_next_screen() throws Exception {
-        // First screen has 2 mItems that need to be moved, but second screen has only one
-        // empty space after migration (top-left corner)
-        int[][][] ids = mModelHelper.createGrid(new int[][][]{{
-                {  0,  0,  0,  1},
-                {  3,  1,  0,  4},
-                { -1, -1, -1, -1},
-                {  5,  2, -1,  6},
-        },{
-                { -1,  0, -1,  1},
-                {  3,  1, -1,  4},
-                { -1, -1, -1, -1},
-                {  5,  2, -1,  6},
-        }});
-
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
-                new Point(4, 4), new Point(3, 3)).migrateWorkspace();
-
-        // Items in the second column of the first screen should get placed on a new screen.
-        verifyWorkspace(new int[][][] {{
-                {ids[0][0][0], ids[0][0][1], ids[0][0][3]},
-                {ids[0][1][0], ids[0][1][1], ids[0][1][3]},
-                {ids[0][3][0], ids[0][3][1], ids[0][3][3]},
-        }, {
-                {          -1, ids[1][0][1], ids[1][0][3]},
-                {ids[1][1][0], ids[1][1][1], ids[1][1][3]},
-                {ids[1][3][0], ids[1][3][1], ids[1][3][3]},
-        }, {
-                {ids[0][0][2], ids[0][1][2], -1},
-        }});
-    }
-
-    @Test
-    public void testWorkspace_first_row_blocked() throws Exception {
-        if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
-            return;
-        }
-        // The first screen has one item on the 4th column which needs moving, as the first row
-        // will be kept empty.
-        int[][][] ids = mModelHelper.createGrid(new int[][][]{{
-                { -1, -1, -1, -1},
-                {  3,  1,  7,  0},
-                {  8,  7,  7, -1},
-                {  5,  2,  7, -1},
-        }}, 0);
-
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
-                new Point(4, 4), new Point(3, 4)).migrateWorkspace();
-
-        // Items in the second column of the first screen should get placed on a new screen.
-        verifyWorkspace(new int[][][] {{
-                {          -1,           -1,           -1},
-                {ids[0][1][0], ids[0][1][1], ids[0][1][2]},
-                {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
-                {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
-        }, {
-                {ids[0][1][3]},
-        }});
-    }
-
-    @Test
-    public void testWorkspace_items_moved_to_empty_first_row() throws Exception {
-        if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
-            return;
-        }
-        // Items will get moved to the next screen to keep the first screen empty.
-        int[][][] ids = mModelHelper.createGrid(new int[][][]{{
-                { -1, -1, -1, -1},
-                {  0,  1,  0,  0},
-                {  8,  7,  7, -1},
-                {  5,  6,  7, -1},
-        }}, 0);
-
-        new GridSizeMigrationTask(mContext, mDb, mValidPackages, false,
-                new Point(4, 4), new Point(3, 3)).migrateWorkspace();
-
-        // Items in the second column of the first screen should get placed on a new screen.
-        verifyWorkspace(new int[][][] {{
-                {          -1,           -1,           -1},
-                {ids[0][2][0], ids[0][2][1], ids[0][2][2]},
-                {ids[0][3][0], ids[0][3][1], ids[0][3][2]},
-        }, {
-                {ids[0][1][1], ids[0][1][0], ids[0][1][2]},
-                {ids[0][1][3]},
-        }});
-    }
-
-    /**
-     * Verifies that the workspace mItems are arranged in the provided order.
-     * @param ids A 3d array where the first dimension represents the screen, and the rest two
-     *            represent the workspace grid.
-     */
-    private void verifyWorkspace(int[][][] ids) {
-        IntArray allScreens = getWorkspaceScreenIds(mDb, LauncherSettings.Favorites.TABLE_NAME);
-        assertEquals(ids.length, allScreens.size());
-        int total = 0;
-
-        for (int i = 0; i < ids.length; i++) {
-            int screenId = allScreens.get(i);
-            for (int y = 0; y < ids[i].length; y++) {
-                for (int x = 0; x < ids[i][y].length; x++) {
-                    int id = ids[i][y][x];
-
-                    Cursor c = mContext.getContentResolver().query(
-                            LauncherSettings.Favorites.CONTENT_URI,
-                            new String[]{LauncherSettings.Favorites._ID},
-                            "container=-100 and screen=" + screenId +
-                                    " and cellX=" + x + " and cellY=" + y, null, null, null);
-                    if (id == -1) {
-                        assertEquals(0, c.getCount());
-                    } else {
-                        assertEquals(1, c.getCount());
-                        c.moveToNext();
-                        assertEquals(String.format("Failed to verify item at %d %d, %d", i, y, x),
-                                id, c.getLong(0));
-                        total++;
-                    }
-                    c.close();
-                }
-            }
-        }
-
-        // Verify that not other entry exist in the DB.
-        Cursor c = mContext.getContentResolver().query(LauncherSettings.Favorites.CONTENT_URI,
-                new String[]{LauncherSettings.Favorites._ID},
-                "container=-100", null, null, null);
-        assertEquals(total, c.getCount());
-        c.close();
-    }
-
-    @Test
-    public void testMultiStepMigration_small_to_large() throws Exception {
-        MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier();
-        verifier.migrate(new Point(3, 3), new Point(5, 5));
-        verifier.assertCompleted();
-    }
-
-    @Test
-    public void testMultiStepMigration_large_to_small() throws Exception {
-        MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
-                5, 5, 4, 4,
-                4, 4, 3, 4
-        );
-        verifier.migrate(new Point(5, 5), new Point(3, 4));
-        verifier.assertCompleted();
-    }
-
-    @Test
-    public void testMultiStepMigration_zig_zag() throws Exception {
-        MultiStepMigrationTaskVerifier verifier = new MultiStepMigrationTaskVerifier(
-                5, 7, 4, 7,
-                4, 7, 3, 7
-        );
-        verifier.migrate(new Point(5, 5), new Point(3, 7));
-        verifier.assertCompleted();
-    }
-
-    private static class MultiStepMigrationTaskVerifier extends MultiStepMigrationTask {
-
-        private final LinkedList<Point> mPoints;
-
-        public MultiStepMigrationTaskVerifier(int... points) {
-            super(null, null, null, false);
-
-            mPoints = new LinkedList<>();
-            for (int i = 0; i < points.length; i += 2) {
-                mPoints.add(new Point(points[i], points[i + 1]));
-            }
-        }
-
-        @Override
-        protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
-            assertEquals(sourceSize, mPoints.poll());
-            assertEquals(nextSize, mPoints.poll());
-            return false;
-        }
-
-        public void assertCompleted() {
-            assertTrue(mPoints.isEmpty());
-        }
-    }
-}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 740bf5a..d2d00c8 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -94,6 +94,8 @@
 
     private static final float TALL_DEVICE_ASPECT_RATIO_THRESHOLD = 2.0f;
     private static final float TALLER_DEVICE_ASPECT_RATIO_THRESHOLD = 2.15f;
+    private static final float TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP = 252;
+    private static final float TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP = 268;
 
     // To evenly space the icons, increase the left/right margins for tablets in portrait mode.
     private static final int PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER = 4;
@@ -257,7 +259,8 @@
         final Resources res = context.getResources();
 
         hotseatQsbHeight = res.getDimensionPixelSize(R.dimen.qsb_widget_height);
-        isTaskbarPresent = isTablet && FeatureFlags.ENABLE_TASKBAR.get();
+        isTaskbarPresent = isTablet && ApiWrapper.TASKBAR_DRAWN_IN_PROCESS
+                && FeatureFlags.ENABLE_TASKBAR.get();
         if (isTaskbarPresent) {
             // Taskbar will be added later, but provides bottom insets that we should subtract
             // from availableHeightPx.
@@ -302,8 +305,10 @@
                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
 
         if (isTwoPanels) {
-            cellLayoutPaddingLeftRightPx =
-                    res.getDimensionPixelSize(R.dimen.two_panel_home_side_padding);
+            cellLayoutPaddingLeftRightPx = res.getDimensionPixelSize(
+                    isLandscape
+                            ? R.dimen.two_panels_home_side_padding_landscape
+                            : R.dimen.two_panels_home_side_padding_portrait);
             cellLayoutBottomPaddingPx = 0;
         } else if (isLandscape) {
             cellLayoutPaddingLeftRightPx = 0;
@@ -407,11 +412,17 @@
         } else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
             // We increase the hotseat size when there is extra space.
 
-            if (Float.compare(aspectRatio, TALLER_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0) {
-                // For taller devices, we will take a third of the extra space from each row,
+            if (Float.compare(aspectRatio, TALLER_DEVICE_ASPECT_RATIO_THRESHOLD) >= 0
+                    && extraSpace >= Utilities.dpToPx(TALL_DEVICE_EXTRA_SPACE_THRESHOLD_DP)) {
+                // For taller devices, we will take a piece of the extra space from each row,
                 // and add it to the space above and below the hotseat.
+
+                // For devices with more extra space, we take a larger piece from each cell.
+                int piece = extraSpace < Utilities.dpToPx(TALL_DEVICE_MORE_EXTRA_SPACE_THRESHOLD_DP)
+                        ? 5 : 3;
+
                 int extraSpace = ((getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2)
-                        * inv.numRows) / 3;
+                        * inv.numRows) / piece;
 
                 int halfExtraSpace = extraSpace / 2;
                 hotseatBarTopPaddingPx += halfExtraSpace;
@@ -752,8 +763,14 @@
         // Since we are only concerned with the overall padding, layout direction does
         // not matter.
         Point padding = getTotalWorkspacePadding();
-        result.x = calculateCellWidth(availableWidthPx - padding.x
-                - cellLayoutPaddingLeftRightPx * 2, cellLayoutBorderSpacingPx, inv.numColumns);
+        // availableWidthPx is the screen width of the device. In 2 panels mode, each panel should
+        // only have half of the screen width. In addition, there is only cellLayoutPadding in the
+        // left side of the left panel and the right side of the right panel. There is no
+        // cellLayoutPadding in the middle.
+        int screenWidthPx = isTwoPanels
+                ? availableWidthPx / 2 - padding.x - cellLayoutPaddingLeftRightPx
+                : availableWidthPx - padding.x - cellLayoutPaddingLeftRightPx * 2;
+        result.x = calculateCellWidth(screenWidthPx, cellLayoutBorderSpacingPx, inv.numColumns);
         result.y = calculateCellHeight(availableHeightPx - padding.y
                 - cellLayoutBottomPaddingPx, cellLayoutBorderSpacingPx, inv.numRows);
         return result;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 07c2113..8249887 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -80,6 +80,7 @@
 import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
 import android.graphics.Rect;
+import android.graphics.RectF;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -2870,13 +2871,26 @@
                 if (Utilities.IS_RUNNING_IN_TEST_HARNESS) {
                     Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up");
                 }
-                OptionsPopupView.showDefaultOptions(this, -1, -1);
+                showDefaultOptions(-1, -1);
             }
             return true;
         }
         return super.onKeyUp(keyCode, event);
     }
 
+    /**
+     * Shows the default options popup
+     */
+    public void showDefaultOptions(float x, float y) {
+        float halfSize = getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
+        if (x < 0 || y < 0) {
+            x = mDragLayer.getWidth() / 2;
+            y = mDragLayer.getHeight() / 2;
+        }
+        RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
+        OptionsPopupView.show(this, target, OptionsPopupView.getOptions(this), false);
+    }
+
     @Override
     protected void collectStateHandlers(List<StateHandler> out) {
         out.add(getAllAppsController());
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index a8ed6bc..95a8d81 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3;
 
-import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
 import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
@@ -433,32 +432,26 @@
                 return null;
             }
             case LauncherSettings.Settings.METHOD_UPDATE_CURRENT_OPEN_HELPER: {
-                if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
-                    Bundle result = new Bundle();
-                    result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                            prepForMigration(
-                                    InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile,
-                                    Favorites.TMP_TABLE,
-                                    () -> mOpenHelper,
-                                    () -> DatabaseHelper.createDatabaseHelper(
-                                            getContext(), true /* forMigration */)));
-                    return result;
-                }
-                return null;
+                Bundle result = new Bundle();
+                result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
+                        prepForMigration(
+                                InvariantDeviceProfile.INSTANCE.get(getContext()).dbFile,
+                                Favorites.TMP_TABLE,
+                                () -> mOpenHelper,
+                                () -> DatabaseHelper.createDatabaseHelper(
+                                        getContext(), true /* forMigration */)));
+                return result;
             }
             case LauncherSettings.Settings.METHOD_PREP_FOR_PREVIEW: {
-                if (MULTI_DB_GRID_MIRATION_ALGO.get()) {
-                    Bundle result = new Bundle();
-                    result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
-                            prepForMigration(
-                                    arg /* dbFile */,
-                                    Favorites.PREVIEW_TABLE_NAME,
-                                    () -> DatabaseHelper.createDatabaseHelper(
-                                            getContext(), arg, true /* forMigration */),
-                                    () -> mOpenHelper));
-                    return result;
-                }
-                return null;
+                Bundle result = new Bundle();
+                result.putBoolean(LauncherSettings.Settings.EXTRA_VALUE,
+                        prepForMigration(
+                                arg /* dbFile */,
+                                Favorites.PREVIEW_TABLE_NAME,
+                                () -> DatabaseHelper.createDatabaseHelper(
+                                        getContext(), arg, true /* forMigration */),
+                                () -> mOpenHelper));
+                return result;
             }
             case LauncherSettings.Settings.METHOD_SWITCH_DATABASE: {
                 if (TextUtils.equals(arg, mOpenHelper.getDatabaseName())) return null;
@@ -654,8 +647,7 @@
         static DatabaseHelper createDatabaseHelper(Context context, String dbName,
                 boolean forMigration) {
             if (dbName == null) {
-                dbName = MULTI_DB_GRID_MIRATION_ALGO.get() ? InvariantDeviceProfile.INSTANCE.get(
-                        context).dbFile : LauncherFiles.LAUNCHER_DB;
+                dbName = InvariantDeviceProfile.INSTANCE.get(context).dbFile;
             }
             DatabaseHelper databaseHelper = new DatabaseHelper(context, dbName, forMigration);
             // Table creation sometimes fails silently, which leads to a crash loop.
@@ -666,10 +658,6 @@
                 // This operation is a no-op if the table already exists.
                 databaseHelper.addFavoritesTable(databaseHelper.getWritableDatabase(), true);
             }
-            if (!MULTI_DB_GRID_MIRATION_ALGO.get()) {
-                databaseHelper.mBackupTableExists = tableExists(
-                        databaseHelper.getReadableDatabase(), Favorites.BACKUP_TABLE_NAME);
-            }
             databaseHelper.mHotseatRestoreTableExists = tableExists(
                     databaseHelper.getReadableDatabase(), Favorites.HYBRID_HOTSEAT_BACKUP_TABLE);
 
@@ -851,11 +839,7 @@
                 case 25:
                     convertShortcutsToLauncherActivities(db);
                 case 26:
-                    // QSB was moved to the grid. Clear the first row on screen 0.
-                    if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
-                            !LauncherDbUtils.prepareScreenZeroToHostQsb(mContext, db)) {
-                        break;
-                    }
+                    // QSB was moved to the grid. Ignore overlapping items
                 case 27: {
                     // Update the favorites table so that the screen ids are ordered based on
                     // workspace page rank.
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index 3f7a3ad..eb3f94c 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -238,10 +238,6 @@
         return getChildAt(index);
     }
 
-    protected int indexToPage(int index) {
-        return index;
-    }
-
     /**
      * Updates the scroll of the current page immediately to its final scroll position.  We use this
      * in CustomizePagedView to allow tabs to share the same PagedView while resetting the scroll of
@@ -322,18 +318,51 @@
      */
     @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
     public IntSet getVisiblePageIndices() {
-        IntSet visiblePageIndices = new IntSet();
+        return getPageIndices(mCurrentPage);
+    }
+
+    /**
+     * In case the panelCount is 1 this just returns the same page index in an IntSet.
+     * But in cases where the panelCount > 1 this will return all the page indices that belong
+     * together, i.e. on the Workspace they are next to each other and shown at the same time.
+     */
+    private IntSet getPageIndices(int pageIndex) {
+        // we want to make sure the pageIndex is the leftmost page
+        pageIndex = getLeftmostVisiblePageForIndex(pageIndex);
+
+        IntSet pageIndices = new IntSet();
         int panelCount = getPanelCount();
         int pageCount = getPageCount();
-
-        // If a device goes from one panel to two panel (i.e. unfolding a foldable device) while
-        // an odd indexed page is the current page, then the new leftmost visible page will be
-        // different from the old mCurrentPage.
-        int currentPage = getLeftmostVisiblePageForIndex(mCurrentPage);
-        for (int page = currentPage; page < currentPage + panelCount && page < pageCount; page++) {
-            visiblePageIndices.add(page);
+        for (int page = pageIndex; page < pageIndex + panelCount && page < pageCount; page++) {
+            pageIndices.add(page);
         }
-        return visiblePageIndices;
+        return pageIndices;
+    }
+
+    /**
+     * Returns an IntSet with the indices of the neighbour pages that are in the focus direction.
+     */
+    private IntSet getNeighbourPageIndices(int focus) {
+        int panelCount = getPanelCount();
+        // getNextPage is more reliable than getCurrentPage
+        int currentPage = getNextPage();
+
+        int nextPage;
+        if (focus == View.FOCUS_LEFT) {
+            nextPage = currentPage - panelCount;
+        } else if (focus == View.FOCUS_RIGHT) {
+            nextPage = currentPage + panelCount;
+        } else {
+            // no neighbours to other directions
+            return new IntSet();
+        }
+        nextPage = validateNewPage(nextPage);
+        if (nextPage == currentPage) {
+            // We reached the end of the pages
+            return new IntSet();
+        }
+
+        return getPageIndices(nextPage);
     }
 
     /**
@@ -352,7 +381,14 @@
      * Returns true if the view is on one of the current pages, false otherwise.
      */
     public boolean isVisible(View child) {
-        return getLeftmostVisiblePageForIndex(indexOfChild(child)) == mCurrentPage;
+        return isVisible(indexOfChild(child));
+    }
+
+    /**
+     * Returns true if the page with the given index is currently visible, false otherwise.
+     */
+    private boolean isVisible(int pageIndex) {
+        return getLeftmostVisiblePageForIndex(pageIndex) == mCurrentPage;
     }
 
     /**
@@ -817,8 +853,8 @@
 
     @Override
     public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
-        int page = indexToPage(indexOfChild(child));
-        if (page != mCurrentPage || !mScroller.isFinished()) {
+        int page = indexOfChild(child);
+        if (!isVisible(page) || !mScroller.isFinished()) {
             if (immediate) {
                 setCurrentPage(page);
             } else {
@@ -857,21 +893,25 @@
                 direction = View.FOCUS_LEFT;
             }
         }
-        if (direction == View.FOCUS_LEFT) {
-            if (getCurrentPage() > 0) {
-                int nextPage = validateNewPage(getCurrentPage() - 1);
-                snapToPage(nextPage);
-                getChildAt(nextPage).requestFocus(direction);
-                return true;
-            }
-        } else if (direction == View.FOCUS_RIGHT) {
-            if (getCurrentPage() < getPageCount() - 1) {
-                int nextPage = validateNewPage(getCurrentPage() + 1);
-                snapToPage(nextPage);
-                getChildAt(nextPage).requestFocus(direction);
-                return true;
+
+        int currentPage = getNextPage();
+        int closestNeighbourIndex = -1;
+        int closestNeighbourDistance = Integer.MAX_VALUE;
+        // Find the closest neighbour page
+        for (int neighbourPageIndex : getNeighbourPageIndices(direction)) {
+            int distance = Math.abs(neighbourPageIndex - currentPage);
+            if (closestNeighbourDistance > distance) {
+                closestNeighbourDistance = distance;
+                closestNeighbourIndex = neighbourPageIndex;
             }
         }
+        if (closestNeighbourIndex != -1) {
+            View page = getPageAt(closestNeighbourIndex);
+            snapToPage(closestNeighbourIndex);
+            page.requestFocus(direction);
+            return true;
+        }
+
         return false;
     }
 
@@ -881,28 +921,12 @@
             return;
         }
 
-        // Add the current page's views as focusable and the next possible page's too. If the
-        // last focus change action was left then the left neighbour's views will be added, and
-        // if it was right then the right neighbour's views will be added.
-        // Unfortunately mCurrentPage can be outdated if there were multiple control actions in a
-        // short period of time, but mNextPage is up to date because it is always updated by
-        // method snapToPage.
-        int nextPage = getNextPage();
-        // XXX-RTL: This will be fixed in a future CL
-        if (nextPage >= 0 && nextPage < getPageCount()) {
-            getPageAt(nextPage).addFocusables(views, direction, focusableMode);
-        }
-        if (direction == View.FOCUS_LEFT) {
-            if (nextPage > 0) {
-                nextPage = validateNewPage(nextPage - 1);
-                getPageAt(nextPage).addFocusables(views, direction, focusableMode);
-            }
-        } else if (direction == View.FOCUS_RIGHT) {
-            if (nextPage < getPageCount() - 1) {
-                nextPage = validateNewPage(nextPage + 1);
-                getPageAt(nextPage).addFocusables(views, direction, focusableMode);
-            }
-        }
+        // nextPage is more reliable when multiple control movements have been done in a short
+        // period of time
+        getPageIndices(getNextPage())
+                .addAll(getNeighbourPageIndices(direction))
+                .forEach(pageIndex ->
+                        getPageAt(pageIndex).addFocusables(views, direction, focusableMode));
     }
 
     /**
@@ -1482,8 +1506,8 @@
             setCurrentPage(nextPage);
         }
 
-        int page = indexToPage(indexOfChild(child));
-        if (page >= 0 && page != getCurrentPage() && !isInTouchMode()) {
+        int page = indexOfChild(child);
+        if (page >= 0 && !isVisible(page) && !isInTouchMode()) {
             snapToPage(page);
         }
     }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5200456..83ca08d 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -325,10 +325,13 @@
             setPageSpacing(Math.max(maxInsets, maxPadding));
         }
 
+        updateWorkspaceScreensPadding();
+    }
+
+    private void updateWorkspaceScreensPadding() {
+        DeviceProfile grid = mLauncher.getDeviceProfile();
         int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
         int paddingBottom = grid.cellLayoutBottomPaddingPx;
-        int twoPanelLandscapeSidePadding = paddingLeftRight * 2;
-        int twoPanelPortraitSidePadding = paddingLeftRight / 2;
 
         int panelCount = getPanelCount();
         for (int i = mWorkspaceScreens.size() - 1; i >= 0; i--) {
@@ -336,13 +339,11 @@
             int paddingRight = paddingLeftRight;
             if (panelCount > 1) {
                 if (i % panelCount == 0) { // left side panel
-                    paddingLeft = grid.isLandscape ? twoPanelLandscapeSidePadding
-                            : twoPanelPortraitSidePadding;
+                    paddingLeft = paddingLeftRight;
                     paddingRight = 0;
                 } else if (i % panelCount == panelCount - 1) { // right side panel
                     paddingLeft = 0;
-                    paddingRight = grid.isLandscape ? twoPanelLandscapeSidePadding
-                            : twoPanelPortraitSidePadding;
+                    paddingRight = paddingLeftRight;
                 } else { // middle panel
                     paddingLeft = 0;
                     paddingRight = 0;
@@ -625,10 +626,6 @@
         // created CellLayout.
         CellLayout newScreen = (CellLayout) LayoutInflater.from(getContext()).inflate(
                         R.layout.workspace_screen, this, false /* attachToRoot */);
-        DeviceProfile grid = mLauncher.getDeviceProfile();
-        int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
-        int paddingBottom = grid.cellLayoutBottomPaddingPx;
-        newScreen.setPadding(paddingLeftRight, 0, paddingLeftRight, paddingBottom);
 
         mWorkspaceScreens.put(screenId, newScreen);
         mScreenOrder.add(insertIndex, screenId);
@@ -637,6 +634,7 @@
                 mLauncher.getStateManager().getState(), newScreen, insertIndex);
 
         updatePageScrollValues();
+        updateWorkspaceScreensPadding();
         return newScreen;
     }
 
@@ -1779,7 +1777,7 @@
 
         boolean droppedOnOriginalCell = false;
 
-        int snapScreen = -1;
+        boolean snappedToNewPage = false;
         boolean resizeOnDrop = false;
         Runnable onCompleteRunnable = null;
         if (d.dragSource != this || mDragInfo == null) {
@@ -1861,11 +1859,14 @@
                 }
 
                 if (foundCell) {
-                    if (getScreenIdForPageIndex(mCurrentPage) != screenId && !hasMovedIntoHotseat) {
-                        snapScreen = getPageIndexForScreenId(screenId);
+                    int targetScreenIndex = getPageIndexForScreenId(screenId);
+                    int snapScreen = getLeftmostVisiblePageForIndex(targetScreenIndex);
+                    // On large screen devices two pages can be shown at the same time, and snap
+                    // isn't needed if the source and target screens appear at the same time
+                    if (snapScreen != mCurrentPage && !hasMovedIntoHotseat) {
                         snapToPage(snapScreen);
+                        snappedToNewPage = true;
                     }
-
                     final ItemInfo info = (ItemInfo) cell.getTag();
                     if (hasMovedLayouts) {
                         // Reparent the view
@@ -1957,7 +1958,7 @@
                             ANIMATE_INTO_POSITION_AND_DISAPPEAR;
                     animateWidgetDrop(info, parent, d.dragView, null, animationType, cell, false);
                 } else {
-                    int duration = snapScreen < 0 ? -1 : ADJACENT_SCREEN_DROP_DURATION;
+                    int duration = snappedToNewPage ? ADJACENT_SCREEN_DROP_DURATION : -1;
                     mLauncher.getDragLayer().animateViewIntoPosition(d.dragView, cell, duration,
                             this);
                 }
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 9faac5b..0fb5e77 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -188,7 +188,8 @@
     private boolean itemSupportsAccessibleDrag(ItemInfo item) {
         if (item instanceof WorkspaceItemInfo) {
             // Support the action unless the item is in a context menu.
-            return item.screenId >= 0 && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
+            return (item.screenId >= 0 || item.screenId == Workspace.LEFT_PANEL_ID)
+                    && item.container != Favorites.CONTAINER_HOTSEAT_PREDICTION;
         }
         return (item instanceof LauncherAppWidgetInfo)
                 || (item instanceof FolderInfo);
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 9b71918..da701a8 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -144,9 +144,6 @@
     public static final BooleanFlag ENABLE_DEEP_SHORTCUT_ICON_CACHE = getDebugFlag(
             "ENABLE_DEEP_SHORTCUT_ICON_CACHE", true, "R/W deep shortcut in IconCache");
 
-    public static final BooleanFlag MULTI_DB_GRID_MIRATION_ALGO = getDebugFlag(
-            "MULTI_DB_GRID_MIRATION_ALGO", true, "Use the multi-db grid migration algorithm");
-
     public static final BooleanFlag ENABLE_THEMED_ICONS = getDebugFlag(
             "ENABLE_THEMED_ICONS", true, "Enable themed icons on workspace");
 
@@ -258,6 +255,10 @@
             "WIDGETS_IN_LAUNCHER_PREVIEW", true,
             "Enables widgets in Launcher preview for the Wallpaper app.");
 
+    public static final BooleanFlag QUICK_WALLPAPER_PICKER = getDebugFlag(
+            "QUICK_WALLPAPER_PICKER", false,
+            "Shows quick wallpaper picker in long-press menu");
+
     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 d0464a4..c7448dc 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.graphics;
 
-import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
 
@@ -47,7 +46,6 @@
 import com.android.launcher3.Workspace;
 import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext;
 import com.android.launcher3.model.BgDataModel;
-import com.android.launcher3.model.GridSizeMigrationTask;
 import com.android.launcher3.model.GridSizeMigrationTaskV2;
 import com.android.launcher3.model.LoaderTask;
 import com.android.launcher3.model.ModelDelegate;
@@ -198,16 +196,10 @@
 
     @WorkerThread
     private boolean doGridMigrationIfNecessary() {
-        boolean needsToMigrate =
-                MULTI_DB_GRID_MIRATION_ALGO.get()
-                        ? GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)
-                        : GridSizeMigrationTask.needsToMigrate(mContext, mIdp);
-        if (!needsToMigrate) {
+        if (!GridSizeMigrationTaskV2.needsToMigrate(mContext, mIdp)) {
             return false;
         }
-        return MULTI_DB_GRID_MIRATION_ALGO.get()
-                ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp)
-                : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp);
+        return GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp);
     }
 
     @UiThread
diff --git a/src/com/android/launcher3/model/GridBackupTable.java b/src/com/android/launcher3/model/GridBackupTable.java
index acfc339..51cbf4b 100644
--- a/src/com/android/launcher3/model/GridBackupTable.java
+++ b/src/com/android/launcher3/model/GridBackupTable.java
@@ -23,14 +23,12 @@
 import android.content.Context;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
 import android.os.Process;
 import android.util.Log;
 
 import androidx.annotation.IntDef;
 
 import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.Settings;
 import com.android.launcher3.pm.UserCache;
 
 /**
@@ -85,49 +83,6 @@
     }
 
     /**
-     * Create a backup from current workspace layout if one isn't created already (Note backup
-     * created this way is always sanitized). Otherwise restore from the backup instead.
-     */
-    public boolean backupOrRestoreAsNeeded() {
-        // Check if backup table exists
-        if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
-            if (Settings.call(mContext.getContentResolver(), Settings.METHOD_WAS_EMPTY_DB_CREATED)
-                    .getBoolean(Settings.EXTRA_VALUE, false)) {
-                // No need to copy if empty DB was created.
-                return false;
-            }
-            doBackup(UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
-                    Process.myUserHandle()), 0);
-            return false;
-        }
-        return restoreIfBackupExists(Favorites.TABLE_NAME);
-    }
-
-    public boolean restoreToPreviewIfBackupExists() {
-        if (!tableExists(mDb, BACKUP_TABLE_NAME)) {
-            return false;
-        }
-
-        return restoreIfBackupExists(Favorites.PREVIEW_TABLE_NAME);
-    }
-
-    private boolean restoreIfBackupExists(String toTableName) {
-        if (loadDBProperties() != STATE_SANITIZED) {
-            return false;
-        }
-        long userSerial = UserCache.INSTANCE.get(mContext).getSerialNumberForUser(
-                Process.myUserHandle());
-        copyTable(mDb, BACKUP_TABLE_NAME, toTableName, userSerial);
-        Log.d(TAG, "Backup table found");
-        return true;
-    }
-
-    public int getRestoreHotseatAndGridSize(Point outGridSize) {
-        outGridSize.set(mRestoredGridX, mRestoredGridY);
-        return mRestoredHotseatSize;
-    }
-
-    /**
      * Creates a new table and populates with copy of Favorites.TABLE_NAME
      */
     public void createCustomBackupTable(String tableName) {
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTask.java b/src/com/android/launcher3/model/GridSizeMigrationTask.java
deleted file mode 100644
index 7b3e509..0000000
--- a/src/com/android/launcher3/model/GridSizeMigrationTask.java
+++ /dev/null
@@ -1,1098 +0,0 @@
-package com.android.launcher3.model;
-
-import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_HOTSEAT_COUNT;
-import static com.android.launcher3.InvariantDeviceProfile.KEY_MIGRATION_SRC_WORKSPACE_SIZE;
-import static com.android.launcher3.LauncherSettings.Settings.EXTRA_VALUE;
-import static com.android.launcher3.Utilities.getPointString;
-import static com.android.launcher3.Utilities.parsePoint;
-import static com.android.launcher3.provider.LauncherDbUtils.copyTable;
-
-import android.content.ComponentName;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
-import android.os.SystemClock;
-import android.util.Log;
-import android.util.SparseArray;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-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;
-import com.android.launcher3.provider.LauncherDbUtils;
-import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
-import com.android.launcher3.util.GridOccupancy;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
-import com.android.launcher3.widget.WidgetManagerHelper;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-
-/**
- * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
- * result of restoring from a larger device or device density change.
- */
-public class GridSizeMigrationTask {
-
-    private static final String TAG = "GridSizeMigrationTask";
-    private static final boolean DEBUG = false;
-
-    // These are carefully selected weights for various item types (Math.random?), to allow for
-    // the least absurd migration experience.
-    private static final float WT_SHORTCUT = 1;
-    private static final float WT_APPLICATION = 0.8f;
-    private static final float WT_WIDGET_MIN = 2;
-    private static final float WT_WIDGET_FACTOR = 0.6f;
-    private static final float WT_FOLDER_FACTOR = 0.5f;
-
-    protected final SQLiteDatabase mDb;
-    protected final Context mContext;
-
-    protected final IntArray mEntryToRemove = new IntArray();
-    protected final ArrayList<DbEntry> mCarryOver = new ArrayList<>();
-
-    private final SparseArray<ContentValues> mUpdateOperations = new SparseArray<>();
-    private final HashSet<String> mValidPackages;
-    private final String mTableName;
-
-    private final int mSrcX, mSrcY;
-    private final int mTrgX, mTrgY;
-    private final boolean mShouldRemoveX, mShouldRemoveY;
-
-    private final int mSrcHotseatSize;
-    private final int mDestHotseatSize;
-
-    protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
-            HashSet<String> validPackages, boolean usePreviewTable, Point sourceSize,
-            Point targetSize) {
-        mContext = context;
-        mDb = db;
-        mValidPackages = validPackages;
-        mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
-
-        mSrcX = sourceSize.x;
-        mSrcY = sourceSize.y;
-
-        mTrgX = targetSize.x;
-        mTrgY = targetSize.y;
-
-        mShouldRemoveX = mTrgX < mSrcX;
-        mShouldRemoveY = mTrgY < mSrcY;
-
-        // Non-used variables
-        mSrcHotseatSize = mDestHotseatSize = -1;
-    }
-
-    protected GridSizeMigrationTask(Context context, SQLiteDatabase db,
-            HashSet<String> validPackages, boolean usePreviewTable, int srcHotseatSize,
-            int destHotseatSize) {
-        mContext = context;
-        mDb = db;
-        mValidPackages = validPackages;
-        mTableName = usePreviewTable ? Favorites.PREVIEW_TABLE_NAME : Favorites.TABLE_NAME;
-
-        mSrcHotseatSize = srcHotseatSize;
-
-        mDestHotseatSize = destHotseatSize;
-
-        // Non-used variables
-        mSrcX = mSrcY = mTrgX = mTrgY = -1;
-        mShouldRemoveX = mShouldRemoveY = false;
-    }
-
-    /**
-     * Applied all the pending DB operations
-     *
-     * @return true if any DB operation was commited.
-     */
-    private boolean applyOperations() throws Exception {
-        // Update items
-        int updateCount = mUpdateOperations.size();
-        for (int i = 0; i < updateCount; i++) {
-            mDb.update(mTableName, mUpdateOperations.valueAt(i),
-                    "_id=" + mUpdateOperations.keyAt(i), null);
-        }
-
-        if (!mEntryToRemove.isEmpty()) {
-            if (DEBUG) {
-                Log.d(TAG, "Removing items: " + mEntryToRemove.toConcatString());
-            }
-            mDb.delete(mTableName, Utilities.createDbSelectionQuery(Favorites._ID, mEntryToRemove),
-                    null);
-        }
-
-        return updateCount > 0 || !mEntryToRemove.isEmpty();
-    }
-
-    /**
-     * To migrate hotseat, we load all the entries in order (LTR or RTL) and arrange them
-     * in the order in the new hotseat while keeping an empty space for all-apps. If the number of
-     * entries is more than what can fit in the new hotseat, we drop the entries with least weight.
-     * For weight calculation {@see #WT_SHORTCUT}, {@see #WT_APPLICATION}
-     * & {@see #WT_FOLDER_FACTOR}.
-     *
-     * @return true if any DB change was made
-     */
-    protected boolean migrateHotseat() throws Exception {
-        ArrayList<DbEntry> items = loadHotseatEntries();
-        while (items.size() > mDestHotseatSize) {
-            // Pick the center item by default.
-            DbEntry toRemove = items.get(items.size() / 2);
-
-            // Find the item with least weight.
-            for (DbEntry entry : items) {
-                if (entry.weight < toRemove.weight) {
-                    toRemove = entry;
-                }
-            }
-
-            mEntryToRemove.add(toRemove.id);
-            items.remove(toRemove);
-        }
-
-        // Update screen IDS
-        int newScreenId = 0;
-        for (DbEntry entry : items) {
-            if (entry.screenId != newScreenId) {
-                entry.screenId = newScreenId;
-
-                // These values does not affect the item position, but we should set them
-                // to something other than -1.
-                entry.cellX = newScreenId;
-                entry.cellY = 0;
-
-                update(entry);
-            }
-
-            newScreenId++;
-        }
-
-        return applyOperations();
-    }
-
-    @VisibleForTesting
-    static IntArray getWorkspaceScreenIds(SQLiteDatabase db, String tableName) {
-        return LauncherDbUtils.queryIntArray(db, tableName, Favorites.SCREEN,
-                Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP,
-                Favorites.SCREEN, Favorites.SCREEN);
-    }
-
-    /**
-     * @return true if any DB change was made
-     */
-    protected boolean migrateWorkspace() throws Exception {
-        IntArray allScreens = getWorkspaceScreenIds(mDb, mTableName);
-        if (allScreens.isEmpty()) {
-            throw new Exception("Unable to get workspace screens");
-        }
-
-        for (int i = 0; i < allScreens.size(); i++) {
-            int screenId = allScreens.get(i);
-            if (DEBUG) {
-                Log.d(TAG, "Migrating " + screenId);
-            }
-            migrateScreen(screenId);
-        }
-
-        if (!mCarryOver.isEmpty()) {
-            IntSparseArrayMap<DbEntry> itemMap = new IntSparseArrayMap<>();
-            for (DbEntry e : mCarryOver) {
-                itemMap.put(e.id, e);
-            }
-
-            do {
-                // Some items are still remaining. Try adding a few new screens.
-
-                // At every iteration, make sure that at least one item is removed from
-                // {@link #mCarryOver}, to prevent an infinite loop. If no item could be removed,
-                // break the loop and abort migration by throwing an exception.
-                OptimalPlacementSolution placement = new OptimalPlacementSolution(
-                        new GridOccupancy(mTrgX, mTrgY), deepCopy(mCarryOver), 0, true);
-                placement.find();
-                if (placement.finalPlacedItems.size() > 0) {
-                    int newScreenId = LauncherSettings.Settings.call(
-                            mContext.getContentResolver(),
-                            LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
-                            .getInt(EXTRA_VALUE);
-                    for (DbEntry item : placement.finalPlacedItems) {
-                        if (!mCarryOver.remove(itemMap.get(item.id))) {
-                            throw new Exception("Unable to find matching items");
-                        }
-                        item.screenId = newScreenId;
-                        update(item);
-                    }
-                } else {
-                    throw new Exception("None of the items can be placed on an empty screen");
-                }
-
-            } while (!mCarryOver.isEmpty());
-        }
-        return applyOperations();
-    }
-
-    /**
-     * Migrate a particular screen id.
-     * Strategy:
-     *  1) For all possible combinations of row and column, pick the one which causes the least
-     *    data loss: {@link #tryRemove(int, int, int, ArrayList, float[])}
-     *  2) Maintain a list of all lost items before this screen, and add any new item lost from
-     *    this screen to that list as well.
-     *  3) If all those items from the above list can be placed on this screen, place them
-     *    (otherwise they are placed on a new screen).
-     */
-    protected void migrateScreen(int screenId) {
-        // If we are migrating the first screen, do not touch the first row.
-        int startY = (FeatureFlags.QSB_ON_FIRST_SCREEN && screenId == Workspace.FIRST_SCREEN_ID)
-                ? 1 : 0;
-
-        ArrayList<DbEntry> items = loadWorkspaceEntries(screenId);
-
-        int removedCol = Integer.MAX_VALUE;
-        int removedRow = Integer.MAX_VALUE;
-
-        // removeWt represents the cost function for loss of items during migration, and moveWt
-        // represents the cost function for repositioning the items. moveWt is only considered if
-        // removeWt is same for two different configurations.
-        // Start with Float.MAX_VALUE (assuming full data) and pick the configuration with least
-        // cost.
-        float removeWt = Float.MAX_VALUE;
-        float moveWt = Float.MAX_VALUE;
-        float[] outLoss = new float[2];
-        ArrayList<DbEntry> finalItems = null;
-
-        // Try removing all possible combinations
-        for (int x = 0; x < mSrcX; x++) {
-            // Try removing the rows first from bottom. This keeps the workspace
-            // nicely aligned with hotseat.
-            for (int y = mSrcY - 1; y >= startY; y--) {
-                // Use a deep copy when trying out a particular combination as it can change
-                // the underlying object.
-                ArrayList<DbEntry> itemsOnScreen = tryRemove(x, y, startY, deepCopy(items),
-                        outLoss);
-
-                if ((outLoss[0] < removeWt) || ((outLoss[0] == removeWt) && (outLoss[1]
-                        < moveWt))) {
-                    removeWt = outLoss[0];
-                    moveWt = outLoss[1];
-                    removedCol = mShouldRemoveX ? x : removedCol;
-                    removedRow = mShouldRemoveY ? y : removedRow;
-                    finalItems = itemsOnScreen;
-                }
-
-                // No need to loop over all rows, if a row removal is not needed.
-                if (!mShouldRemoveY) {
-                    break;
-                }
-            }
-
-            if (!mShouldRemoveX) {
-                break;
-            }
-        }
-
-        if (DEBUG) {
-            Log.d(TAG, String.format("Removing row %d, column %d on screen %d",
-                    removedRow, removedCol, screenId));
-        }
-
-        IntSparseArrayMap<DbEntry> itemMap = new IntSparseArrayMap<>();
-        for (DbEntry e : deepCopy(items)) {
-            itemMap.put(e.id, e);
-        }
-
-        for (DbEntry item : finalItems) {
-            DbEntry org = itemMap.get(item.id);
-            itemMap.remove(item.id);
-
-            // Check if update is required
-            if (!item.columnsSame(org)) {
-                update(item);
-            }
-        }
-
-        // The remaining items in {@link #itemMap} are those which didn't get placed.
-        for (DbEntry item : itemMap) {
-            mCarryOver.add(item);
-        }
-
-        if (!mCarryOver.isEmpty() && removeWt == 0) {
-            // No new items were removed in this step. Try placing all the items on this screen.
-            GridOccupancy occupied = new GridOccupancy(mTrgX, mTrgY);
-            occupied.markCells(0, 0, mTrgX, startY, true);
-            for (DbEntry item : finalItems) {
-                occupied.markCells(item, true);
-            }
-
-            OptimalPlacementSolution placement = new OptimalPlacementSolution(occupied,
-                    deepCopy(mCarryOver), startY, true);
-            placement.find();
-            if (placement.lowestWeightLoss == 0) {
-                // All items got placed
-
-                for (DbEntry item : placement.finalPlacedItems) {
-                    item.screenId = screenId;
-                    update(item);
-                }
-
-                mCarryOver.clear();
-            }
-        }
-    }
-
-    /**
-     * Updates an item in the DB.
-     */
-    protected void update(DbEntry item) {
-        ContentValues values = new ContentValues();
-        item.addToContentValues(values);
-        mUpdateOperations.put(item.id, values);
-    }
-
-    /**
-     * Tries the remove the provided row and column.
-     *
-     * @param items   all the items on the screen under operation
-     * @param outLoss array of size 2. The first entry is filled with weight loss, and the second
-     *                with the overall item movement.
-     */
-    private ArrayList<DbEntry> tryRemove(int col, int row, int startY,
-            ArrayList<DbEntry> items, float[] outLoss) {
-        GridOccupancy occupied = new GridOccupancy(mTrgX, mTrgY);
-        occupied.markCells(0, 0, mTrgX, startY, true);
-
-        col = mShouldRemoveX ? col : Integer.MAX_VALUE;
-        row = mShouldRemoveY ? row : Integer.MAX_VALUE;
-
-        ArrayList<DbEntry> finalItems = new ArrayList<>();
-        ArrayList<DbEntry> removedItems = new ArrayList<>();
-
-        for (DbEntry item : items) {
-            if ((item.cellX <= col && (item.spanX + item.cellX) > col)
-                    || (item.cellY <= row && (item.spanY + item.cellY) > row)) {
-                removedItems.add(item);
-                if (item.cellX >= col) item.cellX--;
-                if (item.cellY >= row) item.cellY--;
-            } else {
-                if (item.cellX > col) item.cellX--;
-                if (item.cellY > row) item.cellY--;
-                finalItems.add(item);
-                occupied.markCells(item, true);
-            }
-        }
-
-        OptimalPlacementSolution placement =
-                new OptimalPlacementSolution(occupied, removedItems, startY);
-        placement.find();
-        finalItems.addAll(placement.finalPlacedItems);
-        outLoss[0] = placement.lowestWeightLoss;
-        outLoss[1] = placement.lowestMoveCost;
-        return finalItems;
-    }
-
-    private class OptimalPlacementSolution {
-        private final ArrayList<DbEntry> itemsToPlace;
-        private final GridOccupancy occupied;
-
-        // If set to true, item movement are not considered in move cost, leading to a more
-        // linear placement.
-        private final boolean ignoreMove;
-
-        // The first row in the grid from where the placement should start.
-        private final int startY;
-
-        float lowestWeightLoss = Float.MAX_VALUE;
-        float lowestMoveCost = Float.MAX_VALUE;
-        ArrayList<DbEntry> finalPlacedItems;
-
-        public OptimalPlacementSolution(
-                GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace, int startY) {
-            this(occupied, itemsToPlace, startY, false);
-        }
-
-        public OptimalPlacementSolution(GridOccupancy occupied, ArrayList<DbEntry> itemsToPlace,
-                int startY, boolean ignoreMove) {
-            this.occupied = occupied;
-            this.itemsToPlace = itemsToPlace;
-            this.ignoreMove = ignoreMove;
-            this.startY = startY;
-
-            // Sort the items such that larger widgets appear first followed by 1x1 items
-            Collections.sort(this.itemsToPlace);
-        }
-
-        public void find() {
-            find(0, 0, 0, new ArrayList<DbEntry>());
-        }
-
-        /**
-         * Recursively finds a placement for the provided items.
-         *
-         * @param index       the position in {@link #itemsToPlace} to start looking at.
-         * @param weightLoss  total weight loss upto this point
-         * @param moveCost    total move cost upto this point
-         * @param itemsPlaced all the items already placed upto this point
-         */
-        public void find(int index, float weightLoss, float moveCost,
-                ArrayList<DbEntry> itemsPlaced) {
-            if ((weightLoss >= lowestWeightLoss) ||
-                    ((weightLoss == lowestWeightLoss) && (moveCost >= lowestMoveCost))) {
-                // Abort, as we already have a better solution.
-                return;
-
-            } else if (index >= itemsToPlace.size()) {
-                // End loop.
-                lowestWeightLoss = weightLoss;
-                lowestMoveCost = moveCost;
-
-                // Keep a deep copy of current configuration as it can change during recursion.
-                finalPlacedItems = deepCopy(itemsPlaced);
-                return;
-            }
-
-            DbEntry me = itemsToPlace.get(index);
-            int myX = me.cellX;
-            int myY = me.cellY;
-
-            // List of items to pass over if this item was placed.
-            ArrayList<DbEntry> itemsIncludingMe = new ArrayList<>(itemsPlaced.size() + 1);
-            itemsIncludingMe.addAll(itemsPlaced);
-            itemsIncludingMe.add(me);
-
-            if (me.spanX > 1 || me.spanY > 1) {
-                // If the current item is a widget (and it greater than 1x1), try to place it at
-                // all possible positions. This is because a widget placed at one position can
-                // affect the placement of a different widget.
-                int myW = me.spanX;
-                int myH = me.spanY;
-
-                for (int y = startY; y < mTrgY; y++) {
-                    for (int x = 0; x < mTrgX; x++) {
-                        float newMoveCost = moveCost;
-                        if (x != myX) {
-                            me.cellX = x;
-                            newMoveCost++;
-                        }
-                        if (y != myY) {
-                            me.cellY = y;
-                            newMoveCost++;
-                        }
-                        if (ignoreMove) {
-                            newMoveCost = moveCost;
-                        }
-
-                        if (occupied.isRegionVacant(x, y, myW, myH)) {
-                            // place at this position and continue search.
-                            occupied.markCells(me, true);
-                            find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
-                            occupied.markCells(me, false);
-                        }
-
-                        // Try resizing horizontally
-                        if (myW > me.minSpanX && occupied.isRegionVacant(x, y, myW - 1, myH)) {
-                            me.spanX--;
-                            occupied.markCells(me, true);
-                            // 1 extra move cost
-                            find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
-                            occupied.markCells(me, false);
-                            me.spanX++;
-                        }
-
-                        // Try resizing vertically
-                        if (myH > me.minSpanY && occupied.isRegionVacant(x, y, myW, myH - 1)) {
-                            me.spanY--;
-                            occupied.markCells(me, true);
-                            // 1 extra move cost
-                            find(index + 1, weightLoss, newMoveCost + 1, itemsIncludingMe);
-                            occupied.markCells(me, false);
-                            me.spanY++;
-                        }
-
-                        // Try resizing horizontally & vertically
-                        if (myH > me.minSpanY && myW > me.minSpanX &&
-                                occupied.isRegionVacant(x, y, myW - 1, myH - 1)) {
-                            me.spanX--;
-                            me.spanY--;
-                            occupied.markCells(me, true);
-                            // 2 extra move cost
-                            find(index + 1, weightLoss, newMoveCost + 2, itemsIncludingMe);
-                            occupied.markCells(me, false);
-                            me.spanX++;
-                            me.spanY++;
-                        }
-                        me.cellX = myX;
-                        me.cellY = myY;
-                    }
-                }
-
-                // Finally also try a solution when this item is not included. Trying it in the end
-                // causes it to get skipped in most cases due to higher weight loss, and prevents
-                // unnecessary deep copies of various configurations.
-                find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
-            } else {
-                // Since this is a 1x1 item and all the following items are also 1x1, just place
-                // it at 'the most appropriate position' and hope for the best.
-                // The most appropriate position: one with lease straight line distance
-                int newDistance = Integer.MAX_VALUE;
-                int newX = Integer.MAX_VALUE, newY = Integer.MAX_VALUE;
-
-                for (int y = startY; y < mTrgY; y++) {
-                    for (int x = 0; x < mTrgX; x++) {
-                        if (!occupied.cells[x][y]) {
-                            int dist = ignoreMove ? 0 :
-                                    ((me.cellX - x) * (me.cellX - x) + (me.cellY - y) * (me.cellY
-                                            - y));
-                            if (dist < newDistance) {
-                                newX = x;
-                                newY = y;
-                                newDistance = dist;
-                            }
-                        }
-                    }
-                }
-
-                if (newX < mTrgX && newY < mTrgY) {
-                    float newMoveCost = moveCost;
-                    if (newX != myX) {
-                        me.cellX = newX;
-                        newMoveCost++;
-                    }
-                    if (newY != myY) {
-                        me.cellY = newY;
-                        newMoveCost++;
-                    }
-                    if (ignoreMove) {
-                        newMoveCost = moveCost;
-                    }
-                    occupied.markCells(me, true);
-                    find(index + 1, weightLoss, newMoveCost, itemsIncludingMe);
-                    occupied.markCells(me, false);
-                    me.cellX = myX;
-                    me.cellY = myY;
-
-                    // Try to find a solution without this item, only if
-                    //  1) there was at least one space, i.e., we were able to place this item
-                    //  2) if the next item has the same weight (all items are already sorted), as
-                    //     if it has lower weight, that solution will automatically get discarded.
-                    //  3) ignoreMove false otherwise, move cost is ignored and the weight will
-                    //      anyway be same.
-                    if (index + 1 < itemsToPlace.size()
-                            && itemsToPlace.get(index + 1).weight >= me.weight && !ignoreMove) {
-                        find(index + 1, weightLoss + me.weight, moveCost, itemsPlaced);
-                    }
-                } else {
-                    // No more space. Jump to the end.
-                    for (int i = index + 1; i < itemsToPlace.size(); i++) {
-                        weightLoss += itemsToPlace.get(i).weight;
-                    }
-                    find(itemsToPlace.size(), weightLoss + me.weight, moveCost, itemsPlaced);
-                }
-            }
-        }
-    }
-
-    private ArrayList<DbEntry> loadHotseatEntries() {
-        Cursor c = queryWorkspace(
-                new String[]{
-                        Favorites._ID,                  // 0
-                        Favorites.ITEM_TYPE,            // 1
-                        Favorites.INTENT,               // 2
-                        Favorites.SCREEN},              // 3
-                Favorites.CONTAINER + " = " + Favorites.CONTAINER_HOTSEAT);
-
-        final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
-        final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
-        final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
-        final int indexScreen = c.getColumnIndexOrThrow(Favorites.SCREEN);
-
-        ArrayList<DbEntry> entries = new ArrayList<>();
-        while (c.moveToNext()) {
-            DbEntry entry = new DbEntry();
-            entry.id = c.getInt(indexId);
-            entry.itemType = c.getInt(indexItemType);
-            entry.screenId = c.getInt(indexScreen);
-
-            if (entry.screenId >= mSrcHotseatSize) {
-                mEntryToRemove.add(entry.id);
-                continue;
-            }
-
-            try {
-                // calculate weight
-                switch (entry.itemType) {
-                    case Favorites.ITEM_TYPE_SHORTCUT:
-                    case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                    case Favorites.ITEM_TYPE_APPLICATION: {
-                        verifyIntent(c.getString(indexIntent));
-                        entry.weight = entry.itemType == Favorites.ITEM_TYPE_APPLICATION ?
-                                WT_APPLICATION : WT_SHORTCUT;
-                        break;
-                    }
-                    case Favorites.ITEM_TYPE_FOLDER: {
-                        int total = getFolderItemsCount(entry.id);
-                        if (total == 0) {
-                            throw new Exception("Folder is empty");
-                        }
-                        entry.weight = WT_FOLDER_FACTOR * total;
-                        break;
-                    }
-                    default:
-                        throw new Exception("Invalid item type");
-                }
-            } catch (Exception e) {
-                if (DEBUG) {
-                    Log.d(TAG, "Removing item " + entry.id, e);
-                }
-                mEntryToRemove.add(entry.id);
-                continue;
-            }
-            entries.add(entry);
-        }
-        c.close();
-        return entries;
-    }
-
-
-    /**
-     * Loads entries for a particular screen id.
-     */
-    protected ArrayList<DbEntry> loadWorkspaceEntries(int screen) {
-        Cursor c = queryWorkspace(
-                new String[]{
-                        Favorites._ID,                  // 0
-                        Favorites.ITEM_TYPE,            // 1
-                        Favorites.CELLX,                // 2
-                        Favorites.CELLY,                // 3
-                        Favorites.SPANX,                // 4
-                        Favorites.SPANY,                // 5
-                        Favorites.INTENT,               // 6
-                        Favorites.APPWIDGET_PROVIDER,   // 7
-                        Favorites.APPWIDGET_ID},        // 8
-                Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP
-                        + " AND " + Favorites.SCREEN + " = " + screen);
-
-        final int indexId = c.getColumnIndexOrThrow(Favorites._ID);
-        final int indexItemType = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
-        final int indexCellX = c.getColumnIndexOrThrow(Favorites.CELLX);
-        final int indexCellY = c.getColumnIndexOrThrow(Favorites.CELLY);
-        final int indexSpanX = c.getColumnIndexOrThrow(Favorites.SPANX);
-        final int indexSpanY = c.getColumnIndexOrThrow(Favorites.SPANY);
-        final int indexIntent = c.getColumnIndexOrThrow(Favorites.INTENT);
-        final int indexAppWidgetProvider = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER);
-        final int indexAppWidgetId = c.getColumnIndexOrThrow(Favorites.APPWIDGET_ID);
-
-        ArrayList<DbEntry> entries = new ArrayList<>();
-        WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(mContext);
-        while (c.moveToNext()) {
-            DbEntry entry = new DbEntry();
-            entry.id = c.getInt(indexId);
-            entry.itemType = c.getInt(indexItemType);
-            entry.cellX = c.getInt(indexCellX);
-            entry.cellY = c.getInt(indexCellY);
-            entry.spanX = c.getInt(indexSpanX);
-            entry.spanY = c.getInt(indexSpanY);
-            entry.screenId = screen;
-
-            try {
-                // calculate weight
-                switch (entry.itemType) {
-                    case Favorites.ITEM_TYPE_SHORTCUT:
-                    case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
-                    case Favorites.ITEM_TYPE_APPLICATION: {
-                        verifyIntent(c.getString(indexIntent));
-                        entry.weight = entry.itemType == Favorites.ITEM_TYPE_APPLICATION ?
-                                WT_APPLICATION : WT_SHORTCUT;
-                        break;
-                    }
-                    case Favorites.ITEM_TYPE_APPWIDGET: {
-                        String provider = c.getString(indexAppWidgetProvider);
-                        ComponentName cn = ComponentName.unflattenFromString(provider);
-                        verifyPackage(cn.getPackageName());
-                        entry.weight = Math.max(WT_WIDGET_MIN, WT_WIDGET_FACTOR
-                                * entry.spanX * entry.spanY);
-
-                        int widgetId = c.getInt(indexAppWidgetId);
-                        LauncherAppWidgetProviderInfo pInfo =
-                                widgetManagerHelper.getLauncherAppWidgetInfo(widgetId);
-                        Point spans = null;
-                        if (pInfo != null) {
-                            spans = pInfo.getMinSpans();
-                        }
-                        if (spans != null) {
-                            entry.minSpanX = spans.x > 0 ? spans.x : entry.spanX;
-                            entry.minSpanY = spans.y > 0 ? spans.y : entry.spanY;
-                        } else {
-                            // Assume that the widget be resized down to 2x2
-                            entry.minSpanX = entry.minSpanY = 2;
-                        }
-
-                        if (entry.minSpanX > mTrgX || entry.minSpanY > mTrgY) {
-                            throw new Exception("Widget can't be resized down to fit the grid");
-                        }
-                        break;
-                    }
-                    case Favorites.ITEM_TYPE_FOLDER: {
-                        int total = getFolderItemsCount(entry.id);
-                        if (total == 0) {
-                            throw new Exception("Folder is empty");
-                        }
-                        entry.weight = WT_FOLDER_FACTOR * total;
-                        break;
-                    }
-                    default:
-                        throw new Exception("Invalid item type");
-                }
-            } catch (Exception e) {
-                if (DEBUG) {
-                    Log.d(TAG, "Removing item " + entry.id, e);
-                }
-                mEntryToRemove.add(entry.id);
-                continue;
-            }
-            entries.add(entry);
-        }
-        c.close();
-        return entries;
-    }
-
-    /**
-     * @return the number of valid items in the folder.
-     */
-    private int getFolderItemsCount(int folderId) {
-        Cursor c = queryWorkspace(
-                new String[]{Favorites._ID, Favorites.INTENT},
-                Favorites.CONTAINER + " = " + folderId);
-
-        int total = 0;
-        while (c.moveToNext()) {
-            try {
-                verifyIntent(c.getString(1));
-                total++;
-            } catch (Exception e) {
-                mEntryToRemove.add(c.getInt(0));
-            }
-        }
-        c.close();
-        return total;
-    }
-
-    protected Cursor queryWorkspace(String[] columns, String where) {
-        return mDb.query(mTableName, columns, where, null, null, null, null);
-    }
-
-    /**
-     * Verifies if the intent should be restored.
-     */
-    private void verifyIntent(String intentStr) throws Exception {
-        Intent intent = Intent.parseUri(intentStr, 0);
-        if (intent.getComponent() != null) {
-            verifyPackage(intent.getComponent().getPackageName());
-        } else if (intent.getPackage() != null) {
-            // Only verify package if the component was null.
-            verifyPackage(intent.getPackage());
-        }
-    }
-
-    /**
-     * Verifies if the package should be restored
-     */
-    private void verifyPackage(String packageName) throws Exception {
-        if (!mValidPackages.contains(packageName)) {
-            throw new Exception("Package not available");
-        }
-    }
-
-    protected static class DbEntry extends ItemInfo implements Comparable<DbEntry> {
-
-        public float weight;
-
-        public DbEntry() {
-        }
-
-        public DbEntry copy() {
-            DbEntry entry = new DbEntry();
-            entry.copyFrom(this);
-            entry.weight = weight;
-            entry.minSpanX = minSpanX;
-            entry.minSpanY = minSpanY;
-            return entry;
-        }
-
-        /**
-         * Comparator such that larger widgets come first,  followed by all 1x1 items
-         * based on their weights.
-         */
-        @Override
-        public int compareTo(DbEntry another) {
-            if (itemType == Favorites.ITEM_TYPE_APPWIDGET) {
-                if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
-                    return another.spanY * another.spanX - spanX * spanY;
-                } else {
-                    return -1;
-                }
-            } else if (another.itemType == Favorites.ITEM_TYPE_APPWIDGET) {
-                return 1;
-            } else {
-                // Place higher weight before lower weight.
-                return Float.compare(another.weight, weight);
-            }
-        }
-
-        public boolean columnsSame(DbEntry org) {
-            return org.cellX == cellX && org.cellY == cellY && org.spanX == spanX &&
-                    org.spanY == spanY && org.screenId == screenId;
-        }
-
-        public void addToContentValues(ContentValues values) {
-            values.put(Favorites.SCREEN, screenId);
-            values.put(Favorites.CELLX, cellX);
-            values.put(Favorites.CELLY, cellY);
-            values.put(Favorites.SPANX, spanX);
-            values.put(Favorites.SPANY, spanY);
-        }
-    }
-
-    private static ArrayList<DbEntry> deepCopy(ArrayList<DbEntry> src) {
-        ArrayList<DbEntry> dup = new ArrayList<>(src.size());
-        for (DbEntry e : src) {
-            dup.add(e.copy());
-        }
-        return dup;
-    }
-
-    public static void markForMigration(
-            Context context, int gridX, int gridY, int hotseatSize) {
-        Utilities.getPrefs(context).edit()
-                .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, getPointString(gridX, gridY))
-                .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, hotseatSize)
-                .apply();
-    }
-
-    /**
-     * Check given a new IDP, if migration is necessary.
-     */
-    public static boolean needsToMigrate(Context context, InvariantDeviceProfile idp) {
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
-
-        return !gridSizeString.equals(prefs.getString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, ""))
-                || idp.numDatabaseHotseatIcons != prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, -1);
-    }
-
-    /** See {@link #migrateGridIfNeeded(Context, InvariantDeviceProfile)} */
-    public static boolean migrateGridIfNeeded(Context context) {
-        if (context instanceof LauncherPreviewRenderer.PreviewContext) {
-            return true;
-        }
-        return migrateGridIfNeeded(context, null);
-    }
-
-    /**
-     * Run the migration algorithm if needed. For preview, we provide the intended idp because it
-     * has not been changed. If idp is null, we read it from the context, for actual grid migration.
-     *
-     * @return false if the migration failed.
-     */
-    public static boolean migrateGridIfNeeded(Context context, InvariantDeviceProfile idp) {
-        boolean migrateForPreview = idp != null;
-        if (!migrateForPreview) {
-            idp = LauncherAppState.getIDP(context);
-        }
-
-        if (!needsToMigrate(context, idp)) {
-            return true;
-        }
-
-        SharedPreferences prefs = Utilities.getPrefs(context);
-        String gridSizeString = getPointString(idp.numColumns, idp.numRows);
-        long migrationStartTime = SystemClock.elapsedRealtime();
-        try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
-                context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
-                .getBinder(Settings.EXTRA_VALUE)) {
-
-            int srcHotseatCount = prefs.getInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT,
-                    idp.numDatabaseHotseatIcons);
-            Point sourceSize = parsePoint(prefs.getString(
-                    KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString));
-
-            boolean dbChanged = false;
-            if (migrateForPreview) {
-                copyTable(transaction.getDb(), Favorites.TABLE_NAME, transaction.getDb(),
-                        Favorites.PREVIEW_TABLE_NAME, context);
-            }
-
-            GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb(),
-                    srcHotseatCount, sourceSize.x, sourceSize.y);
-            if (migrateForPreview ? backupTable.restoreToPreviewIfBackupExists()
-                    : backupTable.backupOrRestoreAsNeeded()) {
-                dbChanged = true;
-                srcHotseatCount = backupTable.getRestoreHotseatAndGridSize(sourceSize);
-            }
-
-            HashSet<String> validPackages = getValidPackages(context);
-            // Hotseat.
-            if (srcHotseatCount != idp.numDatabaseHotseatIcons
-                    && new GridSizeMigrationTask(context, transaction.getDb(), validPackages,
-                            migrateForPreview, srcHotseatCount,
-                            idp.numDatabaseHotseatIcons).migrateHotseat()) {
-                dbChanged = true;
-            }
-
-            // Grid size
-            Point targetSize = new Point(idp.numColumns, idp.numRows);
-            if (new MultiStepMigrationTask(validPackages, context, transaction.getDb(),
-                    migrateForPreview).migrate(sourceSize, targetSize)) {
-                dbChanged = true;
-            }
-
-            if (dbChanged) {
-                // Make sure we haven't removed everything.
-                final Cursor c = context.getContentResolver().query(
-                        migrateForPreview ? Favorites.PREVIEW_CONTENT_URI : Favorites.CONTENT_URI,
-                        null, null, null, null);
-                boolean hasData = c.moveToNext();
-                c.close();
-                if (!hasData) {
-                    throw new Exception("Removed every thing during grid resize");
-                }
-            }
-
-            transaction.commit();
-            if (!migrateForPreview) {
-                Settings.call(context.getContentResolver(), Settings.METHOD_REFRESH_BACKUP_TABLE);
-            }
-            return true;
-        } catch (Exception e) {
-            Log.e(TAG, "Error during preview grid migration", e);
-
-            return false;
-        } finally {
-            Log.v(TAG, "Preview workspace migration completed in "
-                    + (SystemClock.elapsedRealtime() - migrationStartTime));
-
-            if (!migrateForPreview) {
-                // Save current configuration, so that the migration does not run again.
-                prefs.edit()
-                        .putString(KEY_MIGRATION_SRC_WORKSPACE_SIZE, gridSizeString)
-                        .putInt(KEY_MIGRATION_SRC_HOTSEAT_COUNT, idp.numDatabaseHotseatIcons)
-                        .apply();
-            }
-        }
-    }
-
-    protected static HashSet<String> getValidPackages(Context context) {
-        // Initialize list of valid packages. This contain all the packages which are already on
-        // the device and packages which are being installed. Any item which doesn't belong to
-        // this set is removed.
-        // Since the loader removes such items anyway, removing these items here doesn't cause
-        // any extra data loss and gives us more free space on the grid for better migration.
-        HashSet<String> validPackages = new HashSet<>();
-        for (PackageInfo info : context.getPackageManager()
-                .getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
-            validPackages.add(info.packageName);
-        }
-        InstallSessionHelper.INSTANCE.get(context)
-                .getActiveSessions().keySet()
-                .forEach(packageUserKey -> validPackages.add(packageUserKey.mPackageName));
-        return validPackages;
-    }
-
-    /**
-     * Removes any broken item from the hotseat.
-     *
-     * @return a map with occupied hotseat position set to non-null value.
-     */
-    public static IntSparseArrayMap<Object> removeBrokenHotseatItems(Context context)
-            throws Exception {
-        try (SQLiteTransaction transaction = (SQLiteTransaction) Settings.call(
-                context.getContentResolver(), Settings.METHOD_NEW_TRANSACTION)
-                .getBinder(Settings.EXTRA_VALUE)) {
-            GridSizeMigrationTask task = new GridSizeMigrationTask(
-                    context, transaction.getDb(), getValidPackages(context),
-                    false /* usePreviewTable */, Integer.MAX_VALUE, Integer.MAX_VALUE);
-
-            // Load all the valid entries
-            ArrayList<DbEntry> items = task.loadHotseatEntries();
-            // Delete any entry marked for deletion by above load.
-            task.applyOperations();
-            IntSparseArrayMap<Object> positions = new IntSparseArrayMap<>();
-            for (DbEntry item : items) {
-                positions.put(item.screenId, item);
-            }
-            transaction.commit();
-            return positions;
-        }
-    }
-
-    /**
-     * Task to run grid migration in multiple steps when the size difference is more than 1.
-     */
-    protected static class MultiStepMigrationTask {
-        private final HashSet<String> mValidPackages;
-        private final Context mContext;
-        private final SQLiteDatabase mDb;
-        private final boolean mUsePreviewTable;
-
-        public MultiStepMigrationTask(HashSet<String> validPackages, Context context,
-                SQLiteDatabase db, boolean usePreviewTable) {
-            mValidPackages = validPackages;
-            mContext = context;
-            mDb = db;
-            mUsePreviewTable = usePreviewTable;
-        }
-
-        public boolean migrate(Point sourceSize, Point targetSize) throws Exception {
-            boolean dbChanged = false;
-            if (!targetSize.equals(sourceSize)) {
-                if (sourceSize.x < targetSize.x) {
-                    // Source is smaller that target, just expand the grid without actual migration.
-                    sourceSize.x = targetSize.x;
-                }
-                if (sourceSize.y < targetSize.y) {
-                    // Source is smaller that target, just expand the grid without actual migration.
-                    sourceSize.y = targetSize.y;
-                }
-
-                // Migrate the workspace grid, such that the points differ by max 1 in x and y
-                // each on every step.
-                while (!targetSize.equals(sourceSize)) {
-                    // Get the next size, such that the points differ by max 1 in x and y each
-                    Point nextSize = new Point(sourceSize);
-                    if (targetSize.x < nextSize.x) {
-                        nextSize.x--;
-                    }
-                    if (targetSize.y < nextSize.y) {
-                        nextSize.y--;
-                    }
-                    if (runStepTask(sourceSize, nextSize)) {
-                        dbChanged = true;
-                    }
-                    sourceSize.set(nextSize.x, nextSize.y);
-                }
-            }
-            return dbChanged;
-        }
-
-        protected boolean runStepTask(Point sourceSize, Point nextSize) throws Exception {
-            return new GridSizeMigrationTask(mContext, mDb, mValidPackages, mUsePreviewTable,
-                    sourceSize, nextSize).migrateWorkspace();
-        }
-    }
-}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 43f9be5..1378a1a 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -17,7 +17,6 @@
 package com.android.launcher3.model;
 
 import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
-import static com.android.launcher3.config.FeatureFlags.MULTI_DB_GRID_MIRATION_ALGO;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_HAS_SHORTCUT_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_CHANGE_PERMISSION;
 import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
@@ -80,7 +79,6 @@
 import com.android.launcher3.pm.InstallSessionHelper;
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.provider.ImportDataTask;
 import com.android.launcher3.qsb.QsbContainerView;
 import com.android.launcher3.shortcuts.ShortcutKey;
 import com.android.launcher3.shortcuts.ShortcutRequest;
@@ -334,16 +332,7 @@
         final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
 
         boolean clearDb = false;
-        try {
-            ImportDataTask.performImportIfPossible(context);
-        } catch (Exception e) {
-            // Migration failed. Clear workspace.
-            clearDb = true;
-        }
-
-        if (!clearDb && (MULTI_DB_GRID_MIRATION_ALGO.get()
-                ? !GridSizeMigrationTaskV2.migrateGridIfNeeded(context)
-                : !GridSizeMigrationTask.migrateGridIfNeeded(context))) {
+        if (!GridSizeMigrationTaskV2.migrateGridIfNeeded(context)) {
             // Migration failed. Clear workspace.
             clearDb = true;
         }
diff --git a/src/com/android/launcher3/provider/ImportDataTask.java b/src/com/android/launcher3/provider/ImportDataTask.java
deleted file mode 100644
index c9af2fe..0000000
--- a/src/com/android/launcher3/provider/ImportDataTask.java
+++ /dev/null
@@ -1,438 +0,0 @@
-/*
- * Copyright (C) 2016 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.provider;
-
-import static com.android.launcher3.Utilities.getDevicePrefs;
-
-import android.content.ContentProviderOperation;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.Process;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.SparseBooleanArray;
-
-import com.android.launcher3.AutoInstallsLayout.LayoutParserCallback;
-import com.android.launcher3.DefaultLayoutParser;
-import com.android.launcher3.LauncherAppState;
-import com.android.launcher3.LauncherProvider;
-import com.android.launcher3.LauncherSettings;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.LauncherSettings.Settings;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.logging.FileLog;
-import com.android.launcher3.model.GridSizeMigrationTask;
-import com.android.launcher3.model.data.LauncherAppWidgetInfo;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.util.IntArray;
-import com.android.launcher3.util.IntSparseArrayMap;
-import com.android.launcher3.util.PackageManagerHelper;
-
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.HashSet;
-
-/**
- * Utility class to import data from another Launcher which is based on Launcher3 schema.
- */
-public class ImportDataTask {
-
-    public static final String KEY_DATA_IMPORT_SRC_PKG = "data_import_src_pkg";
-    public static final String KEY_DATA_IMPORT_SRC_AUTHORITY = "data_import_src_authority";
-
-    private static final String TAG = "ImportDataTask";
-    private static final int MIN_ITEM_COUNT_FOR_SUCCESSFUL_MIGRATION = 6;
-    // Insert items progressively to avoid OOM exception when loading icons.
-    private static final int BATCH_INSERT_SIZE = 15;
-
-    private final Context mContext;
-
-    private final Uri mOtherFavoritesUri;
-
-    private int mHotseatSize;
-    private int mMaxGridSizeX;
-    private int mMaxGridSizeY;
-
-    private ImportDataTask(Context context, String sourceAuthority) {
-        mContext = context;
-        mOtherFavoritesUri = Uri.parse("content://" + sourceAuthority + "/" + Favorites.TABLE_NAME);
-    }
-
-    public boolean importWorkspace() throws Exception {
-        FileLog.d(TAG, "Importing DB from " + mOtherFavoritesUri);
-
-        mHotseatSize = mMaxGridSizeX = mMaxGridSizeY = 0;
-        importWorkspaceItems();
-        GridSizeMigrationTask.markForMigration(mContext, mMaxGridSizeX, mMaxGridSizeY, mHotseatSize);
-
-        // Create empty DB flag.
-        LauncherSettings.Settings.call(mContext.getContentResolver(),
-                LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
-        return true;
-    }
-
-    /**
-     * 1) Imports all the workspace entries from the source provider.
-     * 2) For home screen entries, maps the screen id based on {@param screenIdMap}
-     * 3) In the end fills any holes in hotseat with items from default hotseat layout.
-     */
-    private void importWorkspaceItems() throws Exception {
-        String profileId = Long.toString(UserCache.INSTANCE.get(mContext)
-                .getSerialNumberForUser(Process.myUserHandle()));
-
-        boolean createEmptyRowOnFirstScreen;
-        if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
-            try (Cursor c = mContext.getContentResolver().query(mOtherFavoritesUri, null,
-                    // get items on the first row of the first screen (min screen id)
-                    "profileId = ? AND container = -100 AND cellY = 0 AND screen = " +
-                    "(SELECT MIN(screen) FROM favorites WHERE container = -100)",
-                    new String[]{profileId},
-                    null)) {
-                // First row of first screen is not empty
-                createEmptyRowOnFirstScreen = c.moveToNext();
-            }
-        } else {
-            createEmptyRowOnFirstScreen = false;
-        }
-
-        ArrayList<ContentProviderOperation> insertOperations = new ArrayList<>(BATCH_INSERT_SIZE);
-
-        // Set of package names present in hotseat
-        final HashSet<String> hotseatTargetApps = new HashSet<>();
-        int maxId = 0;
-
-        // Number of imported items on workspace and hotseat
-        int totalItemsOnWorkspace = 0;
-
-        try (Cursor c = mContext.getContentResolver()
-                .query(mOtherFavoritesUri, null,
-                        // Only migrate the primary user
-                        Favorites.PROFILE_ID + " = ?", new String[]{profileId},
-                        // Get the items sorted by container, so that the folders are loaded
-                        // before the corresponding items.
-                        Favorites.CONTAINER + " , " + Favorites.SCREEN)) {
-
-            // various columns we expect to exist.
-            final int idIndex = c.getColumnIndexOrThrow(Favorites._ID);
-            final int intentIndex = c.getColumnIndexOrThrow(Favorites.INTENT);
-            final int titleIndex = c.getColumnIndexOrThrow(Favorites.TITLE);
-            final int containerIndex = c.getColumnIndexOrThrow(Favorites.CONTAINER);
-            final int itemTypeIndex = c.getColumnIndexOrThrow(Favorites.ITEM_TYPE);
-            final int widgetProviderIndex = c.getColumnIndexOrThrow(Favorites.APPWIDGET_PROVIDER);
-            final int screenIndex = c.getColumnIndexOrThrow(Favorites.SCREEN);
-            final int cellXIndex = c.getColumnIndexOrThrow(Favorites.CELLX);
-            final int cellYIndex = c.getColumnIndexOrThrow(Favorites.CELLY);
-            final int spanXIndex = c.getColumnIndexOrThrow(Favorites.SPANX);
-            final int spanYIndex = c.getColumnIndexOrThrow(Favorites.SPANY);
-            final int rankIndex = c.getColumnIndexOrThrow(Favorites.RANK);
-            final int iconIndex = c.getColumnIndexOrThrow(Favorites.ICON);
-            final int iconPackageIndex = c.getColumnIndexOrThrow(Favorites.ICON_PACKAGE);
-            final int iconResourceIndex = c.getColumnIndexOrThrow(Favorites.ICON_RESOURCE);
-
-            SparseBooleanArray mValidFolders = new SparseBooleanArray();
-            ContentValues values = new ContentValues();
-
-            Integer firstScreenId = null;
-            while (c.moveToNext()) {
-                values.clear();
-                int id = c.getInt(idIndex);
-                maxId = Math.max(maxId, id);
-                int type = c.getInt(itemTypeIndex);
-                int container = c.getInt(containerIndex);
-
-                int screen = c.getInt(screenIndex);
-
-                int cellX = c.getInt(cellXIndex);
-                int cellY = c.getInt(cellYIndex);
-                int spanX = c.getInt(spanXIndex);
-                int spanY = c.getInt(spanYIndex);
-
-                switch (container) {
-                    case Favorites.CONTAINER_DESKTOP: {
-                        if (screen < Workspace.FIRST_SCREEN_ID) {
-                            FileLog.d(TAG, String.format(
-                                    "Skipping item %d, type %d not on a valid screen %d",
-                                    id, type, screen));
-                            continue;
-                        }
-                        if (firstScreenId == null) {
-                            firstScreenId = screen;
-                        }
-                        // Reset the screen to 0-index value
-                        if (createEmptyRowOnFirstScreen && firstScreenId.equals(screen)) {
-                            // Shift items by 1.
-                            cellY++;
-                            // Change the screen id to first screen
-                            screen = Workspace.FIRST_SCREEN_ID;
-                        }
-
-                        mMaxGridSizeX = Math.max(mMaxGridSizeX, cellX + spanX);
-                        mMaxGridSizeY = Math.max(mMaxGridSizeY, cellY + spanY);
-                        break;
-                    }
-                    case Favorites.CONTAINER_HOTSEAT: {
-                        mHotseatSize = Math.max(mHotseatSize, screen + 1);
-                        break;
-                    }
-                    default:
-                        if (!mValidFolders.get(container)) {
-                            FileLog.d(TAG, String.format("Skipping item %d, type %d not in a valid folder %d", id, type, container));
-                            continue;
-                        }
-                }
-
-                Intent intent = null;
-                switch (type) {
-                    case Favorites.ITEM_TYPE_FOLDER: {
-                        mValidFolders.put(id, true);
-                        // Use a empty intent to indicate a folder.
-                        intent = new Intent();
-                        break;
-                    }
-                    case Favorites.ITEM_TYPE_APPWIDGET: {
-                        values.put(Favorites.RESTORED,
-                                LauncherAppWidgetInfo.FLAG_ID_NOT_VALID |
-                                        LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY |
-                                        LauncherAppWidgetInfo.FLAG_UI_NOT_READY);
-                        values.put(Favorites.APPWIDGET_PROVIDER, c.getString(widgetProviderIndex));
-                        break;
-                    }
-                    case Favorites.ITEM_TYPE_SHORTCUT:
-                    case Favorites.ITEM_TYPE_APPLICATION: {
-                        intent = Intent.parseUri(c.getString(intentIndex), 0);
-                        if (PackageManagerHelper.isLauncherAppTarget(intent)) {
-                            type = Favorites.ITEM_TYPE_APPLICATION;
-                        } else {
-                            values.put(Favorites.ICON_PACKAGE, c.getString(iconPackageIndex));
-                            values.put(Favorites.ICON_RESOURCE, c.getString(iconResourceIndex));
-                        }
-                        values.put(Favorites.ICON,  c.getBlob(iconIndex));
-                        values.put(Favorites.INTENT, intent.toUri(0));
-                        values.put(Favorites.RANK, c.getInt(rankIndex));
-
-                        values.put(Favorites.RESTORED, 1);
-                        break;
-                    }
-                    default:
-                        FileLog.d(TAG, String.format("Skipping item %d, not a valid type %d", id, type));
-                        continue;
-                }
-
-                if (container == Favorites.CONTAINER_HOTSEAT) {
-                    if (intent == null) {
-                        FileLog.d(TAG, String.format("Skipping item %d, null intent on hotseat", id));
-                        continue;
-                    }
-                    if (intent.getComponent() != null) {
-                        intent.setPackage(intent.getComponent().getPackageName());
-                    }
-                    hotseatTargetApps.add(getPackage(intent));
-                }
-
-                values.put(Favorites._ID, id);
-                values.put(Favorites.ITEM_TYPE, type);
-                values.put(Favorites.CONTAINER, container);
-                values.put(Favorites.SCREEN, screen);
-                values.put(Favorites.CELLX, cellX);
-                values.put(Favorites.CELLY, cellY);
-                values.put(Favorites.SPANX, spanX);
-                values.put(Favorites.SPANY, spanY);
-                values.put(Favorites.TITLE, c.getString(titleIndex));
-                insertOperations.add(ContentProviderOperation
-                        .newInsert(Favorites.CONTENT_URI).withValues(values).build());
-                if (container < 0) {
-                    totalItemsOnWorkspace++;
-                }
-
-                if (insertOperations.size() >= BATCH_INSERT_SIZE) {
-                    mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY,
-                            insertOperations);
-                    insertOperations.clear();
-                }
-            }
-        }
-        FileLog.d(TAG, totalItemsOnWorkspace + " items imported from external source");
-        if (totalItemsOnWorkspace < MIN_ITEM_COUNT_FOR_SUCCESSFUL_MIGRATION) {
-            throw new Exception("Insufficient data");
-        }
-        if (!insertOperations.isEmpty()) {
-            mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY,
-                    insertOperations);
-            insertOperations.clear();
-        }
-
-        IntSparseArrayMap<Object> hotseatItems = GridSizeMigrationTask
-                .removeBrokenHotseatItems(mContext);
-        int myHotseatCount = LauncherAppState.getIDP(mContext).numDatabaseHotseatIcons;
-        if (hotseatItems.size() < myHotseatCount) {
-            // Insufficient hotseat items. Add a few more.
-            HotseatParserCallback parserCallback = new HotseatParserCallback(
-                    hotseatTargetApps, hotseatItems, insertOperations, maxId + 1, myHotseatCount);
-            new HotseatLayoutParser(mContext,
-                    parserCallback).loadLayout(null, new IntArray());
-            mHotseatSize = hotseatItems.keyAt(hotseatItems.size() - 1) + 1;
-
-            if (!insertOperations.isEmpty()) {
-                mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY,
-                        insertOperations);
-            }
-        }
-    }
-
-    private static String getPackage(Intent intent) {
-        return intent.getComponent() != null ? intent.getComponent().getPackageName()
-            : intent.getPackage();
-    }
-
-    /**
-     * Performs data import if possible.
-     * @return true on successful data import, false if it was not available
-     * @throws Exception if the import failed
-     */
-    public static boolean performImportIfPossible(Context context) throws Exception {
-        SharedPreferences devicePrefs = getDevicePrefs(context);
-        String sourcePackage = devicePrefs.getString(KEY_DATA_IMPORT_SRC_PKG, "");
-        String sourceAuthority = devicePrefs.getString(KEY_DATA_IMPORT_SRC_AUTHORITY, "");
-
-        if (TextUtils.isEmpty(sourcePackage) || TextUtils.isEmpty(sourceAuthority)) {
-            return false;
-        }
-
-        // Synchronously clear the migration flags. This ensures that we do not try migration
-        // again and thus prevents potential crash loops due to migration failure.
-        devicePrefs.edit().remove(KEY_DATA_IMPORT_SRC_PKG).remove(KEY_DATA_IMPORT_SRC_AUTHORITY).commit();
-
-        if (!Settings.call(context.getContentResolver(), Settings.METHOD_WAS_EMPTY_DB_CREATED)
-                .getBoolean(Settings.EXTRA_VALUE, false)) {
-            // Only migration if a new DB was created.
-            return false;
-        }
-
-        for (ProviderInfo info : context.getPackageManager().queryContentProviders(
-                null, context.getApplicationInfo().uid, 0)) {
-
-            if (sourcePackage.equals(info.packageName)) {
-                if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
-                    // Only migrate if the source launcher is also on system image.
-                    return false;
-                }
-
-                // Wait until we found a provider with matching authority.
-                if (sourceAuthority.equals(info.authority)) {
-                    if (TextUtils.isEmpty(info.readPermission) ||
-                            context.checkPermission(info.readPermission, Process.myPid(),
-                                    Process.myUid()) == PackageManager.PERMISSION_GRANTED) {
-                        // All checks passed, run the import task.
-                        return new ImportDataTask(context, sourceAuthority).importWorkspace();
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Extension of {@link DefaultLayoutParser} which only allows icons and shortcuts.
-     */
-    private static class HotseatLayoutParser extends DefaultLayoutParser {
-        public HotseatLayoutParser(Context context, LayoutParserCallback callback) {
-            super(context, null, callback, context.getResources(),
-                    LauncherAppState.getIDP(context).defaultLayoutId);
-        }
-
-        @Override
-        protected ArrayMap<String, TagParser> getLayoutElementsMap() {
-            // Only allow shortcut parsers
-            ArrayMap<String, TagParser> parsers = new ArrayMap<>();
-            parsers.put(TAG_FAVORITE, new AppShortcutWithUriParser());
-            parsers.put(TAG_SHORTCUT, new UriShortcutParser(mSourceRes));
-            parsers.put(TAG_RESOLVE, new ResolveParser());
-            return parsers;
-        }
-    }
-
-    /**
-     * {@link LayoutParserCallback} which adds items in empty hotseat spots.
-     */
-    private static class HotseatParserCallback implements LayoutParserCallback {
-        private final HashSet<String> mExistingApps;
-        private final IntSparseArrayMap<Object> mExistingItems;
-        private final ArrayList<ContentProviderOperation> mOutOps;
-        private final int mRequiredSize;
-        private int mStartItemId;
-
-        HotseatParserCallback(
-                HashSet<String> existingApps, IntSparseArrayMap<Object> existingItems,
-                ArrayList<ContentProviderOperation> outOps, int startItemId, int requiredSize) {
-            mExistingApps = existingApps;
-            mExistingItems = existingItems;
-            mOutOps = outOps;
-            mRequiredSize = requiredSize;
-            mStartItemId = startItemId;
-        }
-
-        @Override
-        public int generateNewItemId() {
-            return mStartItemId++;
-        }
-
-        @Override
-        public int insertAndCheck(SQLiteDatabase db, ContentValues values) {
-            if (mExistingItems.size() >= mRequiredSize) {
-                // No need to add more items.
-                return 0;
-            }
-            if (!Integer.valueOf(Favorites.CONTAINER_HOTSEAT)
-                    .equals(values.getAsInteger(Favorites.CONTAINER))) {
-                // Ignore items which are not for hotseat.
-                return 0;
-            }
-
-            Intent intent;
-            try {
-                intent = Intent.parseUri(values.getAsString(Favorites.INTENT), 0);
-            } catch (URISyntaxException e) {
-                return 0;
-            }
-            String pkg = getPackage(intent);
-            if (pkg == null || mExistingApps.contains(pkg)) {
-                // The item does not target an app or is already in hotseat.
-                return 0;
-            }
-            mExistingApps.add(pkg);
-
-            // find next vacant spot.
-            int screen = 0;
-            while (mExistingItems.get(screen) != null) {
-                screen++;
-            }
-            mExistingItems.put(screen, intent);
-            values.put(Favorites.SCREEN, screen);
-            mOutOps.add(ContentProviderOperation.newInsert(Favorites.CONTENT_URI).withValues(values).build());
-            return 0;
-        }
-    }
-}
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 7e05a5a..6855bb1 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -16,84 +16,21 @@
 
 package com.android.launcher3.provider;
 
-import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
-import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
 import android.os.Binder;
 import android.os.Process;
-import android.util.Log;
 
-import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.util.IntArray;
 
-import java.util.Locale;
-
 /**
  * A set of utility methods for Launcher DB used for DB updates and migration.
  */
 public class LauncherDbUtils {
 
-    private static final String TAG = "LauncherDbUtils";
-
-    /**
-     * Makes the first screen as screen 0 (if screen 0 already exists,
-     * renames it to some other number).
-     * If the first row of screen 0 is non empty, runs a 'lossy' GridMigrationTask to clear
-     * the first row. The items in the first screen are moved and resized but the carry-forward
-     * items are simply deleted.
-     */
-    public static boolean prepareScreenZeroToHostQsb(Context context, SQLiteDatabase db) {
-        try (SQLiteTransaction t = new SQLiteTransaction(db)) {
-            // Get the first screen
-            final int firstScreenId;
-            try (Cursor c = db.rawQuery(String.format(Locale.ENGLISH,
-                    "SELECT MIN(%1$s) from %2$s where %3$s = %4$d",
-                    Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
-                    Favorites.CONTAINER_DESKTOP), null)) {
-
-                if (!c.moveToNext()) {
-                    // No update needed
-                    t.commit();
-                    return true;
-                }
-
-                firstScreenId = c.getInt(0);
-            }
-
-            if (firstScreenId != 0) {
-                // Rename the first screen to 0.
-                renameScreen(db, firstScreenId, 0);
-            }
-
-            // Check if the first row is empty
-            if (DatabaseUtils.queryNumEntries(db, Favorites.TABLE_NAME,
-                    "container = -100 and screen = 0 and cellY = 0") == 0) {
-                // First row is empty, no need to migrate.
-                t.commit();
-                return true;
-            }
-
-            new LossyScreenMigrationTask(context, LauncherAppState.getIDP(context), db)
-                    .migrateScreen0();
-            t.commit();
-            return true;
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to update workspace size", e);
-            return false;
-        }
-    }
-
-    private static void renameScreen(SQLiteDatabase db, int oldScreen, int newScreen) {
-        String[] whereParams = new String[] { Integer.toString(oldScreen) };
-        ContentValues values = new ContentValues();
-        values.put(Favorites.SCREEN, newScreen);
-        db.update(Favorites.TABLE_NAME, values, "container = -100 and screen = ?", whereParams);
-    }
-
     public static IntArray queryIntArray(SQLiteDatabase db, String tableName, String columnName,
             String selection, String groupBy, String orderBy) {
         IntArray out = new IntArray();
diff --git a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java b/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
deleted file mode 100644
index c0111b9..0000000
--- a/src/com/android/launcher3/provider/LossyScreenMigrationTask.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2016 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.provider;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.graphics.Point;
-
-import com.android.launcher3.InvariantDeviceProfile;
-import com.android.launcher3.LauncherSettings.Favorites;
-import com.android.launcher3.Utilities;
-import com.android.launcher3.Workspace;
-import com.android.launcher3.model.GridSizeMigrationTask;
-import com.android.launcher3.util.IntSparseArrayMap;
-
-import java.util.ArrayList;
-
-/**
- * An extension of {@link GridSizeMigrationTask} which migrates only one screen and
- * deletes all carry-forward items.
- */
-public class LossyScreenMigrationTask extends GridSizeMigrationTask {
-
-    private final IntSparseArrayMap<DbEntry> mOriginalItems;
-    private final IntSparseArrayMap<DbEntry> mUpdates;
-
-    protected LossyScreenMigrationTask(
-            Context context, InvariantDeviceProfile idp, SQLiteDatabase db) {
-        // Decrease the rows count by 1
-        super(context, db, getValidPackages(context), false /* usePreviewTable */,
-                new Point(idp.numColumns, idp.numRows + 1),
-                new Point(idp.numColumns, idp.numRows));
-
-        mOriginalItems = new IntSparseArrayMap<>();
-        mUpdates = new IntSparseArrayMap<>();
-    }
-
-    @Override
-    protected void update(DbEntry item) {
-        mUpdates.put(item.id, item.copy());
-    }
-
-    @Override
-    protected ArrayList<DbEntry> loadWorkspaceEntries(int screen) {
-        ArrayList<DbEntry> result = super.loadWorkspaceEntries(screen);
-        for (DbEntry entry : result) {
-            mOriginalItems.put(entry.id, entry.copy());
-
-            // Shift all items by 1 in y direction and mark them for update.
-            entry.cellY++;
-            mUpdates.put(entry.id, entry.copy());
-        }
-
-        return result;
-    }
-
-    public void migrateScreen0() {
-        migrateScreen(Workspace.FIRST_SCREEN_ID);
-
-        ContentValues tempValues = new ContentValues();
-        for (DbEntry update : mUpdates) {
-            DbEntry org = mOriginalItems.get(update.id);
-
-            if (org.cellX != update.cellX || org.cellY != update.cellY
-                    || org.spanX != update.spanX || org.spanY != update.spanY) {
-                tempValues.clear();
-                update.addToContentValues(tempValues);
-                mDb.update(Favorites.TABLE_NAME, tempValues, "_id = ?",
-                        new String[] {Integer.toString(update.id)});
-            }
-        }
-
-        // Delete any carry over items as we are only migration a single screen.
-        for (DbEntry entry : mCarryOver) {
-            mEntryToRemove.add(entry.id);
-        }
-
-        if (!mEntryToRemove.isEmpty()) {
-            mDb.delete(Favorites.TABLE_NAME,
-                    Utilities.createDbSelectionQuery(Favorites._ID, mEntryToRemove), null);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 944a41f..5106992 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -102,8 +102,11 @@
                 return getUIProperty(Bundle::putParcelable, a -> {
                     WindowInsets insets = a.getWindow()
                             .getDecorView().getRootWindowInsets();
-                    return Insets.max(
-                            insets.getSystemGestureInsets(), insets.getSystemWindowInsets());
+                    return Insets.subtract(
+                            Insets.max(
+                                    insets.getSystemGestureInsets(),
+                                    insets.getSystemWindowInsets()),
+                            Insets.of(0, 0, 0, mDeviceProfile.nonOverlappingTaskbarInset));
                 }, this::getCurrentActivity);
             }
 
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 65bec25..75c0d03 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -112,4 +112,5 @@
     public static final String PERMANENT_DIAG_TAG = "TaplTarget";
     public static final String WORK_PROFILE_REMOVED = "b/159671700";
     public static final String FALLBACK_ACTIVITY_NO_SET = "b/181019015";
+    public static final String THIRD_PARTY_LAUNCHER_NOT_SET = "b/187080582";
 }
diff --git a/src/com/android/launcher3/touch/WorkspaceTouchListener.java b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
index 4fa658e..20d2ad3 100644
--- a/src/com/android/launcher3/touch/WorkspaceTouchListener.java
+++ b/src/com/android/launcher3/touch/WorkspaceTouchListener.java
@@ -41,7 +41,6 @@
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
-import com.android.launcher3.views.OptionsPopupView;
 
 /**
  * Helper class to handle touch on empty space in workspace and show options popup on long press
@@ -175,7 +174,7 @@
                 mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                         HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                 mLauncher.getStatsLogManager().logger().log(LAUNCHER_WORKSPACE_LONGPRESS);
-                OptionsPopupView.showDefaultOptions(mLauncher, mTouchDownPoint.x, mTouchDownPoint.y);
+                mLauncher.showDefaultOptions(mTouchDownPoint.x, mTouchDownPoint.y);
             } else {
                 cancelLongPress();
             }
diff --git a/src/com/android/launcher3/util/IntSet.java b/src/com/android/launcher3/util/IntSet.java
index e5b4f59..4fd06fe 100644
--- a/src/com/android/launcher3/util/IntSet.java
+++ b/src/com/android/launcher3/util/IntSet.java
@@ -37,6 +37,15 @@
     }
 
     /**
+     * Appends the specified IntSet's values to the set if they does not exist, then returns the
+     * original set that now also contains the new values.
+     */
+    public IntSet addAll(IntSet other) {
+        other.forEach(this::add);
+        return this;
+    }
+
+    /**
      * Removes the specified value from the set if it exist.
      */
     public void remove(int value) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index ecdd206..257b18f 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -176,16 +176,6 @@
         return launcher.findViewById(R.id.popup_container);
     }
 
-    public static void showDefaultOptions(Launcher launcher, float x, float y) {
-        float halfSize = launcher.getResources().getDimension(R.dimen.options_menu_thumb_size) / 2;
-        if (x < 0 || y < 0) {
-            x = launcher.getDragLayer().getWidth() / 2;
-            y = launcher.getDragLayer().getHeight() / 2;
-        }
-        RectF target = new RectF(x - halfSize, y - halfSize, x + halfSize, y + halfSize);
-        show(launcher, target, getOptions(launcher), false);
-    }
-
     /**
      * Returns the list of supported actions
      */
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index e36ea90..be83f9a 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -402,7 +402,10 @@
     private boolean updateMaxSpansPerRow() {
         if (getMeasuredWidth() == 0) return false;
 
-        int maxHorizontalSpans = computeMaxHorizontalSpans(mContent,
+        View content = mHasWorkProfile
+                ? mViewPager
+                : mAdapters.get(AdapterHolder.PRIMARY).mWidgetsRecyclerView;
+        int maxHorizontalSpans = computeMaxHorizontalSpans(content,
                 mWidgetSheetContentHorizontalPadding);
         if (mMaxSpansPerRow != maxHorizontalSpans) {
             mMaxSpansPerRow = maxHorizontalSpans;
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index bf4eba0..dcb6dc1 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -153,6 +153,8 @@
     public static String dumpHprofData() {
         String result;
         if (sDumpWasGenerated) {
+            Log.d("b/195319692", "dump has already been generated by another test",
+                    new Exception());
             result = "dump has already been generated by another test";
         } else {
             try {
@@ -167,6 +169,7 @@
                             "am dumpheap " + device.getLauncherPackageName() + " " + fileName);
                 }
                 sDumpWasGenerated = true;
+                Log.d("b/195319692", "sDumpWasGenerated := true", new Exception());
                 result = "memory dump filename: " + fileName;
             } catch (Throwable e) {
                 Log.e(TAG, "dumpHprofData failed", e);
diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
index dad4f2b..2e8048a 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java
@@ -27,6 +27,7 @@
 import com.android.launcher3.tapl.Widget;
 import com.android.launcher3.ui.AbstractLauncherUiTest;
 import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
 import com.android.launcher3.util.rule.ShellCommandRule;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 
@@ -80,6 +81,7 @@
      */
     @Test
     @PortraitLandscape
+    @ScreenRecord //b/195263971
     public void testDragCustomShortcut() throws Throwable {
         clearHomescreen();
         mDevice.pressHome();
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 641e53a..a5694fc 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -87,7 +87,6 @@
     }
 
     @Test
-    @ScreenRecord  //b/192005114
     public void testPinWidgetNoConfig_customPreview() throws Throwable {
         // Command to set custom preview
         Intent command = RequestPinItemActivity.getCommandIntent(
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 93c921e..49934f7 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -728,18 +728,23 @@
             // otherwise waitForIdle may return immediately in case when there was a big enough
             // pause in accessibility events prior to pressing Home.
             final String action;
-            final boolean launcherWasVisible = isLauncherVisible();
             if (getNavigationModel() == NavigationModel.ZERO_BUTTON) {
                 checkForAnomaly();
 
                 final Point displaySize = getRealDisplaySize();
+                boolean gestureStartFromLauncher = isTablet()
+                        ? !isLauncher3() || hasLauncherObject(WORKSPACE_RES_ID)
+                        : isLauncherVisible();
+                GestureScope gestureScope = gestureStartFromLauncher
+                        ? GestureScope.INSIDE_TO_OUTSIDE
+                        : GestureScope.OUTSIDE_WITH_PILFER;
 
                 if (hasLauncherObject(CONTEXT_MENU_RES_ID)) {
                     linearGesture(
                             displaySize.x / 2, displaySize.y - 1,
                             displaySize.x / 2, 0,
                             ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME,
-                            false, GestureScope.INSIDE_TO_OUTSIDE);
+                            false, gestureScope);
                     try (LauncherInstrumentation.Closable c1 = addContextLayer(
                             "Swiped up from context menu to home")) {
                         waitUntilLauncherObjectGone(CONTEXT_MENU_RES_ID);
@@ -760,9 +765,7 @@
                             displaySize.x / 2, displaySize.y - 1,
                             displaySize.x / 2, 0,
                             ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME, NORMAL_STATE_ORDINAL,
-                            launcherWasVisible || isTablet()
-                                    ? GestureScope.INSIDE_TO_OUTSIDE
-                                    : GestureScope.OUTSIDE_WITH_PILFER);
+                            gestureScope);
                 }
             } else {
                 log("Hierarchy before clicking home:");
@@ -1115,9 +1118,9 @@
                 "swiping");
     }
 
-    private int getBottomGestureSize() {
-        return ResourceUtils.getNavbarSize(
-                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources()) + 1;
+    int getBottomGestureSize() {
+        return Math.max(getTargetInsets().bottom, ResourceUtils.getNavbarSize(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, getResources())) + 1;
     }
 
     int getBottomGestureMarginInContainer(UiObject2 container) {
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 2acf7b4..db2e250 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -34,7 +34,6 @@
 import androidx.test.uiautomator.Direction;
 import androidx.test.uiautomator.UiObject2;
 
-import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.testing.TestProtocol;
 
 import java.util.regex.Pattern;
@@ -72,8 +71,7 @@
                      mLauncher.addContextLayer("want to switch from workspace to all apps")) {
             verifyActiveContainer();
             final int deviceHeight = mLauncher.getDevice().getDisplayHeight();
-            final int bottomGestureMargin = ResourceUtils.getNavbarSize(
-                    ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mLauncher.getResources());
+            final int bottomGestureMargin = mLauncher.getBottomGestureSize();
             final int windowCornerRadius = (int) Math.ceil(mLauncher.getWindowCornerRadius());
             final int startY = deviceHeight - Math.max(bottomGestureMargin, windowCornerRadius) - 1;
             final int swipeHeight = mLauncher.getTestInfo(