Merge "launcher: use a different task menu for foldables" into sc-v2-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index a83f51a..51d7914 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1558,7 +1558,8 @@
 
                 View workspaceView = findWorkspaceView(appTargets);
                 boolean isWorkspaceViewVisible = workspaceView != null
-                        && !mLauncher.isInState(LauncherState.ALL_APPS);
+                        && !mLauncher.isInState(LauncherState.ALL_APPS)
+                        && !mLauncher.getWorkspace().isOverlayShown();
                 boolean playFallBackAnimation = !isWorkspaceViewVisible
                         && (launcherIsATargetWithMode(appTargets, MODE_OPENING)
                         || mLauncher.isForceInvisible());
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index a0a940b..45b2081 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -122,6 +122,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Arrays;
 import java.util.function.Consumer;
 
 /**
@@ -976,12 +977,13 @@
     }
 
     /** @return Whether this was the task we were waiting to appear, and thus handled it. */
-    protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+    protected boolean handleTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {
         if (mStateCallback.hasStates(STATE_HANDLER_INVALIDATED)) {
             return false;
         }
-        if (mStateCallback.hasStates(STATE_START_NEW_TASK)
-                && appearedTaskTarget.taskId == mGestureState.getLastStartedTaskId()) {
+        boolean hasStartedTaskBefore = Arrays.stream(appearedTaskTarget).anyMatch(
+                targetCompat -> targetCompat.taskId == mGestureState.getLastStartedTaskId());
+        if (mStateCallback.hasStates(STATE_START_NEW_TASK) && hasStartedTaskBefore) {
             reset();
             return true;
         }
@@ -1809,13 +1811,8 @@
                 mGestureState.updateLastStartedTaskId(taskId);
                 boolean hasTaskPreviouslyAppeared = mGestureState.getPreviouslyAppearedTaskIds()
                         .contains(taskId);
-                boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
-                        .getRunningSplitTaskIds().length > 0;
                 nextTask.launchTask(success -> {
                     resultCallback.accept(success);
-                    if (isOldTaskSplit) {
-                        SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(taskId);
-                    }
                     if (success) {
                         if (hasTaskPreviouslyAppeared) {
                             onRestartPreviouslyAppearedTask();
@@ -1871,9 +1868,9 @@
     }
 
     @Override
-    public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+    public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
         if (mRecentsAnimationController != null) {
-            if (handleTaskAppeared(appearedTaskTarget)) {
+            if (handleTaskAppeared(appearedTaskTargets)) {
                 mRecentsAnimationController.finish(false /* toRecents */,
                         null /* onFinishComplete */);
                 mActivityInterface.onLaunchTaskSuccess();
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index 834bc55..f9b749e 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -306,7 +306,6 @@
      * Calculates the overview grid size for the provided device configuration.
      */
     public final void calculateGridSize(Context context, DeviceProfile dp, Rect outRect) {
-        Resources res = context.getResources();
         Rect insets = dp.getInsets();
         int topMargin = dp.overviewTaskThumbnailTopMarginPx;
         int bottomMargin = getOverviewActionsHeight(context, dp);
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index fed5ae5..a82137e 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -147,7 +147,7 @@
     }
 
     @Override
-    protected boolean handleTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+    protected boolean handleTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {
         if (mActiveAnimationFactory != null
                 && mActiveAnimationFactory.handleHomeTaskAppeared(appearedTaskTarget)) {
             mActiveAnimationFactory = null;
@@ -260,7 +260,8 @@
             }
         }
 
-        public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+        public boolean handleHomeTaskAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
+            RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0];
             if (appearedTaskTarget.activityType == ACTIVITY_TYPE_HOME) {
                 RemoteAnimationTargets targets = new RemoteAnimationTargets(
                         new RemoteAnimationTargetCompat[] {appearedTaskTarget},
diff --git a/quickstep/src/com/android/quickstep/RecentTasksList.java b/quickstep/src/com/android/quickstep/RecentTasksList.java
index 79dd633..c5f4a53 100644
--- a/quickstep/src/com/android/quickstep/RecentTasksList.java
+++ b/quickstep/src/com/android/quickstep/RecentTasksList.java
@@ -27,12 +27,14 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.quickstep.util.GroupTask;
 import com.android.launcher3.util.LooperExecutor;
-import com.android.systemui.shared.recents.model.GroupTask;
+import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.wm.shell.recents.IRecentTasksListener;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
+import com.android.wm.shell.util.StagedSplitBounds;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -192,12 +194,23 @@
                                 tmpLockedUsers.get(task2Key.userId) /* isLocked */);
                 task2.setLastSnapshotData(taskInfo2);
             }
-            allTasks.add(new GroupTask(task1, task2));
+            final SplitConfigurationOptions.StagedSplitBounds launcherSplitBounds =
+                    convertSplitBounds(rawTask.mStagedSplitBounds);
+            allTasks.add(new GroupTask(task1, task2, launcherSplitBounds));
         }
 
         return allTasks;
     }
 
+    private SplitConfigurationOptions.StagedSplitBounds convertSplitBounds(
+            StagedSplitBounds shellSplitBounds) {
+        return shellSplitBounds == null ?
+                null :
+                new SplitConfigurationOptions.StagedSplitBounds(
+                        shellSplitBounds.leftTopBounds, shellSplitBounds.rightBottomBounds,
+                        shellSplitBounds.leftTopTaskId, shellSplitBounds.rightBottomTaskId);
+    }
+
     private ArrayList<GroupTask> copyOf(ArrayList<GroupTask> tasks) {
         ArrayList<GroupTask> newTasks = new ArrayList<>();
         for (int i = 0; i < tasks.size(); i++) {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
index 750985a..dd6392c 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationCallbacks.java
@@ -136,10 +136,10 @@
 
     @BinderThread
     @Override
-    public void onTaskAppeared(RemoteAnimationTargetCompat app) {
+    public void onTasksAppeared(RemoteAnimationTargetCompat[] apps) {
         Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
             for (RecentsAnimationListener listener : getListeners()) {
-                listener.onTaskAppeared(app);
+                listener.onTasksAppeared(apps);
             }
         });
     }
@@ -177,6 +177,6 @@
         /**
          * Callback made when a task started from the recents is ready for an app transition.
          */
-        default void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {}
+        default void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTarget) {}
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RecentsModel.java b/quickstep/src/com/android/quickstep/RecentsModel.java
index 71153c6..ac97dd6 100644
--- a/quickstep/src/com/android/quickstep/RecentsModel.java
+++ b/quickstep/src/com/android/quickstep/RecentsModel.java
@@ -34,7 +34,7 @@
 import com.android.launcher3.icons.IconProvider.IconChangeListener;
 import com.android.launcher3.util.Executors.SimpleThreadFactory;
 import com.android.launcher3.util.MainThreadInitializedObject;
-import com.android.systemui.shared.recents.model.GroupTask;
+import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
diff --git a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
index 5f2b49d..b031c47 100644
--- a/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
+++ b/quickstep/src/com/android/quickstep/RemoteTargetGluer.java
@@ -20,7 +20,7 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.launcher3.util.SplitConfigurationOptions;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.quickstep.util.AnimatorControllerWithResistance;
 import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.TaskViewSimulator;
@@ -33,7 +33,7 @@
  */
 public class RemoteTargetGluer {
     private RemoteTargetHandle[] mRemoteTargetHandles;
-    private SplitConfigurationOptions.StagedSplitBounds mStagedSplitBounds;
+    private StagedSplitBounds mStagedSplitBounds;
 
     /**
      * Use this constructor if remote targets are split-screen independent
@@ -85,13 +85,21 @@
 
     /**
      * Similar to {@link #assignTargets(RemoteAnimationTargets)}, except this matches the
-     * apps in targets.apps to that of the split screened tasks. If split screen is active, then
-     * {@link #mRemoteTargetHandles} index 0 will be the left/top task, index one right/bottom
+     * apps in targets.apps to that of the _active_ split screened tasks.
+     * See {@link #assignTargetsForSplitScreen(RemoteAnimationTargets, int[])}
      */
     public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets) {
         int[] splitIds = LauncherSplitScreenListener.INSTANCE.getNoCreate()
                 .getRunningSplitTaskIds();
+        return assignTargetsForSplitScreen(targets, splitIds);
+    }
 
+    /**
+     * Assigns the provided splitIDs to the {@link #mRemoteTargetHandles}, with index 0 will beint
+     * the left/top task, index 1 right/bottom
+     */
+    public RemoteTargetHandle[] assignTargetsForSplitScreen(RemoteAnimationTargets targets,
+            int[] splitIds) {
         RemoteAnimationTargetCompat primaryTaskTarget;
         RemoteAnimationTargetCompat secondaryTaskTarget;
         if (mRemoteTargetHandles.length == 1) {
@@ -108,9 +116,9 @@
             primaryTaskTarget = targets.findTask(splitIds[0]);
             secondaryTaskTarget = targets.findTask(splitIds[1]);
 
-            mStagedSplitBounds = new SplitConfigurationOptions.StagedSplitBounds(
+            mStagedSplitBounds = new StagedSplitBounds(
                     primaryTaskTarget.screenSpaceBounds,
-                    secondaryTaskTarget.screenSpaceBounds);
+                    secondaryTaskTarget.screenSpaceBounds, splitIds[0], splitIds[1]);
             mRemoteTargetHandles[0].mTransformParams.setTargetSet(
                     createRemoteAnimationTargetsForTarget(primaryTaskTarget, targets));
             mRemoteTargetHandles[0].mTaskViewSimulator.setPreview(primaryTaskTarget,
@@ -135,7 +143,7 @@
         return mRemoteTargetHandles;
     }
 
-    public SplitConfigurationOptions.StagedSplitBounds getStagedSplitBounds() {
+    public StagedSplitBounds getStagedSplitBounds() {
         return mStagedSplitBounds;
     }
 
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index a875b69..4239739 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -572,53 +572,6 @@
         mPendingSplitScreenListener = null;
     }
 
-    public void setSideStageVisibility(boolean visible) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.setSideStageVisibility(visible);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call setSideStageVisibility");
-            }
-        }
-    }
-
-    /**
-     * To be called whenever the user exits out of split screen apps (either by launching another
-     * app or by swiping home)
-     * @param topTaskId The taskId of the new app that was launched. System will then move this task
-     *                  to the front of what the user sees while removing all other split stages.
-     *                  If swiping to home (or there is no task to put at the top), can pass in -1.
-     */
-    public void exitSplitScreen(int topTaskId) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.exitSplitScreen(topTaskId);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call exitSplitScreen");
-            }
-        }
-    }
-
-    public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.exitSplitScreenOnHide(exitSplitScreenOnHide);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call exitSplitScreen");
-            }
-        }
-    }
-
-    public void startTask(int taskId, int stage, int position, Bundle options) {
-        if (mSplitScreen != null) {
-            try {
-                mSplitScreen.startTask(taskId, stage, position, options);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed call startTask");
-            }
-        }
-    }
-
     /** Start multiple tasks in split-screen simultaneously. */
     public void startTasks(int mainTaskId, Bundle mainOptions, int sideTaskId, Bundle sideOptions,
             @SplitConfigurationOptions.StagePosition int sidePosition,
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index ed7f0b3..e69330a 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -147,16 +147,16 @@
             }
 
             @Override
-            public void onTaskAppeared(RemoteAnimationTargetCompat appearedTaskTarget) {
+            public void onTasksAppeared(RemoteAnimationTargetCompat[] appearedTaskTargets) {
+                RemoteAnimationTargetCompat appearedTaskTarget = appearedTaskTargets[0];
                 BaseActivityInterface activityInterface = mLastGestureState.getActivityInterface();
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && activityInterface.isInLiveTileMode()
                         && activityInterface.getCreatedActivity() != null) {
                     RecentsView recentsView =
                             activityInterface.getCreatedActivity().getOverviewPanel();
                     if (recentsView != null) {
-                        RemoteAnimationTargetCompat[] apps = new RemoteAnimationTargetCompat[1];
-                        apps[0] = appearedTaskTarget;
-                        recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId, apps,
+                        recentsView.launchSideTaskInLiveTileMode(appearedTaskTarget.taskId,
+                                appearedTaskTargets,
                                 new RemoteAnimationTargetCompat[0] /* wallpaper */,
                                 new RemoteAnimationTargetCompat[0] /* nonApps */);
                         return;
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 15729e1..67a94cc 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -177,8 +177,8 @@
         } else {
             RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
                     recentsView.getSizeStrategy(), targets);
-            if (recentsViewHandles != null && recentsViewHandles.length > 1) {
-                remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets);
+            if (v.containsMultipleTasks()) {
+                remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets, v.getTaskIds());
             } else {
                 remoteTargetHandles = gluer.assignTargets(targets);
             }
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 48315d0..eff59e2 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -42,7 +42,7 @@
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.TaskView;
-import com.android.systemui.shared.recents.model.GroupTask;
+import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 
@@ -145,9 +145,10 @@
         RunningTaskInfo runningTaskInfo = runningTaskInfos[0];
         if (mHomeTaskInfo != null && runningTaskInfo != null &&
                 mHomeTaskInfo.taskId == runningTaskInfo.taskId
-                && getTaskViewCount() == 0) {
+                && getTaskViewCount() == 0 && mLoadPlanEverApplied) {
             // Do not add a stub task if we are running over home with empty recents, so that we
             // show the empty recents message instead of showing a stub task and later removing it.
+            // Ignore empty task signal if applyLoadPlan has never run.
             return false;
         }
         return super.shouldAddStubTaskView(runningTaskInfos);
@@ -174,7 +175,7 @@
                 newList.addAll(taskGroups);
                 newList.add(new GroupTask(
                         Task.from(new TaskKey(mHomeTaskInfo), mHomeTaskInfo, false),
-                        null));
+                        null, null));
                 taskGroups = newList;
             }
         }
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
index 033fd85..71dca66 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java
@@ -153,10 +153,8 @@
 
         ObjectAnimator anim = mProgress.animateToValue(endValue);
         anim.setDuration(duration).setInterpolator(scrollInterpolatorForVelocity(velocity));
-        if (mRecentsAnimationController != null) {
-            anim.addListener(AnimatorListeners.forSuccessCallback(
-                    () -> mStateCallback.setState(STATE_FLING_FINISHED)));
-        }
+        anim.addListener(AnimatorListeners.forSuccessCallback(
+                () -> mStateCallback.setState(STATE_FLING_FINISHED)));
         anim.start();
     }
 
diff --git a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
index 272a9a1..965c1bc 100644
--- a/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
+++ b/quickstep/src/com/android/quickstep/interaction/AllSetActivity.java
@@ -136,6 +136,9 @@
     protected void onPause() {
         super.onPause();
         clearBinderOverride();
+        if (mSwipeProgress.value >= 1) {
+            finishAndRemoveTask();
+        }
     }
 
     private void clearBinderOverride() {
diff --git a/quickstep/src/com/android/quickstep/util/GroupTask.java b/quickstep/src/com/android/quickstep/util/GroupTask.java
new file mode 100644
index 0000000..e2563e3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/GroupTask.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+package com.android.quickstep.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
+import com.android.systemui.shared.recents.model.Task;
+
+/**
+ * A {@link Task} container that can contain one or two tasks, depending on if the two tasks
+ * are represented as an app-pair in the recents task list.
+ */
+public class GroupTask {
+    public @NonNull Task task1;
+    public @Nullable Task task2;
+    public @Nullable StagedSplitBounds mStagedSplitBounds;
+
+    public GroupTask(@NonNull Task t1, @Nullable Task t2,
+            @Nullable StagedSplitBounds stagedSplitBounds) {
+        task1 = t1;
+        task2 = t2;
+        mStagedSplitBounds = stagedSplitBounds;
+    }
+
+    public GroupTask(@NonNull GroupTask group) {
+        task1 = new Task(group.task1);
+        task2 = group.task2 != null
+                ? new Task(group.task2)
+                : null;
+        mStagedSplitBounds = group.mStagedSplitBounds;
+    }
+
+    public boolean containsTask(int taskId) {
+        return task1.key.id == taskId || (task2 != null && task2.key.id == taskId);
+    }
+
+    public boolean hasMultipleTasks() {
+        return task2 != null;
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
index fa4cddc..99efb39 100644
--- a/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
+++ b/quickstep/src/com/android/quickstep/util/LauncherSplitScreenListener.java
@@ -149,7 +149,6 @@
             return;
         }
 
-        SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(-1);
         mPersistentGroupedIds = EMPTY_ARRAY;
     }
 
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 4c300ec..e1afa97 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -56,6 +56,7 @@
     private Task mInitialTask;
     private Task mSecondTask;
     private Rect mInitialBounds;
+    private boolean mRecentsAnimationRunning;
 
     public SplitSelectStateController(Handler handler, SystemUiProxy systemUiProxy) {
         mHandler = handler;
@@ -113,6 +114,10 @@
         return mStagePosition;
     }
 
+    public void setRecentsAnimationRunning(boolean running) {
+        this.mRecentsAnimationRunning = running;
+    }
+
     /**
      * Requires Shell Transitions
      */
@@ -172,7 +177,9 @@
         public void onAnimationCancelled() {
             postAsyncCallback(mHandler, () -> {
                 if (mSuccessCallback != null) {
-                    mSuccessCallback.accept(false);
+                    // Launching legacy tasks while recents animation is running will always cause
+                    // onAnimationCancelled to be called (should be fixed w/ shell transitions?)
+                    mSuccessCallback.accept(mRecentsAnimationRunning);
                 }
                 resetState();
             });
@@ -187,6 +194,7 @@
         mSecondTask = null;
         mStagePosition = SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
         mInitialBounds = null;
+        mRecentsAnimationRunning = false;
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
index 977c696..280b5d7 100644
--- a/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
+++ b/quickstep/src/com/android/quickstep/util/TaskViewSimulator.java
@@ -22,7 +22,6 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
 import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
-import static com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import static com.android.quickstep.util.RecentsOrientedState.postDisplayRotation;
 import static com.android.quickstep.util.RecentsOrientedState.preDisplayRotation;
 import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_FULLSCREEN;
@@ -41,6 +40,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.util.TraceHelper;
 import com.android.quickstep.AnimatedFloat;
 import com.android.quickstep.BaseActivityInterface;
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index a79b1f3..eace227 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -14,6 +14,7 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.util.RunnableList;
+import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.util.TransformingTouchDelegate;
 import com.android.quickstep.RecentsModel;
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index 5a8cbc1..5c0b50c 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -127,6 +127,9 @@
      * @param callbacks for callbacks, or {@code null} to clear the listener.
      */
     public void setCallbacks(T callbacks) {
+        if (TestProtocol.sDebugTracing) {
+            Log.d(TestProtocol.NO_SCREENSHOT, "OverviewActionsView setCallbacks: " + callbacks);
+        }
         mCallbacks = callbacks;
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index c721e50..1d01b7a 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -137,8 +137,8 @@
 import com.android.launcher3.util.MultiValueAlpha;
 import com.android.launcher3.util.ResourceBasedOverride.Overrides;
 import com.android.launcher3.util.RunnableList;
-import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.util.Themes;
 import com.android.launcher3.util.TranslateEdgeEffect;
 import com.android.launcher3.util.ViewPool;
@@ -157,7 +157,6 @@
 import com.android.quickstep.TaskThumbnailCache;
 import com.android.quickstep.TaskViewUtils;
 import com.android.quickstep.ViewUtils;
-import com.android.quickstep.util.LauncherSplitScreenListener;
 import com.android.quickstep.util.LayoutUtils;
 import com.android.quickstep.util.RecentsOrientedState;
 import com.android.quickstep.util.SplitScreenBounds;
@@ -167,7 +166,7 @@
 import com.android.quickstep.util.TransformParams;
 import com.android.quickstep.util.VibratorWrapper;
 import com.android.systemui.plugins.ResourceProvider;
-import com.android.systemui.shared.recents.model.GroupTask;
+import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -573,6 +572,7 @@
 
     // Keeps track of task id whose visual state should not be reset
     private int mIgnoreResetTaskId = -1;
+    protected boolean mLoadPlanEverApplied;
 
     // Variables for empty state
     private final Drawable mEmptyIcon;
@@ -598,7 +598,7 @@
      */
     private TaskView mSplitHiddenTaskView;
     private TaskView mSecondSplitHiddenTaskView;
-    private SplitConfigurationOptions.StagedSplitBounds mSplitBoundsConfig;
+    private StagedSplitBounds mSplitBoundsConfig;
     private final Toast mSplitToast = Toast.makeText(getContext(),
             R.string.toast_split_select_app, Toast.LENGTH_SHORT);
     private final Toast mSplitUnsupportedToast = Toast.makeText(getContext(),
@@ -664,9 +664,8 @@
         mClearAllButton.setOnClickListener(this::dismissAllTasks);
         mTaskViewPool = new ViewPool<>(context, this, R.layout.task, 20 /* max size */,
                 10 /* initial size */);
-        // There's only one pair of grouped tasks we can envision at the moment
         mGroupedTaskViewPool = new ViewPool<>(context, this,
-                R.layout.task_grouped, 2 /* max size */, 1 /* initial size */);
+                R.layout.task_grouped, 20 /* max size */, 10 /* initial size */);
 
         mIsRtl = mOrientationHandler.getRecentsRtlSetting(getResources());
         setLayoutDirection(mIsRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
@@ -1070,6 +1069,10 @@
     }
 
     public TaskView getTaskViewByTaskId(int taskId) {
+        if (taskId == -1) {
+            return null;
+        }
+
         for (int i = 0; i < getTaskViewCount(); i++) {
             TaskView taskView = getTaskViewAt(i);
             int[] taskIds = taskView.getTaskIds();
@@ -1322,72 +1325,34 @@
         TaskView ignoreResetTaskView =
                 mIgnoreResetTaskId == -1 ? null : getTaskViewByTaskId(mIgnoreResetTaskId);
 
-        int[] splitTaskIds =
-                LauncherSplitScreenListener.INSTANCE.getNoCreate().getPersistentSplitIds();
-        int requiredGroupTaskViews = splitTaskIds.length / 2;
-
-        // TODO(b/202740477): Update once grouped tasks are returned
-        // Subtract half the number of split tasks and not total number because we've already
-        // added a GroupedTaskView when swipe up gesture happens.
-        // This will need to change if we start showing GroupedTaskViews during swipe up from home
-        int requiredTaskViewCount = taskGroups.size() - requiredGroupTaskViews;
-
-        if (getTaskViewCount() != requiredTaskViewCount) {
-            if (indexOfChild(mClearAllButton) != -1) {
-                removeView(mClearAllButton);
-            }
-
-            for (int i = getTaskViewCount(); i < requiredTaskViewCount; i++) {
-                addView(getTaskViewFromPool(false));
-            }
-            while (getTaskViewCount() > requiredTaskViewCount) {
-                removeView(getChildAt(getChildCount() - 1));
-            }
-            int groupedTaskViewCount = getGroupedTaskViewCount();
-            while (requiredGroupTaskViews > groupedTaskViewCount) {
-                // Add to front of list
-                addView(getTaskViewFromPool(true), 0);
-                requiredGroupTaskViews--;
-            }
-            if (requiredTaskViewCount > 0) {
-                addView(mClearAllButton);
-            }
-        }
-
         // 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];
-        Log.d(TASK_VIEW_ID_CRASH, "runningTaskId beforeBind: " + runningTaskId
-                + " runningTaskViewId: " + mRunningTaskViewId
-                + " forTaskView: " + getTaskViewFromTaskViewId(mRunningTaskViewId));
 
-        for (int taskViewIndex = requiredTaskViewCount - 1, taskDataIndex = taskGroups.size() - 1;
-                taskViewIndex >= 0;
-                taskViewIndex--, taskDataIndex--) {
-            final int pageIndex = requiredTaskViewCount - taskViewIndex - 1;
-            // TODO(b/202740477): Temporary assumption, to be updated once groups are actually used
-            final Task task = taskGroups.get(taskDataIndex).task1;
-            final TaskView taskView = (TaskView) getChildAt(pageIndex);
-            if (taskView instanceof GroupedTaskView) {
-                Task leftTop;
-                Task rightBottom;
-                if (task.key.id == splitTaskIds[0]) {
-                    leftTop = task;
-                    taskDataIndex--;
-                    rightBottom = taskGroups.get(taskDataIndex).task1;
-                } else {
-                    rightBottom = task;
-                    taskDataIndex--;
-                    leftTop = taskGroups.get(taskDataIndex).task1;
-                }
-                ((GroupedTaskView) taskView).bind(leftTop, rightBottom, mOrientationState,
-                        mSplitBoundsConfig);
+        // Removing views sets the currentPage to 0, so we save this and restore it after
+        // the new set of views are added
+        int previousPage = mCurrentPage;
+        removeAllViews();
+
+        // Add views as children based on whether it's grouped or single task
+        for (int i = taskGroups.size() - 1; i >= 0; i--) {
+            GroupTask groupTask = taskGroups.get(i);
+            boolean hasMultipleTasks = groupTask.hasMultipleTasks();
+            TaskView taskView = getTaskViewFromPool(hasMultipleTasks);
+            addView(taskView);
+
+            if (hasMultipleTasks) {
+                ((GroupedTaskView) taskView).bind(groupTask.task1, groupTask.task2,
+                        mOrientationState, groupTask.mStagedSplitBounds);
             } else {
-                taskView.bind(task, mOrientationState);
+                taskView.bind(groupTask.task1, mOrientationState);
             }
         }
+        if (!taskGroups.isEmpty()) {
+            addView(mClearAllButton);
+        }
+        setCurrentPage(previousPage);
 
         // Keep same previous focused task
         TaskView newFocusedTaskView = getTaskViewByTaskId(focusedTaskId);
@@ -1405,21 +1370,10 @@
             // Update mRunningTaskViewId to be the new TaskView that was assigned by binding
             // the full list of tasks to taskViews
             newRunningTaskView = getTaskViewByTaskId(runningTaskId);
-            if (newRunningTaskView == null) {
-                StringBuilder sb = new StringBuilder();
-                for (int i = requiredTaskViewCount - 1; i >= 0; i--) {
-                    final int pageIndex = requiredTaskViewCount - i - 1;
-                    final TaskView taskView = (TaskView) getChildAt(pageIndex);
-                    int taskViewId = taskView.getTaskViewId();
-                    sb.append(" taskViewId: " + taskViewId
-                            + " taskId: " + getTaskIdsForTaskViewId(taskViewId)[0]
-                            + " for taskView: " + taskView + "\n");
-                }
-                Log.d(TASK_VIEW_ID_CRASH, "taskViewCount: " + getTaskViewCount()
-                        + " " + sb.toString());
-                mRunningTaskViewId = -1;
-            } else {
+            if (newRunningTaskView != null) {
                 mRunningTaskViewId = newRunningTaskView.getTaskViewId();
+            } else {
+                mRunningTaskViewId = -1;
             }
         }
 
@@ -1451,6 +1405,7 @@
         resetTaskVisuals();
         onTaskStackUpdated();
         updateEnabledOverlays();
+        mLoadPlanEverApplied = true;
     }
 
     private boolean isModal() {
@@ -1919,6 +1874,7 @@
             remoteTargetHandle.getTransformParams().setTargetSet(null);
             remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(true);
         });
+        mSplitSelectStateController.resetState();
 
         // These are relatively expensive and don't need to be done this frame (RecentsView isn't
         // visible anyway), so defer by a frame to get off the critical path, e.g. app to home.
@@ -2768,6 +2724,9 @@
         boolean isSplitPlaceholderFirstInGrid = isSplitPlaceholderFirstInGrid();
         boolean isSplitPlaceholderLastInGrid = isSplitPlaceholderLastInGrid();
         TaskView lastGridTaskView = showAsGrid ? getLastGridTaskView() : null;
+        int currentPageScroll = getScrollForPage(mCurrentPage);
+        int lastGridTaskScroll = getScrollForPage(indexOfChild(lastGridTaskView));
+        boolean currentPageSnapsToEndOfGrid = currentPageScroll == lastGridTaskScroll;
         if (lastGridTaskView != null && lastGridTaskView.isVisibleToUser()) {
             // After dismissal, animate translation of the remaining tasks to fill any gap left
             // between the end of the grid and the clear all button. Only animate if the clear
@@ -2811,8 +2770,7 @@
                     // Shift all the tasks to make space for split placeholder.
                     longGridRowWidthDiff += mIsRtl ? mSplitPlaceholderSize : -mSplitPlaceholderSize;
                 }
-            } else if (isLandscapeSplit && getScrollForPage(mCurrentPage)
-                    == getScrollForPage(indexOfChild(lastGridTaskView))) {
+            } else if (isLandscapeSplit && currentPageSnapsToEndOfGrid) {
                 // Use last task as reference point for scroll diff and snapping calculation as it's
                 // the only invariant point in landscape split screen.
                 snapToLastTask = true;
@@ -3183,14 +3141,39 @@
                                 }
                             }
 
+                            TaskView newLastGridTaskView = getLastGridTaskView();
                             if (finalSnapToLastTask) {
                                 // If snapping to last task, find the last task after dismissal.
-                                pageToSnapTo = indexOfChild(getLastGridTaskView());
+                                pageToSnapTo = indexOfChild(newLastGridTaskView);
                             } else if (taskViewIdToSnapTo != -1) {
                                 // If snapping to another page due to indices rearranging, find
                                 // the new index after dismissal & rearrange using the task view id.
                                 pageToSnapTo = indexOfChild(
                                         getTaskViewFromTaskViewId(taskViewIdToSnapTo));
+                                int taskViewToSnapToScroll = getScrollForPage(pageToSnapTo);
+                                int lastGridTaskScroll = getScrollForPage(
+                                        indexOfChild(newLastGridTaskView));
+                                if (!currentPageSnapsToEndOfGrid
+                                        && taskViewToSnapToScroll == lastGridTaskScroll) {
+                                    // If it wasn't snapped to one of the last pages, but is now
+                                    // snapped to last pages, we'll need to compensate for the
+                                    // difference as last pages' scroll is the position where
+                                    // ClearAllButton is barely invisible, instead of aligned to
+                                    // mLastComputedTaskSize.
+                                    int normalTaskEnd = mIsRtl
+                                            ? mLastComputedTaskSize.right
+                                            : mLastComputedTaskSize.left;
+                                    int lastTaskStart = mIsRtl
+                                            ? mLastComputedGridSize.left
+                                            : mLastComputedGridSize.right;
+                                    // As snapped task is not the last task, it can only be the
+                                    // second last task.
+                                    int distanceToSnappedTaskEnd =
+                                            (mPageSpacing + mLastComputedGridTaskSize.width()) * 2;
+                                    int snappedTaskEnd = lastTaskStart + (mIsRtl
+                                            ? distanceToSnappedTaskEnd : -distanceToSnappedTaskEnd);
+                                    mCurrentPageScrollDiff += snappedTaskEnd - normalTaskEnd;
+                                }
                             }
                         }
                         setCurrentPage(pageToSnapTo);
@@ -3817,7 +3800,8 @@
      * if RecentsView is in portrait or RecentsView isn't shown as grid.
      */
     private boolean isSplitPlaceholderFirstInGrid() {
-        if (!mActivity.getDeviceProfile().isLandscape || !showAsGrid()) {
+        if (!mActivity.getDeviceProfile().isLandscape || !showAsGrid()
+                || !isSplitSelectionActive()) {
             return false;
         }
         @StagePosition int position = mSplitSelectStateController.getActiveSplitStagePosition();
@@ -3831,7 +3815,8 @@
      * RecentsView is in portrait or RecentsView isn't shown as grid.
      */
     private boolean isSplitPlaceholderLastInGrid() {
-        if (!mActivity.getDeviceProfile().isLandscape || !showAsGrid()) {
+        if (!mActivity.getDeviceProfile().isLandscape || !showAsGrid()
+                || !isSplitSelectionActive()) {
             return false;
         }
         @StagePosition int position = mSplitSelectStateController.getActiveSplitStagePosition();
@@ -4349,6 +4334,7 @@
     public void setRecentsAnimationTargets(RecentsAnimationController recentsAnimationController,
             RecentsAnimationTargets recentsAnimationTargets) {
         mRecentsAnimationController = recentsAnimationController;
+        mSplitSelectStateController.setRecentsAnimationRunning(true);
         if (recentsAnimationTargets == null || recentsAnimationTargets.apps.length == 0) {
             return;
         }
@@ -4448,6 +4434,7 @@
         // taps on QSB (3) user goes back to Overview and launch the most recent task.
         setCurrentTask(-1);
         mRecentsAnimationController = null;
+        mSplitSelectStateController.setRecentsAnimationRunning(false);
         executeSideTaskLaunchCallback();
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 2ee78dc..8996127 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -557,6 +557,10 @@
         return mTaskIdContainer;
     }
 
+    public boolean containsMultipleTasks() {
+        return mTaskIdContainer[1] != -1;
+    }
+
     public TaskThumbnailView getThumbnail() {
         return mSnapshotView;
     }
@@ -689,13 +693,8 @@
                     TestProtocol.SEQUENCE_MAIN, "startActivityFromRecentsAsync", mTask);
             ActivityOptionsWrapper opts =  mActivity.getActivityLaunchOptions(this, null);
             opts.options.setLaunchDisplayId(getRootViewDisplayId());
-            boolean isOldTaskSplit = LauncherSplitScreenListener.INSTANCE.getNoCreate()
-                    .getPersistentSplitIds().length > 0;
             if (ActivityManagerWrapper.getInstance()
                     .startActivityFromRecents(mTask.key, opts.options)) {
-                if (isOldTaskSplit) {
-                    SystemUiProxy.INSTANCE.getNoCreate().exitSplitScreen(mTask.key.id);
-                }
                 RecentsView recentsView = getRecentsView();
                 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskViewId() != -1) {
                     recentsView.onTaskLaunchedInLiveTileMode();
diff --git a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
index 6b2d5ed..4e49716 100644
--- a/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
+++ b/quickstep/tests/src/com/android/quickstep/RecentTasksListTest.java
@@ -30,9 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.launcher3.util.LooperExecutor;
-import com.android.systemui.shared.recents.model.GroupTask;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.quickstep.util.GroupTask;
 import com.android.systemui.shared.system.KeyguardManagerCompat;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 
@@ -73,7 +71,7 @@
     @Test
     public void loadTasksInBackground_onlyKeys_noValidTaskDescription() {
         GroupedRecentTaskInfo recentTaskInfos = new GroupedRecentTaskInfo(
-                new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo());
+                new ActivityManager.RecentTaskInfo(), new ActivityManager.RecentTaskInfo(), null);
         when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
                 .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
 
@@ -93,7 +91,7 @@
         ActivityManager.RecentTaskInfo task2 = new ActivityManager.RecentTaskInfo();
         task2.taskDescription = new ActivityManager.TaskDescription();
         GroupedRecentTaskInfo recentTaskInfos = new GroupedRecentTaskInfo(
-                task1, task2);
+                task1, task2, null);
         when(mockSystemUiProxy.getRecentTasks(anyInt(), anyInt()))
                 .thenReturn(new ArrayList<>(Collections.singletonList(recentTaskInfos)));
 
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index ea65757..93be6fb 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -271,7 +271,9 @@
         background.quickSwitchToPreviousAppSwipeLeft();
         assertTrue("The 2nd app we should have quick switched to is not running",
                 isTestActivityRunning(3));
-        getAndAssertBackground();
+
+        background = getAndAssertBackground();
+        background.switchToOverview();
     }
 
     private boolean isTestActivityRunning(int activityNumber) {
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 35c257f..1f1d57a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -141,6 +141,7 @@
     private final CheckLongPressHelper mLongPressHelper;
 
     private final boolean mLayoutHorizontal;
+    private final boolean mIsRtl;
     private final int mIconSize;
 
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -185,6 +186,8 @@
         TypedArray a = context.obtainStyledAttributes(attrs,
                 R.styleable.BubbleTextView, defStyle, 0);
         mLayoutHorizontal = a.getBoolean(R.styleable.BubbleTextView_layoutHorizontal, false);
+        mIsRtl = (getResources().getConfiguration().getLayoutDirection()
+                == View.LAYOUT_DIRECTION_RTL);
         DeviceProfile grid = mActivity.getDeviceProfile();
 
         mDisplay = a.getInteger(R.styleable.BubbleTextView_iconDisplay, DISPLAY_WORKSPACE);
@@ -581,19 +584,29 @@
         return mDotInfo != null;
     }
 
+    /**
+     * Get the icon bounds on the view depending on the layout type.
+     */
     public void getIconBounds(Rect outBounds) {
-        getIconBounds(this, outBounds, mIconSize);
+        getIconBounds(mIconSize, outBounds);
     }
 
-    public static void getIconBounds(View iconView, Rect outBounds, int iconSize) {
-        int top = iconView.getPaddingTop();
-        int left = (iconView.getWidth() - iconSize) / 2;
-        int right = left + iconSize;
-        int bottom = top + iconSize;
-        outBounds.set(left, top, right, bottom);
+    /**
+     * Get the icon bounds on the view depending on the layout type.
+     */
+    public void getIconBounds(int iconSize, Rect outBounds) {
+        Utilities.setRectToViewCenter(this, iconSize, outBounds);
+        if (mLayoutHorizontal) {
+            if (mIsRtl) {
+                outBounds.offsetTo(getWidth() - iconSize - getPaddingRight(), outBounds.top);
+            } else {
+                outBounds.offsetTo(getPaddingLeft(), outBounds.top);
+            }
+        } else {
+            outBounds.offsetTo(outBounds.left, getPaddingTop());
+        }
     }
 
-
     /**
      * Sets whether to vertically center the content.
      */
@@ -980,8 +993,7 @@
 
     @Override
     public void getWorkspaceVisualDragBounds(Rect bounds) {
-        DeviceProfile grid = mActivity.getDeviceProfile();
-        BubbleTextView.getIconBounds(this, bounds, grid.iconSizePx);
+        getIconBounds(mIconSize, bounds);
     }
 
     private int getIconSizeForDisplay(int display) {
@@ -998,7 +1010,7 @@
     }
 
     public void getSourceVisualDragBounds(Rect bounds) {
-        BubbleTextView.getIconBounds(this, bounds, getIconSizeForDisplay(mDisplay));
+        getIconBounds(mIconSize, bounds);
     }
 
     @Override
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index df09f29..9f3d445 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -483,7 +483,7 @@
                             LauncherSettings.Favorites.CONTAINER + " FROM "
                                 + Favorites.TABLE_NAME + ")";
 
-            IntArray folderIds = LauncherDbUtils.queryIntArray(db, Favorites.TABLE_NAME,
+            IntArray folderIds = LauncherDbUtils.queryIntArray(false, db, Favorites.TABLE_NAME,
                     Favorites._ID, selection, null, null);
             if (!folderIds.isEmpty()) {
                 db.delete(Favorites.TABLE_NAME, Utilities.createDbSelectionQuery(
@@ -835,8 +835,8 @@
                 case 27: {
                     // Update the favorites table so that the screen ids are ordered based on
                     // workspace page rank.
-                    IntArray finalScreens = LauncherDbUtils.queryIntArray(db, "workspaceScreens",
-                            BaseColumns._ID, null, null, "screenRank");
+                    IntArray finalScreens = LauncherDbUtils.queryIntArray(false, db,
+                            "workspaceScreens", BaseColumns._ID, null, null, "screenRank");
                     int[] original = finalScreens.toArray();
                     Arrays.sort(original);
                     String updatemap = "";
@@ -919,7 +919,7 @@
                 Log.e(TAG, "getAppWidgetIds not supported", e);
                 return;
             }
-            final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(db,
+            final IntSet validWidgets = IntSet.wrap(LauncherDbUtils.queryIntArray(false, db,
                     Favorites.TABLE_NAME, Favorites.APPWIDGET_ID,
                     "itemType=" + Favorites.ITEM_TYPE_APPWIDGET, null, null));
             for (int widgetId : allWidgets) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 7a38fe7..d2fe483 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -527,6 +527,18 @@
     }
 
     /**
+     * Using the view's bounds and icon size, calculate where the icon bounds will
+     * be if it was positioned at the center of the view.
+     */
+    public static void setRectToViewCenter(View iconView, int iconSize, Rect outBounds) {
+        int top = (iconView.getHeight() - iconSize) / 2;
+        int left = (iconView.getWidth() - iconSize) / 2;
+        int right = left + iconSize;
+        int bottom = top + iconSize;
+        outBounds.set(left, top, right, bottom);
+    }
+
+    /**
      * Ensures that a value is within given bounds. Specifically:
      * If value is less than lowerBound, return lowerBound; else if value is greater than upperBound,
      * return upperBound; else return value unchanged.
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 439df80..98be72a 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -628,7 +628,10 @@
     public void drawDot(Canvas canvas) {
         if (!mForceHideDot && ((mDotInfo != null && mDotInfo.hasDot()) || mDotScale > 0)) {
             Rect iconBounds = mDotParams.iconBounds;
-            BubbleTextView.getIconBounds(this, iconBounds, mActivity.getDeviceProfile().iconSizePx);
+
+            Utilities.setRectToViewCenter(this, mActivity.getDeviceProfile().iconSizePx,
+                    iconBounds);
+            iconBounds.offsetTo(iconBounds.left, getPaddingTop());
             float iconScale = (float) mBackground.previewSize / iconBounds.width();
             Utilities.scaleRectAboutCenter(iconBounds, iconScale);
 
diff --git a/src/com/android/launcher3/provider/LauncherDbUtils.java b/src/com/android/launcher3/provider/LauncherDbUtils.java
index 6855bb1..b510378 100644
--- a/src/com/android/launcher3/provider/LauncherDbUtils.java
+++ b/src/com/android/launcher3/provider/LauncherDbUtils.java
@@ -31,11 +31,11 @@
  */
 public class LauncherDbUtils {
 
-    public static IntArray queryIntArray(SQLiteDatabase db, String tableName, String columnName,
-            String selection, String groupBy, String orderBy) {
+    public static IntArray queryIntArray(boolean distinct, SQLiteDatabase db, String tableName,
+            String columnName, String selection, String groupBy, String orderBy) {
         IntArray out = new IntArray();
-        try (Cursor c = db.query(tableName, new String[] { columnName }, selection, null,
-                groupBy, null, orderBy)) {
+        try (Cursor c = db.query(distinct, tableName, new String[] { columnName }, selection, null,
+                groupBy, null, orderBy, null)) {
             while (c.moveToNext()) {
                 out.add(c.getInt(0));
             }
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 257d732..8f607a1 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.provider;
 
+import static com.android.launcher3.model.DeviceGridState.TYPE_MULTI_DISPLAY;
 import static com.android.launcher3.model.DeviceGridState.TYPE_PHONE;
 import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
 
@@ -96,7 +97,7 @@
         try (SQLiteTransaction t = new SQLiteTransaction(db)) {
             RestoreDbTask task = new RestoreDbTask();
             task.backupWorkspace(context, db);
-            task.sanitizeDB(helper, db, new BackupManager(context));
+            task.sanitizeDB(context, helper, db, new BackupManager(context));
             task.restoreAppWidgetIdsIfExists(context);
             t.commit();
             return true;
@@ -139,7 +140,7 @@
         GridBackupTable backupTable = new GridBackupTable(context, db, idp.numDatabaseHotseatIcons,
                 idp.numColumns, idp.numRows);
         if (backupTable.restoreFromRawBackupIfAvailable(getDefaultProfileId(db))) {
-            int itemsDeleted = sanitizeDB(helper, db, backupManager);
+            int itemsDeleted = sanitizeDB(context, helper, db, backupManager);
             LauncherAppState.getInstance(context).getModel().forceReload();
             restoreAppWidgetIdsIfExists(context);
             if (itemsDeleted == 0) {
@@ -156,11 +157,12 @@
      *      the restored apps get installed.
      *   3. If the user serial for any restored profile is different than that of the previous
      *      device, update the entries to the new profile id.
+     *   4. If restored from a single display backup, remove gaps between screenIds
      *
      * @return number of items deleted.
      */
-    private int sanitizeDB(DatabaseHelper helper, SQLiteDatabase db, BackupManager backupManager)
-            throws Exception {
+    private int sanitizeDB(Context context, DatabaseHelper helper, SQLiteDatabase db,
+            BackupManager backupManager) throws Exception {
         // Primary user ids
         long myProfileId = helper.getDefaultUserSerial();
         long oldProfileId = getDefaultProfileId(db);
@@ -236,10 +238,43 @@
         if (myProfileId != oldProfileId) {
             changeDefaultColumn(db, myProfileId);
         }
+
+        // If restored from a single display backup, remove gaps between screenIds
+        if (Utilities.getPrefs(context).getInt(RESTORED_DEVICE_TYPE, TYPE_PHONE)
+                != TYPE_MULTI_DISPLAY) {
+            removeScreenIdGaps(db);
+        }
+
         return itemsDeleted;
     }
 
     /**
+     * Remove gaps between screenIds to make sure no empty pages are left in between.
+     *
+     * e.g. [0, 3, 4, 6, 7] -> [0, 1, 2, 3, 4]
+     */
+    protected void removeScreenIdGaps(SQLiteDatabase db) {
+        FileLog.d(TAG, "Removing gaps between screenIds");
+        IntArray distinctScreens = LauncherDbUtils.queryIntArray(true, db, Favorites.TABLE_NAME,
+                Favorites.SCREEN, Favorites.CONTAINER + " = " + Favorites.CONTAINER_DESKTOP, null,
+                Favorites.SCREEN);
+        if (distinctScreens.isEmpty()) {
+            return;
+        }
+
+        StringBuilder sql = new StringBuilder("UPDATE ").append(Favorites.TABLE_NAME)
+                .append(" SET ").append(Favorites.SCREEN).append(" =\nCASE\n");
+        int screenId = distinctScreens.contains(0) ? 0 : 1;
+        for (int i = 0; i < distinctScreens.size(); i++) {
+            sql.append("WHEN ").append(Favorites.SCREEN).append(" == ")
+                    .append(distinctScreens.get(i)).append(" THEN ").append(screenId++).append("\n");
+        }
+        sql.append("ELSE screen\nEND WHERE ").append(Favorites.CONTAINER).append(" = ")
+                .append(Favorites.CONTAINER_DESKTOP).append(";");
+        db.execSQL(sql.toString());
+    }
+
+    /**
      * Updates profile id of all entries from {@param oldProfileId} to {@param newProfileId}.
      */
     protected void migrateProfileId(SQLiteDatabase db, long oldProfileId, long newProfileId) {
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index a190f52..93e3ea7 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -390,7 +390,7 @@
     @Override
     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
             int parentWidth, int parentHeight,
-            SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
+            StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
         int dividerBar = splitBoundsConfig.visualDividerBounds.width();
@@ -435,15 +435,18 @@
                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
         FrameLayout.LayoutParams secondaryIconParams =
                 new FrameLayout.LayoutParams(primaryIconParams);
+        int dividerBar = (splitConfig.appsStackedVertically ?
+                splitConfig.visualDividerBounds.height() :
+                splitConfig.visualDividerBounds.width());
 
         int primaryHeight = primarySnapshotBounds.height();
-        int secondaryHeight = secondarySnapshotBounds.height();
         primaryIconParams.gravity = (isRtl ? START : END) | TOP;
-        primaryIconView.setTranslationY((primaryHeight + taskIconHeight) / 2f );
+        primaryIconView.setTranslationY(primaryHeight - primaryIconView.getHeight() / 2f);
+        primaryIconView.setTranslationX(0);
 
         secondaryIconParams.gravity = (isRtl ? START : END) | TOP;
-        secondaryIconView.setTranslationY(primaryHeight
-                + ((secondaryHeight + taskIconHeight) / 2f));
+        secondaryIconView.setTranslationY(primaryHeight + taskIconHeight + dividerBar);
+        secondaryIconView.setTranslationX(0);
         primaryIconView.setLayoutParams(primaryIconParams);
         secondaryIconView.setLayoutParams(secondaryIconParams);
     }
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 8112afd..2ff2feb 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -141,13 +141,12 @@
      * @param desiredStagePosition Which stage position (topLeft/rightBottom) we want to resize
      *                           outRect for
      */
-    void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect,
-            StagedSplitBounds splitInfo,
+    void setSplitTaskSwipeRect(DeviceProfile dp, Rect outRect, StagedSplitBounds splitInfo,
             @SplitConfigurationOptions.StagePosition int desiredStagePosition);
 
     void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
             int parentWidth, int parentHeight,
-            SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig, DeviceProfile dp);
+            StagedSplitBounds splitBoundsConfig, DeviceProfile dp);
 
     // Overview TaskMenuView methods
     void setIconAndSnapshotParams(View iconView, int taskIconMargin, int taskIconHeight,
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index 576c6f5..8caf886 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -16,7 +16,6 @@
 
 package com.android.launcher3.touch;
 
-import static android.view.Gravity.BOTTOM;
 import static android.view.Gravity.CENTER_HORIZONTAL;
 import static android.view.Gravity.START;
 import static android.view.Gravity.TOP;
@@ -477,7 +476,7 @@
     @Override
     public void measureGroupedTaskViewThumbnailBounds(View primarySnapshot, View secondarySnapshot,
             int parentWidth, int parentHeight,
-            SplitConfigurationOptions.StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
+            StagedSplitBounds splitBoundsConfig, DeviceProfile dp) {
         int spaceAboveSnapshot = dp.overviewTaskThumbnailTopMarginPx;
         int totalThumbnailHeight = parentHeight - spaceAboveSnapshot;
         int dividerBar = (splitBoundsConfig.appsStackedVertically ?
@@ -535,23 +534,27 @@
                 (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
         FrameLayout.LayoutParams secondaryIconParams =
                 new FrameLayout.LayoutParams(primaryIconParams);
+        int dividerBar = (splitConfig.appsStackedVertically ?
+                splitConfig.visualDividerBounds.height() :
+                splitConfig.visualDividerBounds.width());
 
+        int primaryWidth = primarySnapshotBounds.width();
         if (deviceProfile.isLandscape) {
-            int primaryWidth = primarySnapshotBounds.width();
-            int secondaryWidth = secondarySnapshotBounds.width();
             primaryIconParams.gravity = TOP | START;
-            primaryIconView.setTranslationX((primaryWidth - taskIconHeight) / 2f );
+            primaryIconView.setTranslationX(primaryWidth - primaryIconView.getWidth());
+            primaryIconView.setTranslationY(0);
 
             secondaryIconParams.gravity = TOP | START;
-            secondaryIconView.setTranslationX(primaryWidth
-                    + ((secondaryWidth - taskIconHeight) / 2f));
-        } else {
-            primaryIconView.setTranslationX(0);
-            secondaryIconView.setTranslationX(0);
-            primaryIconView.setTranslationY(0);
+            secondaryIconView.setTranslationX(primaryWidth + dividerBar);
             secondaryIconView.setTranslationY(0);
-            secondaryIconParams.gravity = BOTTOM | CENTER_HORIZONTAL;
-            secondaryIconParams.bottomMargin = -(secondaryIconParams.topMargin + taskIconHeight);
+        } else {
+            primaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
+            primaryIconView.setTranslationX(-(primaryIconView.getWidth()) / 2f);
+            primaryIconView.setTranslationY(0);
+
+            secondaryIconParams.gravity = TOP | CENTER_HORIZONTAL;
+            secondaryIconView.setTranslationX(secondaryIconView.getWidth() / 2f);
+            secondaryIconView.setTranslationY(0);
         }
         primaryIconView.setLayoutParams(primaryIconParams);
         secondaryIconView.setLayoutParams(secondaryIconParams);
diff --git a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
index d5851c8..a0dde22 100644
--- a/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/SeascapePagedViewHandler.java
@@ -19,6 +19,7 @@
 import static android.view.Gravity.CENTER_VERTICAL;
 import static android.view.Gravity.END;
 import static android.view.Gravity.START;
+import static android.view.Gravity.TOP;
 
 import static com.android.launcher3.touch.SingleAxisSwipeDetector.HORIZONTAL;
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
@@ -36,7 +37,9 @@
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.R;
 import com.android.launcher3.Utilities;
+import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
+import com.android.launcher3.util.SplitConfigurationOptions.StagedSplitBounds;
 import com.android.launcher3.views.BaseDragLayer;
 
 import java.util.Collections;
@@ -127,6 +130,23 @@
         iconParams.topMargin = snapshotParams.topMargin / 2;
     }
 
+    @Override
+    public void setSplitIconParams(View primaryIconView, View secondaryIconView,
+            int taskIconHeight, Rect primarySnapshotBounds, Rect secondarySnapshotBounds,
+            boolean isRtl, DeviceProfile deviceProfile, StagedSplitBounds splitConfig) {
+        super.setSplitIconParams(primaryIconView, secondaryIconView, taskIconHeight,
+                primarySnapshotBounds, secondarySnapshotBounds, isRtl, deviceProfile, splitConfig);
+        FrameLayout.LayoutParams primaryIconParams =
+                (FrameLayout.LayoutParams) primaryIconView.getLayoutParams();
+        FrameLayout.LayoutParams secondaryIconParams =
+                (FrameLayout.LayoutParams) secondaryIconView.getLayoutParams();
+
+        primaryIconParams.gravity = (isRtl ? END : START) | TOP;
+        secondaryIconParams.gravity = (isRtl ? END : START) | TOP;
+        primaryIconView.setLayoutParams(primaryIconParams);
+        secondaryIconView.setLayoutParams(secondaryIconParams);
+    }
+
     /* ---------- The following are only used by TaskViewTouchHandler. ---------- */
 
     @Override
diff --git a/src/com/android/launcher3/util/SplitConfigurationOptions.java b/src/com/android/launcher3/util/SplitConfigurationOptions.java
index 0b083e3..6aef38f 100644
--- a/src/com/android/launcher3/util/SplitConfigurationOptions.java
+++ b/src/com/android/launcher3/util/SplitConfigurationOptions.java
@@ -85,6 +85,12 @@
         }
     }
 
+    /**
+     * NOTE: Engineers complained about too little ambiguity in the last survey, so there is a class
+     * with the same name/functionality in wm.shell.util (which launcher3 cannot be built against)
+     *
+     * If you make changes here, consider making the same changes there
+     */
     public static class StagedSplitBounds {
         public final Rect leftTopBounds;
         public final Rect rightBottomBounds;
@@ -100,10 +106,15 @@
          * the bounds were originally in
          */
         public final boolean appsStackedVertically;
+        public final int leftTopTaskId;
+        public final int rightBottomTaskId;
 
-        public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds) {
+        public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds, int leftTopTaskId,
+                int rightBottomTaskId) {
             this.leftTopBounds = leftTopBounds;
             this.rightBottomBounds = rightBottomBounds;
+            this.leftTopTaskId = leftTopTaskId;
+            this.rightBottomTaskId = rightBottomTaskId;
 
             if (rightBottomBounds.top > leftTopBounds.top) {
                 // vertical apps, horizontal divider
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 48305ee..9c8de1c 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -15,6 +15,7 @@
  */
 package com.android.launcher3.provider;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 
 import android.content.ContentValues;
@@ -87,6 +88,56 @@
         assertEquals(1, getCount(db, "select * from favorites where profileId = 33"));
     }
 
+    @Test
+    public void testRemoveScreenIdGaps_firstScreenEmpty() {
+        runRemoveScreenIdGapsTest(
+                new int[]{1, 2, 5, 6, 6, 7, 9, 9},
+                new int[]{1, 2, 3, 4, 4, 5, 6, 6});
+    }
+
+    @Test
+    public void testRemoveScreenIdGaps_firstScreenOccupied() {
+        runRemoveScreenIdGapsTest(
+                new int[]{0, 2, 5, 6, 6, 7, 9, 9},
+                new int[]{0, 1, 2, 3, 3, 4, 5, 5});
+    }
+
+    @Test
+    public void testRemoveScreenIdGaps_noGap() {
+        runRemoveScreenIdGapsTest(
+                new int[]{0, 1, 1, 2, 3, 3, 4, 5},
+                new int[]{0, 1, 1, 2, 3, 3, 4, 5});
+    }
+
+    private void runRemoveScreenIdGapsTest(int[] screenIds, int[] expectedScreenIds) {
+        SQLiteDatabase db = new MyDatabaseHelper(42).getWritableDatabase();
+        // Add some mock data
+        for (int i = 0; i < screenIds.length; i++) {
+            ContentValues values = new ContentValues();
+            values.put(Favorites._ID, i);
+            values.put(Favorites.SCREEN, screenIds[i]);
+            values.put(Favorites.CONTAINER, Favorites.CONTAINER_DESKTOP);
+            db.insert(Favorites.TABLE_NAME, null, values);
+        }
+        // Verify items are added
+        assertEquals(screenIds.length,
+                getCount(db, "select * from favorites where container = -100"));
+
+        new RestoreDbTask().removeScreenIdGaps(db);
+
+        // verify screenId gaps removed
+        int[] resultScreenIds = new int[screenIds.length];
+        try (Cursor c = db.rawQuery(
+                "select screen from favorites where container = -100 order by screen", null)) {
+            int i = 0;
+            while (c.moveToNext()) {
+                resultScreenIds[i++] = c.getInt(0);
+            }
+        }
+
+        assertArrayEquals(expectedScreenIds, resultScreenIds);
+    }
+
     private int getCount(SQLiteDatabase db, String sql) {
         try (Cursor c = db.rawQuery(sql, null)) {
             return c.getCount();
diff --git a/tests/src/com/android/launcher3/ui/WorkProfileTest.java b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
index aca5951..45d20e2 100644
--- a/tests/src/com/android/launcher3/ui/WorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/WorkProfileTest.java
@@ -59,6 +59,15 @@
         String[] tokens = output.split("\\s+");
         mProfileUserId = Integer.parseInt(tokens[tokens.length - 1]);
         mDevice.executeShellCommand("am start-user " + mProfileUserId);
+
+        mDevice.pressHome();
+        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
+        waitForStateTransitionToEnd("Launcher internal state didn't switch to Normal",
+                () -> NORMAL);
+        waitForResumed("Launcher internal state is still Background");
+        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
+        waitForStateTransitionToEnd("Launcher internal state didn't switch to All Apps",
+                () -> ALL_APPS);
     }
 
     @After
@@ -89,13 +98,6 @@
     @Test
     @ScreenRecord // b/202735477
     public void workTabExists() {
-        mDevice.pressHome();
-        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
-        waitForStateTransitionToEnd("Launcher internal state didn't switch to Normal",
-                () -> NORMAL);
-        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
-        waitForStateTransitionToEnd("Launcher internal state didn't switch to All Apps",
-                () -> ALL_APPS);
         waitForLauncherCondition("Personal tab is missing",
                 launcher -> launcher.getAppsView().isPersonalTabVisible(),
                 LauncherInstrumentation.WAIT_TIME_MS);
@@ -106,12 +108,6 @@
 
     @Test
     public void toggleWorks() {
-        mDevice.pressHome();
-        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
-        waitForState("Launcher internal state didn't switch to Normal", () -> NORMAL);
-        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
-        waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
-
         waitForWorkTabSetup();
 
         executeOnLauncher(launcher -> {
@@ -153,11 +149,6 @@
 
     @Test
     public void testEdu() {
-        mDevice.pressHome();
-        waitForLauncherCondition("Launcher didn't start", Objects::nonNull);
-        waitForState("Launcher internal state didn't switch to Normal", () -> NORMAL);
-        executeOnLauncher(launcher -> launcher.getStateManager().goToState(ALL_APPS));
-        waitForState("Launcher internal state didn't switch to All Apps", () -> ALL_APPS);
         waitForWorkTabSetup();
         executeOnLauncher(l -> {
             l.getSharedPrefs().edit().putInt(WorkAdapterProvider.KEY_WORK_EDU_STEP, 0).commit();
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index b290bb1..4eaecca 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -30,6 +30,7 @@
 
 import com.android.launcher3.testing.TestProtocol;
 
+import java.util.List;
 import java.util.regex.Pattern;
 
 /**
@@ -62,11 +63,12 @@
                      "want to switch from background to overview")) {
             verifyActiveContainer();
             goToOverviewUnchecked();
-            return mLauncher.isFallbackOverview() ?
-                    new BaseOverview(mLauncher) : new Overview(mLauncher);
+            return mLauncher.isFallbackOverview()
+                    ? new BaseOverview(mLauncher) : new Overview(mLauncher);
         }
     }
 
+
     protected boolean zeroButtonToOverviewGestureStartsInLauncher() {
         return mLauncher.isTablet();
     }
@@ -78,47 +80,56 @@
     protected void goToOverviewUnchecked() {
         switch (mLauncher.getNavigationModel()) {
             case ZERO_BUTTON: {
-                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
-                final int startY = getSwipeStartY();
-                final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).
-                        getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
-                final Point start = new Point(centerX, startY);
-                final Point end =
-                        new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
-
-                final long downTime = SystemClock.uptimeMillis();
-                final LauncherInstrumentation.GestureScope gestureScope =
-                        zeroButtonToOverviewGestureStartsInLauncher()
-                                ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
-                                : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
-
-                mLauncher.sendPointer(
-                        downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
-                Runnable swipeAndHold = () -> mLauncher.movePointer(
-                        downTime,
-                        downTime,
-                        ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION,
-                        start,
-                        end,
-                        gestureScope);
-                String swipeAndHoldAction = "swiping and holding";
-                Runnable up = () -> mLauncher.sendPointer(
-                        downTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP, end,
-                        gestureScope);
-                String upAction = "sending UP event";
+                sendDownPointerToEnterOverviewToLauncher();
+                String swipeAndHoldToEnterOverviewActionName =
+                        "swiping and holding to enter overview";
+                // If swiping from an app (e.g. Overview is in Background), we pause and hold on
+                // swipe up to make overview appear, or else swiping without holding would take
+                // us to the Home state. If swiping up from Home (e.g. Overview in Home or
+                // Workspace state where the below condition is true), there is no need to pause,
+                // and we will not test for an intermediate carousel as one will not exist.
                 if (zeroButtonToOverviewGestureStateTransitionWhileHolding()) {
-                    mLauncher.runToState(swipeAndHold, OVERVIEW_STATE_ORDINAL, swipeAndHoldAction);
-                    try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(upAction)) {
-                        up.run();
-                    }
+                    mLauncher.runToState(this::sendSwipeUpAndHoldToEnterOverviewGestureToLauncher,
+                            OVERVIEW_STATE_ORDINAL, swipeAndHoldToEnterOverviewActionName);
+                    sendUpPointerToEnterOverviewToLauncher();
                 } else {
+                    // If swiping up from an app to overview, pause on intermediate carousel
+                    // until snapshots are visible. No intermediate carousel when swiping from
+                    // Home. The task swiped up is not a snapshot but the TaskViewSimulator. If
+                    // only a single task exists, no snapshots will be available during swipe up.
                     mLauncher.executeAndWaitForLauncherEvent(
-                            swipeAndHold,
+                            this::sendSwipeUpAndHoldToEnterOverviewGestureToLauncher,
                             event -> TestProtocol.PAUSE_DETECTED_MESSAGE.equals(
-                                    event.getClassName()),
+                                    event.getClassName().toString()),
                             () -> "Pause wasn't detected",
-                            swipeAndHoldAction);
-                    mLauncher.runToState(up, OVERVIEW_STATE_ORDINAL, upAction);
+                            swipeAndHoldToEnterOverviewActionName);
+                    try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                            "paused on swipe up to overview")) {
+                        if (mLauncher.getRecentTasks().size() > 1) {
+                            // When swiping up to grid-overview for tablets, the swiped tab will be
+                            // in the middle of the screen (TaskViewSimulator, not a snapshot), and
+                            // all remaining snapshots will be to the left of that task. In
+                            // non-tablet overview, snapshots can be on either side of the swiped
+                            // task, but we still check that they become visible after swiping and
+                            // pausing.
+                            mLauncher.waitForOverviewObject("snapshot");
+                            if (mLauncher.isTablet()) {
+                                List<UiObject2> tasks = mLauncher.getDevice().findObjects(
+                                        mLauncher.getOverviewObjectSelector("snapshot"));
+                                final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+                                mLauncher.assertTrue(
+                                        "All tasks not to the left of the swiped task",
+                                        tasks.stream()
+                                                .allMatch(
+                                                        t -> t.getVisibleBounds().right < centerX));
+                            }
+
+                        }
+                        String upPointerToEnterOverviewActionName =
+                                "sending UP pointer to enter overview";
+                        mLauncher.runToState(this::sendUpPointerToEnterOverviewToLauncher,
+                                OVERVIEW_STATE_ORDINAL, upPointerToEnterOverviewActionName);
+                    }
                 }
                 break;
             }
@@ -167,6 +178,60 @@
     private void expectSwitchToOverviewEvents() {
     }
 
+    private void sendDownPointerToEnterOverviewToLauncher() {
+        final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+        final int startY = getSwipeStartY();
+        final Point start = new Point(centerX, startY);
+        final long downTime = SystemClock.uptimeMillis();
+        final LauncherInstrumentation.GestureScope gestureScope =
+                zeroButtonToOverviewGestureStartsInLauncher()
+                        ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
+                        : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
+
+        mLauncher.sendPointer(
+                downTime, downTime, MotionEvent.ACTION_DOWN, start, gestureScope);
+    }
+
+    private void sendSwipeUpAndHoldToEnterOverviewGestureToLauncher() {
+        final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+        final int startY = getSwipeStartY();
+        final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        final Point start = new Point(centerX, startY);
+        final Point end =
+                new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
+        final long downTime = SystemClock.uptimeMillis();
+        final LauncherInstrumentation.GestureScope gestureScope =
+                zeroButtonToOverviewGestureStartsInLauncher()
+                        ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
+                        : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
+
+        mLauncher.movePointer(
+                downTime,
+                downTime,
+                ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION,
+                start,
+                end,
+                gestureScope);
+    }
+
+    private void sendUpPointerToEnterOverviewToLauncher() {
+        final int centerX = mLauncher.getDevice().getDisplayWidth() / 2;
+        final int startY = getSwipeStartY();
+        final int swipeHeight = mLauncher.getTestInfo(getSwipeHeightRequestName()).getInt(
+                TestProtocol.TEST_INFO_RESPONSE_FIELD);
+        final Point end =
+                new Point(centerX, startY - swipeHeight - mLauncher.getTouchSlop());
+        final long downTime = SystemClock.uptimeMillis();
+        final LauncherInstrumentation.GestureScope gestureScope =
+                zeroButtonToOverviewGestureStartsInLauncher()
+                        ? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
+                        : LauncherInstrumentation.GestureScope.OUTSIDE_WITH_PILFER;
+
+        mLauncher.sendPointer(downTime, SystemClock.uptimeMillis(),
+                MotionEvent.ACTION_UP, end, gestureScope);
+    }
+
     @NonNull
     public Background quickSwitchToPreviousApp() {
         boolean toRight = true;
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 5a807ca..f3e3ec5 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -1156,6 +1156,11 @@
     }
 
     @NonNull
+    UiObject2 waitForOverviewObject(String resName) {
+        return waitForObjectBySelector(getOverviewObjectSelector(resName));
+    }
+
+    @NonNull
     UiObject2 waitForLauncherObject(String resName) {
         return waitForObjectBySelector(getLauncherObjectSelector(resName));
     }