Merge "Avoid seeing ClearAllButton for overview grid's snap position" into sc-v2-dev
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index f54070e..5fc969d 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -43,7 +43,6 @@
     <dimen name="overview_grid_side_margin">54dp</dimen>
     <dimen name="overview_grid_row_spacing">42dp</dimen>
     <dimen name="overview_grid_focus_vertical_margin">40dp</dimen>
-    <dimen name="split_placeholder_size">110dp</dimen>
 
     <!-- These speeds are in dp/s -->
     <dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
diff --git a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
index 1386ac0..f82fbcc 100644
--- a/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
+++ b/quickstep/robolectric_tests/src/com/android/launcher3/model/WidgetsPredicationUpdateTaskTest.java
@@ -34,7 +34,6 @@
 import android.os.Process;
 import android.os.UserHandle;
 
-import com.android.launcher3.InvariantDeviceProfile;
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.icons.ComponentWithLabel;
@@ -42,7 +41,6 @@
 import com.android.launcher3.model.BgDataModel.FixedContainerItems;
 import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
 import com.android.launcher3.shadows.ShadowDeviceFlag;
-import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LauncherModelHelper;
 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
@@ -76,7 +74,6 @@
     private Context mContext;
     private LauncherModelHelper mModelHelper;
     private UserHandle mUserHandle;
-    private InvariantDeviceProfile mTestProfile;
 
     @Mock
     private IconCache mIconCache;
@@ -92,7 +89,6 @@
         mContext = RuntimeEnvironment.application;
         mModelHelper = new LauncherModelHelper();
         mUserHandle = Process.myUserHandle();
-        mTestProfile = new InvariantDeviceProfile();
         // 2 widgets, app4/provider1 & app5/provider1, have already been added to the workspace.
         mModelHelper.initializeData("/widgets_predication_update_task_data.txt");
 
@@ -226,10 +222,5 @@
         public void bindExtraContainerItems(FixedContainerItems item) {
             mRecommendedWidgets = item;
         }
-
-        @Override
-        public IntSet getPagesToBindSynchronously() {
-            return IntSet.wrap(0);
-        }
     }
 }
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index ecd38b4..5250d18 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -271,12 +271,10 @@
 
         SysUINavigationMode.INSTANCE.get(this).updateMode();
         mActionsView = findViewById(R.id.overview_actions_view);
-        mSplitPlaceholderView = findViewById(R.id.split_placeholder);
         RecentsView overviewPanel = (RecentsView) getOverviewPanel();
-        mSplitPlaceholderView.init(
-                new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this))
-        );
-        overviewPanel.init(mActionsView, mSplitPlaceholderView);
+        SplitSelectStateController controller =
+                new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
+        overviewPanel.init(mActionsView, controller);
         mActionsView.setDp(getDeviceProfile());
         mActionsView.updateVerticalMargin(SysUINavigationMode.getMode(this));
 
diff --git a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
index 6cad3dd..1f744e1 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/RecentsViewStateController.java
@@ -18,13 +18,11 @@
 import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
 import static com.android.launcher3.LauncherState.OVERVIEW_ACTIONS;
 import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
-import static com.android.launcher3.LauncherState.SPLIT_PLACHOLDER_VIEW;
 import static com.android.launcher3.anim.Interpolators.LINEAR;
 import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_ACTIONS_FADE;
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
 import static com.android.quickstep.views.RecentsView.TASK_MODALNESS;
-import static com.android.quickstep.views.SplitPlaceholderView.ALPHA_FLOAT;
 import static com.android.quickstep.views.TaskView.FLAG_UPDATE_ALL;
 
 import android.annotation.TargetApi;
@@ -110,11 +108,6 @@
         propertySetter.setFloat(mLauncher.getActionsView().getVisibilityAlpha(),
                 MultiValueAlpha.VALUE, overviewButtonAlpha, config.getInterpolator(
                         ANIM_OVERVIEW_ACTIONS_FADE, LINEAR));
-
-        float splitPlaceholderAlpha = state.areElementsVisible(mLauncher, SPLIT_PLACHOLDER_VIEW) ?
-                0.85f : 0;
-        propertySetter.setFloat(mRecentsView.getSplitPlaceholder(), ALPHA_FLOAT,
-                splitPlaceholderAlpha, LINEAR);
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
index 6968494..1882a0c 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/SplitScreenSelectState.java
@@ -43,7 +43,7 @@
     @Override
     public float getSplitSelectTranslation(Launcher launcher) {
         RecentsView recentsView = launcher.getOverviewPanel();
-        int splitPosition = recentsView.getSplitPlaceholder().getSplitController()
+        int splitPosition = recentsView.getSplitPlaceholder()
                 .getActiveSplitPositionOption().mStagePosition;
         if (!recentsView.shouldShiftThumbnailsForSplitSelect(splitPosition)) {
             return 0f;
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
index 051485a..0603ba5 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/TaskViewTouchController.java
@@ -227,7 +227,8 @@
         if (goingUp) {
             currentInterpolator = Interpolators.LINEAR;
             pa = mRecentsView.createTaskDismissAnimation(mTaskBeingDragged,
-                    true /* animateTaskView */, true /* removeTask */, maxDuration);
+                    true /* animateTaskView */, true /* removeTask */, maxDuration,
+                    false /* dismissingForSplitSelection*/);
 
             mEndDisplacement = -secondaryTaskDimension;
         } else {
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 076f28a..88fcce1 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1254,12 +1254,23 @@
         }
     }
 
+    private int calculateWindowRotation(RemoteAnimationTargetCompat runningTaskTarget,
+            RecentsOrientedState orientationState) {
+        if (runningTaskTarget.rotationChange != 0
+                && TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+            return Math.abs(runningTaskTarget.rotationChange) == ROTATION_90
+                    ? ROTATION_270 : ROTATION_90;
+        } else {
+            return orientationState.getDisplayRotation();
+        }
+    }
+
     private SwipePipToHomeAnimator createWindowAnimationToPip(HomeAnimationFactory homeAnimFactory,
             RemoteAnimationTargetCompat runningTaskTarget, float startProgress) {
         // Directly animate the app to PiP (picture-in-picture) mode
         final ActivityManager.RunningTaskInfo taskInfo = mGestureState.getRunningTask();
         final RecentsOrientedState orientationState = mTaskViewSimulator.getOrientationState();
-        final int windowRotation = orientationState.getDisplayRotation();
+        final int windowRotation = calculateWindowRotation(runningTaskTarget, orientationState);
         final int homeRotation = orientationState.getRecentsActivityRotation();
 
         final Matrix homeToWindowPositionMap = new Matrix();
diff --git a/quickstep/src/com/android/quickstep/RecentsActivity.java b/quickstep/src/com/android/quickstep/RecentsActivity.java
index 9dfcd12..95be45a 100644
--- a/quickstep/src/com/android/quickstep/RecentsActivity.java
+++ b/quickstep/src/com/android/quickstep/RecentsActivity.java
@@ -122,13 +122,10 @@
         mActionsView = findViewById(R.id.overview_actions_view);
         SYSUI_PROGRESS.set(getRootView().getSysUiScrim(), 0f);
 
-        SplitPlaceholderView splitPlaceholderView = findViewById(R.id.split_placeholder);
-        splitPlaceholderView.init(
-                new SplitSelectStateController(mUiHandler, SystemUiProxy.INSTANCE.get(this))
-        );
-
+        SplitSelectStateController controller =
+                new SplitSelectStateController(mHandler, SystemUiProxy.INSTANCE.get(this));
         mDragLayer.recreateControllers();
-        mFallbackRecentsView.init(mActionsView, splitPlaceholderView);
+        mFallbackRecentsView.init(mActionsView, controller);
     }
 
     @Override
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index b038c7b..e55f1d1 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -583,8 +583,7 @@
                 } else {
                     mUncheckedConsumer = InputConsumer.NO_OP;
                 }
-            } else if (mDeviceState.canTriggerOneHandedAction(event)
-                    && !mDeviceState.isOneHandedModeActive()) {
+            } else if (mDeviceState.canTriggerOneHandedAction(event)) {
                 // Consume gesture event for triggering one handed feature.
                 mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
                         InputConsumer.NO_OP, mInputMonitorCompat);
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 3bf79f1..de79372 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -37,6 +37,7 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.RecentsActivity;
 import com.android.quickstep.util.TaskViewSimulator;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.quickstep.views.OverviewActionsView;
 import com.android.quickstep.views.RecentsView;
 import com.android.quickstep.views.SplitPlaceholderView;
@@ -62,8 +63,8 @@
     }
 
     @Override
-    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
-        super.init(actionsView, splitPlaceholderView);
+    public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
+        super.init(actionsView, splitController);
         setOverviewStateEnabled(true);
         setOverlayEnabled(true);
     }
@@ -96,7 +97,8 @@
         if (mHomeTaskInfo != null && endTarget == RECENTS && animatorSet != null) {
             TaskView tv = getTaskView(mHomeTaskInfo.taskId);
             if (tv != null) {
-                PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150);
+                PendingAnimation pa = createTaskDismissAnimation(tv, true, false, 150,
+                        false /* dismissingForSplitSelection*/);
                 pa.addEndListener(e -> setCurrentTask(-1));
                 AnimatorPlaybackController controller = pa.createPlaybackController();
                 controller.dispatchOnStart();
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index 2351a4e..16c925a 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -56,6 +56,7 @@
 
     private final SystemUiProxy mSystemUiProxy;
     private TaskView mInitialTaskView;
+    private TaskView mSecondTaskView;
     private SplitPositionOption mInitialPosition;
     private Rect mInitialBounds;
     private final Handler mHandler;
@@ -79,23 +80,19 @@
      * To be called after second task selected
      */
     public void setSecondTaskId(TaskView taskView) {
-        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
-            // Assume initial task is for top/left part of screen
-            final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
-                    ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
-                    : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
+        mSecondTaskView = taskView;
+        // Assume initial task is for top/left part of screen
 
+        final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
+                ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
+                : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
+        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
             RemoteSplitLaunchTransitionRunner animationRunner =
                     new RemoteSplitLaunchTransitionRunner(mInitialTaskView, taskView);
             mSystemUiProxy.startTasks(taskIds[0], null /* mainOptions */, taskIds[1],
                     null /* sideOptions */, STAGE_POSITION_BOTTOM_OR_RIGHT,
                     new RemoteTransitionCompat(animationRunner, MAIN_EXECUTOR));
         } else {
-            // Assume initial task is for top/left part of screen
-            final int[] taskIds = mInitialPosition.mStagePosition == STAGE_POSITION_TOP_OR_LEFT
-                    ? new int[]{mInitialTaskView.getTask().key.id, taskView.getTask().key.id}
-                    : new int[]{taskView.getTask().key.id, mInitialTaskView.getTask().key.id};
-
             RemoteSplitLaunchAnimationRunner animationRunner =
                     new RemoteSplitLaunchAnimationRunner(mInitialTaskView, taskView);
             final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
@@ -191,12 +188,17 @@
      */
     public void resetState() {
         mInitialTaskView = null;
+        mSecondTaskView = null;
         mInitialPosition = null;
         mInitialBounds = null;
     }
 
+    /**
+     * @return {@code true} if first task has been selected and waiting for the second task to be
+     *         chosen
+     */
     public boolean isSplitSelectActive() {
-        return mInitialTaskView != null;
+        return mInitialTaskView != null && mSecondTaskView == null;
     }
 
     public Rect getInitialBounds() {
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index c0f5c14..d4191fe 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -40,6 +40,7 @@
 import com.android.launcher3.anim.AnimationSuccessListener;
 import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.util.Themes;
+import com.android.quickstep.TaskAnimationManager;
 import com.android.systemui.shared.pip.PipSurfaceTransactionHelper;
 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 
@@ -278,19 +279,36 @@
 
     private RotatedPosition getRotatedPosition(float progress) {
         final float degree, positionX, positionY;
-        if (mFromRotation == Surface.ROTATION_90) {
-            degree = -90 * progress;
-            positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
-                    + mStartBounds.left;
-            positionY = progress * (mDestinationBoundsTransformed.bottom - mStartBounds.top)
-                    + mStartBounds.top;
+        if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
+            if (mFromRotation == Surface.ROTATION_90) {
+                degree = -90 * (1 - progress);
+                positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
+                        + mStartBounds.left;
+                positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
+                        + mStartBounds.top + mStartBounds.bottom * (1 - progress);
+            } else {
+                degree = 90 * (1 - progress);
+                positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
+                        + mStartBounds.left + mStartBounds.right * (1 - progress);
+                positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
+                        + mStartBounds.top;
+            }
         } else {
-            degree = 90 * progress;
-            positionX = progress * (mDestinationBoundsTransformed.right - mStartBounds.left)
-                    + mStartBounds.left;
-            positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
-                    + mStartBounds.top;
+            if (mFromRotation == Surface.ROTATION_90) {
+                degree = -90 * progress;
+                positionX = progress * (mDestinationBoundsTransformed.left - mStartBounds.left)
+                        + mStartBounds.left;
+                positionY = progress * (mDestinationBoundsTransformed.bottom - mStartBounds.top)
+                        + mStartBounds.top;
+            } else {
+                degree = 90 * progress;
+                positionX = progress * (mDestinationBoundsTransformed.right - mStartBounds.left)
+                        + mStartBounds.left;
+                positionY = progress * (mDestinationBoundsTransformed.top - mStartBounds.top)
+                        + mStartBounds.top;
+            }
         }
+
         return new RotatedPosition(degree, positionX, positionY);
     }
 
diff --git a/quickstep/src/com/android/quickstep/views/FloatingTaskView.java b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
new file mode 100644
index 0000000..a1befc5
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/views/FloatingTaskView.java
@@ -0,0 +1,241 @@
+package com.android.quickstep.views;
+
+import static com.android.launcher3.anim.Interpolators.ACCEL;
+import static com.android.launcher3.anim.Interpolators.DEACCEL_3;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.android.launcher3.InsettableFrameLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAnimUtils;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.PendingAnimation;
+import com.android.launcher3.statemanager.StatefulActivity;
+import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.quickstep.util.MultiValueUpdateListener;
+
+/**
+ * Create an instance via {@link #getFloatingTaskView(StatefulActivity, TaskView, RectF)} to
+ * which will have the thumbnail from the provided existing TaskView overlaying the taskview itself.
+ *
+ * Can then animate the taskview using
+ * {@link #addAnimation(PendingAnimation, RectF, Rect, View, boolean)}
+ * giving a starting and ending bounds. Currently this is set to use the split placeholder view,
+ * but it could be generified.
+ *
+ * TODO: Figure out how to copy thumbnail data from existing TaskView to this view.
+ */
+public class FloatingTaskView extends FrameLayout {
+
+    private SplitPlaceholderView mSplitPlaceholderView;
+    private RectF mStartingPosition;
+    private final Launcher mLauncher;
+    private final boolean mIsRtl;
+    private final Rect mOutline = new Rect();
+    private PagedOrientationHandler mOrientationHandler;
+    private ImageView mImageView;
+
+    public FloatingTaskView(Context context) {
+        this(context, null);
+    }
+
+    public FloatingTaskView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public FloatingTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        mLauncher = Launcher.getLauncher(context);
+        mIsRtl = Utilities.isRtl(getResources());
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mImageView = findViewById(R.id.thumbnail);
+        mImageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+        mImageView.setLayerType(LAYER_TYPE_HARDWARE, null);
+        mSplitPlaceholderView = findViewById(R.id.split_placeholder);
+        mSplitPlaceholderView.setAlpha(0);
+        mSplitPlaceholderView.setBackgroundColor(getResources().getColor(android.R.color.white));
+    }
+
+    public static FloatingTaskView getFloatingTaskView(StatefulActivity launcher,
+            TaskView originalView, RectF positionOut) {
+        final BaseDragLayer dragLayer = launcher.getDragLayer();
+        ViewGroup parent = (ViewGroup) dragLayer.getParent();
+        final FloatingTaskView floatingView = (FloatingTaskView) launcher.getLayoutInflater()
+                .inflate(R.layout.floating_split_select_view, parent, false);
+
+        floatingView.mStartingPosition = positionOut;
+        floatingView.updateInitialPositionForView(originalView);
+        final InsettableFrameLayout.LayoutParams lp =
+                (InsettableFrameLayout.LayoutParams) floatingView.getLayoutParams();
+
+        floatingView.mSplitPlaceholderView.setLayoutParams(
+                new FrameLayout.LayoutParams(lp.width, lp.height));
+        positionOut.round(floatingView.mOutline);
+        floatingView.setPivotX(0);
+        floatingView.setPivotY(0);
+
+        // Copy bounds of exiting thumbnail into ImageView
+        TaskThumbnailView thumbnail = originalView.getThumbnail();
+        floatingView.mImageView.setImageBitmap(thumbnail.getThumbnail());
+        floatingView.mImageView.setVisibility(VISIBLE);
+
+        floatingView.mOrientationHandler =
+                originalView.getRecentsView().getPagedOrientationHandler();
+        floatingView.mSplitPlaceholderView.setIcon(originalView.getIconView());
+        floatingView.mSplitPlaceholderView.getIcon()
+                .setRotation(floatingView.mOrientationHandler.getDegreesRotated());
+        parent.addView(floatingView);
+        return floatingView;
+    }
+
+    public void updateInitialPositionForView(TaskView originalView) {
+        View thumbnail = originalView.getThumbnail();
+        Rect viewBounds = new Rect(0, 0, thumbnail.getWidth(), thumbnail.getHeight());
+        Utilities.getBoundsForViewInDragLayer(mLauncher.getDragLayer(), thumbnail, viewBounds,
+                true /* ignoreTransform */, null /* recycle */,
+                mStartingPosition);
+        mStartingPosition.offset(originalView.getTranslationX(), originalView.getTranslationY());
+        final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams(
+                Math.round(mStartingPosition.width()),
+                Math.round(mStartingPosition.height()));
+        initPosition(mStartingPosition, lp);
+        setLayoutParams(lp);
+    }
+
+    // TODO(194414938) set correct corner radii
+    public void update(RectF position, float progress, float windowRadius) {
+        MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+
+        float dX = mIsRtl
+                ? position.left - (lp.getMarginStart() - lp.width)
+                : position.left - lp.getMarginStart();
+        float dY = position.top - lp.topMargin;
+
+        setTranslationX(dX);
+        setTranslationY(dY);
+
+        float scaleX = position.width() / lp.width;
+        float scaleY = position.height() / lp.height;
+        setScaleX(scaleX);
+        setScaleY(scaleY);
+        float childScaleX = 1f / scaleX;
+        float childScaleY = 1f / scaleY;
+
+        invalidate();
+        // TODO(194414938) seems like this scale value could be fine tuned, some stretchiness
+        mImageView.setScaleX(1f / scaleX + scaleX * progress);
+        mImageView.setScaleY(1f / scaleY + scaleY * progress);
+        mOrientationHandler.setPrimaryScale(mSplitPlaceholderView.getIcon(), childScaleX);
+        mOrientationHandler.setSecondaryScale(mSplitPlaceholderView.getIcon(), childScaleY);
+    }
+
+    protected void initPosition(RectF pos, InsettableFrameLayout.LayoutParams lp) {
+        mStartingPosition.set(pos);
+        lp.ignoreInsets = true;
+        // Position the floating view exactly on top of the original
+        lp.topMargin = Math.round(pos.top);
+        if (mIsRtl) {
+            lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right));
+        } else {
+            lp.setMarginStart(Math.round(pos.left));
+        }
+        // Set the properties here already to make sure they are available when running the first
+        // animation frame.
+        int left = mIsRtl
+                ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width
+                : lp.leftMargin;
+        layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height);
+    }
+
+    public void addAnimation(PendingAnimation animation, RectF startingBounds, Rect endBounds,
+            View viewToCover, boolean fadeWithThumbnail) {
+        final BaseDragLayer dragLayer = mLauncher.getDragLayer();
+        int[] dragLayerBounds = new int[2];
+        dragLayer.getLocationOnScreen(dragLayerBounds);
+        SplitOverlayProperties prop = new SplitOverlayProperties(endBounds,
+                startingBounds, viewToCover, dragLayerBounds[0],
+                dragLayerBounds[1]);
+
+        ValueAnimator transitionAnimator = ValueAnimator.ofFloat(0, 1);
+        animation.add(transitionAnimator);
+        long animDuration = animation.getDuration();
+        Rect crop = new Rect();
+        RectF floatingTaskViewBounds = new RectF();
+        final float initialWindowRadius = supportsRoundedCornersOnWindows(getResources())
+                ? Math.max(crop.width(), crop.height()) / 2f
+                : 0f;
+
+        if (fadeWithThumbnail) {
+            animation.addFloat(mSplitPlaceholderView, SplitPlaceholderView.ALPHA_FLOAT,
+                    0, 1, ACCEL);
+            animation.addFloat(mImageView, LauncherAnimUtils.VIEW_ALPHA,
+                    1, 0, DEACCEL_3);
+        }
+
+        MultiValueUpdateListener listener = new MultiValueUpdateListener() {
+            final FloatProp mWindowRadius = new FloatProp(initialWindowRadius,
+                    initialWindowRadius, 0, animDuration, LINEAR);
+            final FloatProp mDx = new FloatProp(0, prop.dX, 0, animDuration, LINEAR);
+            final FloatProp mDy = new FloatProp(0, prop.dY, 0, animDuration, LINEAR);
+            final FloatProp mTaskViewScaleX = new FloatProp(prop.initialTaskViewScaleX,
+                    prop.finalTaskViewScaleX, 0, animDuration, LINEAR);
+            final FloatProp mTaskViewScaleY = new FloatProp(prop.initialTaskViewScaleY,
+                    prop.finalTaskViewScaleY, 0, animDuration, LINEAR);
+            @Override
+            public void onUpdate(float percent, boolean initOnly) {
+                // Calculate the icon position.
+                floatingTaskViewBounds.set(startingBounds);
+                floatingTaskViewBounds.offset(mDx.value, mDy.value);
+                Utilities.scaleRectFAboutCenter(floatingTaskViewBounds, mTaskViewScaleX.value,
+                        mTaskViewScaleY.value);
+
+                update(floatingTaskViewBounds, percent, mWindowRadius.value * 1);
+            }
+        };
+        transitionAnimator.addUpdateListener(listener);
+    }
+
+    private static class SplitOverlayProperties {
+
+        private final float initialTaskViewScaleX;
+        private final float initialTaskViewScaleY;
+        private final float finalTaskViewScaleX;
+        private final float finalTaskViewScaleY;
+        private final float dX;
+        private final float dY;
+
+        SplitOverlayProperties(Rect endBounds, RectF startTaskViewBounds, View view,
+                int dragLayerLeft, int dragLayerTop) {
+            float maxScaleX = endBounds.width() / startTaskViewBounds.width();
+            float maxScaleY = endBounds.height() / startTaskViewBounds.height();
+
+            initialTaskViewScaleX = view.getScaleX();
+            initialTaskViewScaleY = view.getScaleY();
+            finalTaskViewScaleX = maxScaleX;
+            finalTaskViewScaleY = maxScaleY;
+
+            // Animate the app icon to the center of the window bounds in screen coordinates.
+            float centerX = endBounds.centerX() - dragLayerLeft;
+            float centerY = endBounds.centerY() - dragLayerTop;
+
+            dX = centerX - startTaskViewBounds.centerX();
+            dY = centerY - startTaskViewBounds.centerY();
+        }
+    }
+}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 65956d5..152c2bd 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -38,6 +38,7 @@
 import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
 import com.android.launcher3.util.SplitConfigurationOptions;
 import com.android.quickstep.LauncherActivityInterface;
+import com.android.quickstep.util.SplitSelectStateController;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.plugins.RecentsExtraCard;
 
@@ -81,7 +82,8 @@
     }
 
     @Override
-    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+    public void init(OverviewActionsView actionsView,
+            SplitSelectStateController splitPlaceholderView) {
         super.init(actionsView, splitPlaceholderView);
         setContentAlpha(0);
     }
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index c19772a..6ea4a0d 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -545,15 +545,18 @@
     /**
      * Placeholder view indicating where the first split screen selected app will be placed
      */
-    private SplitPlaceholderView mSplitPlaceholderView;
+    private SplitSelectStateController mSplitSelectStateController;
     /**
      * The first task that split screen selection was initiated with. When split select state is
      * initialized, we create a
-     * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long)} for this TaskView but
-     * don't actually remove the task since the user might back out. As such, we also ensure this
-     * View doesn't go back into the {@link #mTaskViewPool}, see {@link #onViewRemoved(View)}
+     * {@link #createTaskDismissAnimation(TaskView, boolean, boolean, long, boolean)} for this
+     * TaskView but don't actually remove the task since the user might back out. As such, we also
+     * ensure this View doesn't go back into the {@link #mTaskViewPool},
+     * see {@link #onViewRemoved(View)}
      */
     private TaskView mSplitHiddenTaskView;
+    private TaskView mSecondSplitHiddenTaskView;
+
     /**
      * Keeps track of the index of the TaskView that split screen was initialized with so we know
      * where to insert it back into list of taskViews in case user backs out of entering split
@@ -563,6 +566,9 @@
      * removed from recentsView
      */
     private int mSplitHiddenTaskViewIndex;
+    private FloatingTaskView mFirstFloatingTaskView;
+    private FloatingTaskView mSecondFloatingTaskView;
+
     /**
      * The task to be removed and immediately re-added. Should not be added to task pool.
      */
@@ -776,18 +782,18 @@
         updateTaskStackListenerState();
     }
 
-    public void init(OverviewActionsView actionsView, SplitPlaceholderView splitPlaceholderView) {
+    public void init(OverviewActionsView actionsView, SplitSelectStateController splitController) {
         mActionsView = actionsView;
         mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
-        mSplitPlaceholderView = splitPlaceholderView;
+        mSplitSelectStateController = splitController;
     }
 
-    public SplitPlaceholderView getSplitPlaceholder() {
-        return mSplitPlaceholderView;
+    public SplitSelectStateController getSplitPlaceholder() {
+        return mSplitSelectStateController;
     }
 
     public boolean isSplitSelectionActive() {
-        return mSplitPlaceholderView.getSplitController().isSplitSelectActive();
+        return mSplitSelectStateController.isSplitSelectActive();
     }
 
     @Override
@@ -984,7 +990,7 @@
             // Reset the running task when leaving overview since it can still have a reference to
             // its thumbnail
             mTmpRunningTask = null;
-            if (mSplitPlaceholderView.getSplitController().isSplitSelectActive()) {
+            if (mSplitSelectStateController.isSplitSelectActive()) {
                 cancelSplitSelect(false);
             }
         }
@@ -2286,50 +2292,23 @@
             PendingAnimation anim) {
         // Use setFloat instead of setViewAlpha as we want to keep the view visible even when it's
         // alpha is set to 0 so that it can be recycled in the view pool properly
-        anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && taskView.isRunningTask()) {
             anim.setFloat(mLiveTileParams, TransformParams.TARGET_ALPHA, 0,
                     clampToProgress(ACCEL, 0, 0.5f));
         }
-        SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
+        anim.setFloat(taskView, VIEW_ALPHA, 0, clampToProgress(ACCEL, 0, 0.5f));
+        FloatProperty<TaskView> secondaryViewTranslate =
+                taskView.getSecondaryDissmissTranslationProperty();
+        int secondaryTaskDimension = mOrientationHandler.getSecondaryDimension(taskView);
+        int verticalFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
 
         ResourceProvider rp = DynamicResource.provider(mActivity);
         SpringProperty sp = new SpringProperty(SpringProperty.FLAG_CAN_SPRING_ON_START)
                 .setDampingRatio(rp.getFloat(R.dimen.dismiss_task_trans_y_damping_ratio))
                 .setStiffness(rp.getFloat(R.dimen.dismiss_task_trans_y_stiffness));
-        FloatProperty<TaskView> dismissingTaskViewTranslate =
-                taskView.getSecondaryDissmissTranslationProperty();
-        // TODO(b/186800707) translate entire grid size distance
-        int translateDistance = mOrientationHandler.getSecondaryDimension(taskView);
-        int positiveNegativeFactor = mOrientationHandler.getSecondaryTranslationDirectionFactor();
-        if (splitController.isSplitSelectActive()) {
-            // Have the task translate towards whatever side was just pinned
-            int dir = mOrientationHandler.getSplitTaskViewDismissDirection(splitController
-                    .getActiveSplitPositionOption(), mActivity.getDeviceProfile());
-            switch (dir) {
-                case PagedOrientationHandler.SPLIT_TRANSLATE_SECONDARY_NEGATIVE:
-                    dismissingTaskViewTranslate = taskView
-                            .getSecondaryDissmissTranslationProperty();
-                    positiveNegativeFactor = -1;
-                    break;
 
-                case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_POSITIVE:
-                    dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty();
-                    positiveNegativeFactor = 1;
-                    break;
-
-                case PagedOrientationHandler.SPLIT_TRANSLATE_PRIMARY_NEGATIVE:
-                    dismissingTaskViewTranslate = taskView.getPrimaryDismissTranslationProperty();
-                    positiveNegativeFactor = -1;
-                    break;
-                default:
-                    throw new IllegalStateException("Invalid split task translation: " + dir);
-            }
-        }
-        // Double translation distance so dismissal drag is the full height, as we only animate
-        // the drag for the first half of the progress.
-        anim.add(ObjectAnimator.ofFloat(taskView, dismissingTaskViewTranslate,
-                positiveNegativeFactor * translateDistance * 2).setDuration(duration), LINEAR, sp);
+        anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
+                verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
 
         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && mEnableDrawingLiveTile
                 && taskView.isRunningTask()) {
@@ -2344,15 +2323,37 @@
     }
 
     /**
+     * Places an {@link FloatingTaskView} on top of the thumbnail for {@link #mSplitHiddenTaskView}
+     * and then animates it into the split position that was desired
+     */
+    private void createInitialSplitSelectAnimation(PendingAnimation anim) {
+        float placeholderHeight = getResources().getDimension(R.dimen.split_placeholder_size);
+        mOrientationHandler.getInitialSplitPlaceholderBounds((int) placeholderHeight,
+                        mActivity.getDeviceProfile(),
+                mSplitSelectStateController.getActiveSplitPositionOption(), mTempRect);
+
+        RectF startingTaskRect = new RectF();
+        mSplitHiddenTaskView.setVisibility(INVISIBLE);
+        mFirstFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+                mSplitHiddenTaskView, startingTaskRect);
+        mFirstFloatingTaskView.setAlpha(1);
+        mFirstFloatingTaskView.addAnimation(anim, startingTaskRect,
+                mTempRect, mSplitHiddenTaskView, true /*fadeWithThumbnail*/);
+    }
+
+    /**
      * Creates a {@link PendingAnimation} for dismissing the specified {@link TaskView}.
      * @param dismissedTaskView the {@link TaskView} to be dismissed
      * @param animateTaskView whether the {@link TaskView} to be dismissed should be animated
      * @param shouldRemoveTask whether the associated {@link Task} should be removed from
      *                         ActivityManager after dismissal
      * @param duration duration of the animation
+     * @param dismissingForSplitSelection task dismiss animation is used for entering split
+     *                                    selection state from app icon
      */
     public PendingAnimation createTaskDismissAnimation(TaskView dismissedTaskView,
-            boolean animateTaskView, boolean shouldRemoveTask, long duration) {
+            boolean animateTaskView, boolean shouldRemoveTask, long duration,
+            boolean dismissingForSplitSelection) {
         if (mPendingAnimation != null) {
             mPendingAnimation.createPlaybackController().dispatchOnCancel().dispatchOnEnd();
         }
@@ -2419,7 +2420,11 @@
             View child = getChildAt(i);
             if (child == dismissedTaskView) {
                 if (animateTaskView) {
-                    addDismissedTaskAnimations(dismissedTaskView, duration, anim);
+                    if (dismissingForSplitSelection) {
+                        createInitialSplitSelectAnimation(anim);
+                    } else {
+                        addDismissedTaskAnimations(dismissedTaskView, duration, anim);
+                    }
                 }
             } else if (!showAsGrid) {
                 // Compute scroll offsets from task dismissal for animation.
@@ -2602,7 +2607,7 @@
 
     /**
      * @return {@code true} if one of the task thumbnails would intersect/overlap with the
-     *         {@link #mSplitPlaceholderView}
+     *         {@link #mFirstFloatingTaskView}
      */
     public boolean shouldShiftThumbnailsForSplitSelect(@SplitConfigurationOptions.StagePosition
             int stagePosition) {
@@ -2705,7 +2710,7 @@
 
     public void dismissTask(TaskView taskView, boolean animateTaskView, boolean removeTask) {
         runDismissAnimation(createTaskDismissAnimation(taskView, animateTaskView, removeTask,
-                DISMISS_TASK_DURATION));
+                DISMISS_TASK_DURATION, false /* dismissingForSplitSelection*/));
     }
 
     @SuppressWarnings("unused")
@@ -3107,8 +3112,11 @@
     protected void setTaskViewsSecondarySplitTranslation(float translation) {
         mTaskViewsSecondarySplitTranslation = translation;
         for (int i = 0; i < getTaskViewCount(); i++) {
-            TaskView task = getTaskViewAt(i);
-            task.getSecondarySplitTranslationProperty().set(task, translation);
+            TaskView taskView = getTaskViewAt(i);
+            if (taskView == mSplitHiddenTaskView) {
+                continue;
+            }
+            taskView.getSecondarySplitTranslationProperty().set(taskView, translation);
         }
     }
 
@@ -3124,15 +3132,11 @@
 
     public void initiateSplitSelect(TaskView taskView, SplitPositionOption splitPositionOption) {
         mSplitHiddenTaskView = taskView;
-        SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
         Rect initialBounds = new Rect(taskView.getLeft(), taskView.getTop(), taskView.getRight(),
                 taskView.getBottom());
-        splitController.setInitialTaskSelect(taskView, splitPositionOption, initialBounds);
+        mSplitSelectStateController.setInitialTaskSelect(taskView,
+                splitPositionOption, initialBounds);
         mSplitHiddenTaskViewIndex = indexOfChild(taskView);
-        mSplitPlaceholderView.setLayoutParams(
-                splitController.getLayoutParamsForActivePosition(getResources(),
-                        mActivity.getDeviceProfile()));
-        mSplitPlaceholderView.setIcon(taskView.getIconView());
         if (ENABLE_QUICKSTEP_LIVE_TILE.get()) {
             finishRecentsAnimation(true, null);
         }
@@ -3140,17 +3144,48 @@
 
     public PendingAnimation createSplitSelectInitAnimation() {
         int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
-        return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration);
+        return createTaskDismissAnimation(mSplitHiddenTaskView, true, false, duration,
+                true /* dismissingForSplitSelection*/);
     }
 
     public void confirmSplitSelect(TaskView taskView) {
-        mSplitPlaceholderView.getSplitController().setSecondTaskId(taskView);
-        resetTaskVisuals();
-        setTranslationY(0);
+        RectF secondTaskStartingBounds = new RectF();
+        Rect secondTaskEndingBounds = new Rect();
+        // TODO(194414938) starting bounds seem slightly off, investigate
+        Rect firstTaskStartingBounds = new Rect();
+        Rect firstTaskEndingBounds = mTempRect;
+        int duration = mActivity.getStateManager().getState().getTransitionDuration(getContext());
+        PendingAnimation pendingAnimation = new PendingAnimation(duration);
+
+        int halfDividerSize = getResources()
+                .getDimensionPixelSize(R.dimen.multi_window_task_divider_size) / 2;
+        mOrientationHandler.getFinalSplitPlaceholderBounds(halfDividerSize,
+                mActivity.getDeviceProfile(),
+                mSplitSelectStateController.getActiveSplitPositionOption(), firstTaskEndingBounds,
+                secondTaskEndingBounds);
+
+        mFirstFloatingTaskView.getBoundsOnScreen(firstTaskStartingBounds);
+        mFirstFloatingTaskView.addAnimation(pendingAnimation,
+                new RectF(firstTaskStartingBounds), firstTaskEndingBounds, mFirstFloatingTaskView,
+                false /*fadeWithThumbnail*/);
+
+        mSecondFloatingTaskView = FloatingTaskView.getFloatingTaskView(mActivity,
+                taskView, secondTaskStartingBounds);
+        mSecondFloatingTaskView.setAlpha(1);
+        mSecondFloatingTaskView.addAnimation(pendingAnimation, secondTaskStartingBounds,
+                secondTaskEndingBounds, taskView.getThumbnail(),
+                true /*fadeWithThumbnail*/);
+        pendingAnimation.addEndListener(aBoolean -> {
+            mSplitSelectStateController.setSecondTaskId(taskView);
+            resetFromSplitSelectionState();
+        });
+        mSecondSplitHiddenTaskView = taskView;
+        taskView.setVisibility(INVISIBLE);
+        pendingAnimation.buildAnim().start();
     }
 
     public PendingAnimation cancelSplitSelect(boolean animate) {
-        SplitSelectStateController splitController = mSplitPlaceholderView.getSplitController();
+        SplitSelectStateController splitController = mSplitSelectStateController;
         SplitPositionOption splitOption = splitController.getActiveSplitPositionOption();
         Rect initialBounds = splitController.getInitialBounds();
         splitController.resetState();
@@ -3263,8 +3298,19 @@
         }
         onLayout(false /*  changed */, getLeft(), getTop(), getRight(), getBottom());
         resetTaskVisuals();
+        mSplitHiddenTaskView.setVisibility(VISIBLE);
         mSplitHiddenTaskView = null;
+        mSecondSplitHiddenTaskView.setVisibility(VISIBLE);
+        mSecondSplitHiddenTaskView = null;
         mSplitHiddenTaskViewIndex = -1;
+        if (mFirstFloatingTaskView != null) {
+            mActivity.getRootView().removeView(mFirstFloatingTaskView);
+            mFirstFloatingTaskView = null;
+        }
+        if (mSecondFloatingTaskView != null) {
+            mActivity.getRootView().removeView(mSecondFloatingTaskView);
+            mSecondFloatingTaskView = null;
+        }
     }
 
     private void updateDeadZoneRects() {
diff --git a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
index bb8bc11..a712d1a 100644
--- a/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
+++ b/quickstep/src/com/android/quickstep/views/SplitPlaceholderView.java
@@ -22,6 +22,8 @@
 import android.view.Gravity;
 import android.widget.FrameLayout;
 
+import androidx.annotation.Nullable;
+
 import com.android.quickstep.util.SplitSelectStateController;
 
 public class SplitPlaceholderView extends FrameLayout {
@@ -55,6 +57,11 @@
         return mSplitController;
     }
 
+    @Nullable
+    public IconView getIcon() {
+        return mIcon;
+    }
+
     public void setIcon(IconView icon) {
         if (mIcon == null) {
             mIcon = new IconView(getContext());
diff --git a/res/layout/floating_split_select_view.xml b/res/layout/floating_split_select_view.xml
new file mode 100644
index 0000000..e184b91
--- /dev/null
+++ b/res/layout/floating_split_select_view.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.quickstep.views.FloatingTaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/thumbnail"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone" />
+
+    <com.android.quickstep.views.SplitPlaceholderView
+        android:id="@+id/split_placeholder"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/split_placeholder_size"
+        android:background="@android:color/white"
+        android:visibility="gone" />
+
+</com.android.quickstep.views.FloatingTaskView>
\ No newline at end of file
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 46570f0..dd27685 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -326,6 +326,7 @@
     <dimen name="overview_task_margin">0dp</dimen>
     <dimen name="overview_actions_bottom_margin_gesture">0dp</dimen>
     <dimen name="overview_actions_bottom_margin_three_button">0dp</dimen>
+    <dimen name="split_placeholder_size">110dp</dimen>
 
 <!-- Workspace grid visualization parameters -->
     <dimen name="grid_visualization_rounding_radius">22dp</dimen>
diff --git a/res/xml/size_limits_80x104.xml b/res/xml/size_limits_80x104.xml
index f5ca757..93d43fb 100644
--- a/res/xml/size_limits_80x104.xml
+++ b/res/xml/size_limits_80x104.xml
@@ -31,32 +31,72 @@
     </device-padding>
 
     <device-padding
-        launcher:maxEmptySpace="97dp">
+        launcher:maxEmptySpace="100dp">
         <workspaceTopPadding
             launcher:a="0"
-            launcher:b="16dp"/>
+            launcher:b="9dp"/>
         <workspaceBottomPadding
-            launcher:a="0.56"
+            launcher:a="0.40"
             launcher:b="0"
-            launcher:c="16dp"/>
+            launcher:c="9dp"/>
         <hotseatBottomPadding
-            launcher:a="0.44"
+            launcher:a="0.60"
             launcher:b="0"
-            launcher:c="16dp"/>
+            launcher:c="9dp"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="103dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="26dp"/>
+        <workspaceBottomPadding
+            launcher:a="0"
+            launcher:b="20dp"/>
+        <hotseatBottomPadding
+            launcher:a="1"
+            launcher:b="0"
+            launcher:c="46dp"/>
     </device-padding>
 
     <device-padding
         launcher:maxEmptySpace="107dp">
         <workspaceTopPadding
             launcher:a="0"
-            launcher:b="16dp"/>
+            launcher:b="9dp"/>
         <workspaceBottomPadding
             launcher:a="0"
-            launcher:b="36dp"/>
+            launcher:b="34dp"/>
         <hotseatBottomPadding
             launcher:a="1"
             launcher:b="0"
+            launcher:c="43dp"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="120dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="16dp"/>
+        <workspaceBottomPadding
+            launcher:a="1"
             launcher:c="52dp"/>
+        <hotseatBottomPadding
+            launcher:a="0"
+            launcher:b="36dp"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="135dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="39dp"/>
+        <workspaceBottomPadding
+            launcher:a="1"
+            launcher:c="75dp"/>
+        <hotseatBottomPadding
+            launcher:a="0"
+            launcher:b="36dp"/>
     </device-padding>
 
     <device-padding
@@ -71,5 +111,4 @@
             launcher:a="0"
             launcher:b="36dp"/>
     </device-padding>
-
 </device-paddings>
\ No newline at end of file
diff --git a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
index 07351fe..4319355 100644
--- a/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/ModelMultiCallbacksTest.java
@@ -31,6 +31,7 @@
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.shadows.ShadowLooperExecutor;
 import com.android.launcher3.util.Executors;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LauncherLayoutBuilder;
 import com.android.launcher3.util.LauncherModelHelper;
@@ -202,7 +203,7 @@
         }
 
         @Override
-        public IntSet getPagesToBindSynchronously() {
+        public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
             return mPageToBindSync;
         }
 
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherPageRestoreHelperTest.java b/robolectric_tests/src/com/android/launcher3/util/LauncherPageRestoreHelperTest.java
deleted file mode 100644
index 51f5851..0000000
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherPageRestoreHelperTest.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/**
- * 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.launcher3.util;
-
-import android.os.Bundle;
-
-import com.android.launcher3.LauncherPageRestoreHelper;
-import com.android.launcher3.Workspace;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.robolectric.RobolectricTestRunner;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-@RunWith(RobolectricTestRunner.class)
-public class LauncherPageRestoreHelperTest {
-
-    // Type: int
-    private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
-    // Type: int
-    private static final String RUNTIME_STATE_CURRENT_SCREEN_COUNT =
-            "launcher.current_screen_count";
-
-    private LauncherPageRestoreHelper mPageRestoreHelper;
-    private Bundle mState;
-
-    @Mock
-    private Workspace mWorkspace;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mPageRestoreHelper = new LauncherPageRestoreHelper(mWorkspace);
-        mState = new Bundle();
-    }
-
-    @Test
-    public void givenNoChildrenInWorkspace_whenSavePages_thenNothingSaved() {
-        when(mWorkspace.getChildCount()).thenReturn(0);
-
-        mPageRestoreHelper.savePagesToRestore(mState);
-
-        assertFalse(mState.containsKey(RUNTIME_STATE_CURRENT_SCREEN_COUNT));
-        assertFalse(mState.containsKey(RUNTIME_STATE_CURRENT_SCREEN));
-    }
-
-    @Test
-    public void givenMultipleCurrentPages_whenSavePages_thenSavedCorrectly() {
-        when(mWorkspace.getChildCount()).thenReturn(5);
-        when(mWorkspace.getCurrentPage()).thenReturn(2);
-        givenPanelCount(2);
-
-        mPageRestoreHelper.savePagesToRestore(mState);
-
-        assertEquals(5, mState.getInt(RUNTIME_STATE_CURRENT_SCREEN_COUNT));
-        assertEquals(2, mState.getInt(RUNTIME_STATE_CURRENT_SCREEN));
-    }
-
-    @Test
-    public void givenNullSavedState_whenRestorePages_thenReturnEmptyIntSet() {
-        IntSet result = mPageRestoreHelper.getPagesToRestore(null);
-
-        assertTrue(result.isEmpty());
-    }
-
-    @Test
-    public void givenTotalPageCountMissing_whenRestorePages_thenReturnEmptyIntSet() {
-        givenSavedCurrentPage(1);
-        givenPanelCount(1);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertTrue(result.isEmpty());
-    }
-
-    @Test
-    public void givenCurrentPageMissing_whenRestorePages_thenReturnEmptyIntSet() {
-        givenSavedPageCount(3);
-        givenPanelCount(2);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertTrue(result.isEmpty());
-    }
-
-    @Test
-    public void givenOnePanel_whenRestorePages_thenReturnThatPage() {
-        givenSavedCurrentPage(2);
-        givenSavedPageCount(5);
-        givenPanelCount(1);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(1, result.size());
-        assertEquals(2, result.getArray().get(0));
-    }
-
-    @Test
-    public void givenTwoPanelOnFirstPages_whenRestorePages_thenReturnThosePages() {
-        givenSavedCurrentPage(0, 1);
-        givenSavedPageCount(2);
-        givenPanelCount(2);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(IntSet.wrap(0, 1), result);
-    }
-
-    @Test
-    public void givenTwoPanelOnMiddlePages_whenRestorePages_thenReturnThosePages() {
-        givenSavedCurrentPage(2, 3);
-        givenSavedPageCount(5);
-        givenPanelCount(2);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(IntSet.wrap(2, 3), result);
-    }
-
-    @Test
-    public void givenTwoPanelOnLastPage_whenRestorePages_thenReturnOnlyLastPage() {
-        // The device has two panel home but the current page is the last page, so we don't have
-        // a right panel, only the left one.
-        givenSavedCurrentPage(2);
-        givenSavedPageCount(3);
-        givenPanelCount(2);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(IntSet.wrap(2), result);
-    }
-
-    @Test
-    public void givenOnlyOnePageAndPhoneFolding_whenRestorePages_thenReturnOnlyOnePage() {
-        givenSavedCurrentPage(0);
-        givenSavedPageCount(1);
-        givenPanelCount(1);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(IntSet.wrap(0), result);
-    }
-
-    @Test
-    public void givenPhoneFolding_whenRestorePages_thenReturnOnlyTheFirstCurrentPage() {
-        givenSavedCurrentPage(2, 3);
-        givenSavedPageCount(4);
-        givenPanelCount(1);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(IntSet.wrap(2), result);
-    }
-
-    @Test
-    public void givenPhoneUnfolding_whenRestorePages_thenReturnCurrentPagePlusTheNextOne() {
-        givenSavedCurrentPage(2);
-        givenSavedPageCount(4);
-        givenPanelCount(2);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(IntSet.wrap(2, 3), result);
-    }
-
-    @Test
-    public void givenPhoneUnfoldingOnLastPage_whenRestorePages_thenReturnOnlyLastPage() {
-        givenSavedCurrentPage(4);
-        givenSavedPageCount(5);
-        givenPanelCount(2);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(IntSet.wrap(4), result);
-    }
-
-    @Test
-    public void givenOnlyOnePageAndPhoneUnfolding_whenRestorePages_thenReturnOnlyOnePage() {
-        givenSavedCurrentPage(0);
-        givenSavedPageCount(1);
-        givenPanelCount(2);
-
-        IntSet result = mPageRestoreHelper.getPagesToRestore(mState);
-
-        assertEquals(IntSet.wrap(0), result);
-    }
-
-    private void givenPanelCount(int panelCount) {
-        when(mWorkspace.getPanelCount()).thenReturn(panelCount);
-        when(mWorkspace.getLeftmostVisiblePageForIndex(anyInt())).thenAnswer(invocation -> {
-            int pageIndex = invocation.getArgument(0);
-            return pageIndex * panelCount / panelCount;
-        });
-    }
-
-    private void givenSavedPageCount(int pageCount) {
-        mState.putInt(RUNTIME_STATE_CURRENT_SCREEN_COUNT, pageCount);
-    }
-
-    private void givenSavedCurrentPage(int... pages) {
-        mState.putInt(RUNTIME_STATE_CURRENT_SCREEN, pages[0]);
-    }
-}
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index f4447b1..af6cce1 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -566,9 +566,10 @@
                 int spanX = mDragOutlines[i].cellHSpan;
                 int spanY = mDragOutlines[i].cellVSpan;
 
+                // TODO b/194414754 clean this up, reconcile with cellToRect
                 mVisualizeGridRect.set(paddingX, paddingY,
-                        mCellWidth * spanX - paddingX,
-                        mCellHeight * spanY - paddingY);
+                        mCellWidth * spanX + mBorderSpacing * (spanX - 1) - paddingX,
+                        mCellHeight * spanY + mBorderSpacing * (spanY - 1) - paddingY);
 
                 int transX = x * mCellWidth + (x * mBorderSpacing)
                         + getPaddingLeft() + paddingX;
diff --git a/src/com/android/launcher3/DeleteDropTarget.java b/src/com/android/launcher3/DeleteDropTarget.java
index ba55834..477964a 100644
--- a/src/com/android/launcher3/DeleteDropTarget.java
+++ b/src/com/android/launcher3/DeleteDropTarget.java
@@ -25,6 +25,7 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.logging.StatsLogManager;
@@ -128,11 +129,21 @@
     public void completeDrop(DragObject d) {
         ItemInfo item = d.dragInfo;
         if (canRemove(item)) {
-            int itemPage = mLauncher.getWorkspace().getCurrentPage();
+            ItemInfo pageItem = item;
+            if (item.container <= 0) {
+                View v = mLauncher.getWorkspace().getHomescreenIconByItemId(item.container);
+                if (v != null) {
+                    pageItem = (ItemInfo) v.getTag();
+                }
+            }
+            IntSet pageIds = pageItem.container == Favorites.CONTAINER_DESKTOP
+                    ? IntSet.wrap(pageItem.screenId)
+                    : mLauncher.getWorkspace().getCurrentPageScreenIds();
+
             onAccessibilityDrop(null, item);
             ModelWriter modelWriter = mLauncher.getModelWriter();
             Runnable onUndoClicked = () -> {
-                mLauncher.setPagesToBindSynchronously(IntSet.wrap(itemPage));
+                mLauncher.setPagesToBindSynchronously(pageIds);
                 modelWriter.abortDelete();
                 mLauncher.getStatsLogManager().logger().log(LAUNCHER_UNDO);
             };
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index edd44e3..222c7d6 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -22,6 +22,7 @@
 import static com.android.launcher3.ResourceUtils.pxFromDp;
 import static com.android.launcher3.Utilities.dpiFromPx;
 import static com.android.launcher3.Utilities.pxFromSp;
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.util.WindowManagerCompat.MIN_TABLET_WIDTH;
 
 import android.annotation.SuppressLint;
@@ -399,7 +400,8 @@
     }
 
     private void updateHotseatIconSize(int hotseatIconSizePx) {
-        hotseatCellHeightPx = hotseatIconSizePx;
+        // Ensure there is enough space for folder icons, which have a slightly larger radius.
+        hotseatCellHeightPx = (int) Math.ceil(hotseatIconSizePx * ICON_OVERLAP_FACTOR);
         if (isVerticalBarLayout()) {
             hotseatBarSizePx = hotseatIconSizePx + hotseatBarSidePaddingStartPx
                     + hotseatBarSidePaddingEndPx;
@@ -478,7 +480,7 @@
         if (workspaceCellPaddingY < iconTextHeight) {
             iconTextSizePx = 0;
             iconDrawablePaddingPx = 0;
-            cellHeightPx = iconSizePx;
+            cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR);
             autoResizeAllAppsCells();
         }
     }
@@ -565,7 +567,8 @@
             desiredWorkspaceLeftRightMarginPx = (int) (desiredWorkspaceLeftRightOriginalPx * scale);
         } else {
             cellWidthPx = iconSizePx + iconDrawablePaddingPx;
-            cellHeightPx = iconSizePx + iconDrawablePaddingPx
+            cellHeightPx = (int) Math.ceil(iconSizePx * ICON_OVERLAP_FACTOR)
+                    + iconDrawablePaddingPx
                     + Utilities.calculateTextHeight(iconTextSizePx);
             int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
             if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout
@@ -824,9 +827,9 @@
                 ? workspacePadding.bottom
                 : hotseatBarSizePx - hotseatCellHeightPx - hotseatQsbHeight;
 
-        if (isScalableGrid && qsbBottomMarginPx <= freeSpace) {
-            return qsbBottomMarginPx;
-        } else {
+        if (isScalableGrid) {
+            return Math.min(qsbBottomMarginPx, freeSpace);
+        }  else {
             return (int) (freeSpace * QSB_CENTER_FACTOR)
                 + (isTaskbarPresent ? taskbarSize : getInsets().bottom);
         }
@@ -899,14 +902,16 @@
         return isVerticalBarLayout();
     }
 
-    public int getCellHeight(@ContainerType int containerType) {
+    public int getCellContentHeight(@ContainerType int containerType) {
         switch (containerType) {
             case CellLayout.WORKSPACE:
                 return cellHeightPx;
             case CellLayout.FOLDER:
                 return folderCellHeightPx;
             case CellLayout.HOTSEAT:
-                return hotseatCellHeightPx;
+                // The hotseat is the only container where the cell height is going to be
+                // different from the content within that cell.
+                return iconSizePx;
             default:
                 // ??
                 return 0;
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 78a8a97..e736022 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -36,6 +36,7 @@
 import static com.android.launcher3.LauncherState.NO_SCALE;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
+import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -259,6 +260,8 @@
     private static final String RUNTIME_STATE_PENDING_ACTIVITY_RESULT = "launcher.activity_result";
     // Type: SparseArray<Parcelable>
     private static final String RUNTIME_STATE_WIDGET_PANEL = "launcher.widget_panel";
+    // Type int[]
+    private static final String RUNTIME_STATE_CURRENT_SCREEN_IDS = "launcher.current_screen_ids";
 
     public static final String ON_CREATE_EVT = "Launcher.onCreate";
     public static final String ON_START_EVT = "Launcher.onStart";
@@ -287,8 +290,6 @@
     private WidgetManagerHelper mAppWidgetManager;
     private LauncherAppWidgetHost mAppWidgetHost;
 
-    private LauncherPageRestoreHelper mPageRestoreHelper;
-
     private final int[] mTmpAddItemCellCoordinates = new int[2];
 
     @Thunk
@@ -325,7 +326,7 @@
     private PopupDataProvider mPopupDataProvider;
 
     private IntSet mSynchronouslyBoundPages = new IntSet();
-    private IntSet mPagesToBindSynchronously = new IntSet();
+    @NonNull private IntSet mPagesToBindSynchronously = new IntSet();
 
     // We only want to get the SharedPreferences once since it does an FS stat each time we get
     // it from the context.
@@ -460,9 +461,11 @@
         restoreState(savedInstanceState);
         mStateManager.reapplyState();
 
-        mPageRestoreHelper = new LauncherPageRestoreHelper(mWorkspace);
         if (savedInstanceState != null) {
-            mPagesToBindSynchronously = mPageRestoreHelper.getPagesToRestore(savedInstanceState);
+            int[] pageIds = savedInstanceState.getIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS);
+            if (pageIds != null) {
+                mPagesToBindSynchronously = IntSet.wrap(pageIds);
+            }
         }
 
         if (!mModel.addCallbacksAndLoad(this)) {
@@ -1188,7 +1191,6 @@
         // Until the workspace is bound, ensure that we keep the wallpaper offset locked to the
         // default state, otherwise we will update to the wrong offsets in RTL
         mWorkspace.lockWallpaperToDefaultPage();
-        mWorkspace.bindAndInitLeftPanel();
         mWorkspace.bindAndInitFirstWorkspaceScreen(null /* recycled qsb */);
         mDragController.addDragListener(mWorkspace);
 
@@ -1586,14 +1588,19 @@
     public void onRestoreInstanceState(Bundle state) {
         super.onRestoreInstanceState(state);
         if (mSynchronouslyBoundPages != null) {
-            mSynchronouslyBoundPages.forEach(page -> mWorkspace.restoreInstanceStateForChild(page));
+            mSynchronouslyBoundPages.forEach(screenId -> {
+                int pageIndex = mWorkspace.getPageIndexForScreenId(screenId);
+                if (pageIndex != PagedView.INVALID_PAGE) {
+                    mWorkspace.restoreInstanceStateForChild(pageIndex);
+                }
+            });
         }
     }
 
     @Override
     protected void onSaveInstanceState(Bundle outState) {
-        mPageRestoreHelper.savePagesToRestore(outState);
-
+        outState.putIntArray(RUNTIME_STATE_CURRENT_SCREEN_IDS,
+                mWorkspace.getCurrentPageScreenIds().getArray().toArray());
         outState.putInt(RUNTIME_STATE, mStateManager.getState().ordinal);
 
         AbstractFloatingView widgets = AbstractFloatingView
@@ -2081,18 +2088,42 @@
         mPagesToBindSynchronously = pages;
     }
 
-    /**
-     * Implementation of the method from LauncherModel.Callbacks.
-     */
     @Override
-    public IntSet getPagesToBindSynchronously() {
-        if (mPagesToBindSynchronously != null && !mPagesToBindSynchronously.isEmpty()) {
-            return mPagesToBindSynchronously;
-        } else if (mWorkspace != null) {
-            return mWorkspace.getVisiblePageIndices();
+    public IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
+        IntSet visibleIds = mPagesToBindSynchronously.isEmpty()
+                ? mWorkspace.getCurrentPageScreenIds() : mPagesToBindSynchronously;
+        IntArray actualIds = new IntArray();
+
+        if (mDeviceProfile.isTwoPanels) {
+            actualIds.add(LEFT_PANEL_ID);
         } else {
-            return new IntSet();
+            visibleIds.remove(LEFT_PANEL_ID);
         }
+        IntSet result = new IntSet();
+        if (visibleIds.isEmpty()) {
+            return result;
+        }
+        for (int id : orderedScreenIds.toArray()) {
+            if (id != LEFT_PANEL_ID) {
+                actualIds.add(id);
+            }
+        }
+        int firstId = visibleIds.getArray().get(0);
+        if (actualIds.contains(firstId)) {
+            result.add(firstId);
+
+            if (mDeviceProfile.isTwoPanels) {
+                int index = actualIds.indexOf(firstId);
+                int nextIndex = ((int) (index / 2)) * 2;
+                if (nextIndex == index) {
+                    nextIndex++;
+                }
+                if (nextIndex < actualIds.size()) {
+                    result.add(actualIds.get(nextIndex));
+                }
+            }
+        }
+        return result;
     }
 
     /**
@@ -2143,7 +2174,7 @@
         // Make sure the first screen is at the start if there's no widget panel,
         // or on the second place if the first is the widget panel
         boolean isLeftPanelShown =
-                mWorkspace.mWorkspaceScreens.containsKey(Workspace.LEFT_PANEL_ID);
+                mWorkspace.mWorkspaceScreens.containsKey(LEFT_PANEL_ID);
         int firstScreenPosition = isLeftPanelShown && orderedScreenIds.size() > 1 ? 1 : 0;
 
         if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
@@ -2171,7 +2202,7 @@
                 continue;
             }
 
-            if (screenId == Workspace.LEFT_PANEL_ID) {
+            if (screenId == LEFT_PANEL_ID) {
                 // No need to bind the left panel, as its always bound.
                 continue;
             }
@@ -2252,7 +2283,7 @@
             }
 
             // Skip if the item is on the left widget panel but the panel is not shown
-            if (item.screenId == Workspace.LEFT_PANEL_ID && !getDeviceProfile().isTwoPanels) {
+            if (item.screenId == LEFT_PANEL_ID && !getDeviceProfile().isTwoPanels) {
                 continue;
             }
 
@@ -2555,9 +2586,6 @@
     @Override
     public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
         mSynchronouslyBoundPages = boundPages;
-        if (!boundPages.isEmpty()) {
-            mWorkspace.setCurrentPage(boundPages.getArray().get(0));
-        }
         mPagesToBindSynchronously = new IntSet();
 
         clearPendingBinds();
@@ -2598,7 +2626,8 @@
         }
 
         int currentPage = pagesBoundFirst != null && !pagesBoundFirst.isEmpty()
-                ? pagesBoundFirst.getArray().get(0) : PagedView.INVALID_PAGE;
+                ? mWorkspace.getPageIndexForScreenId(pagesBoundFirst.getArray().get(0))
+                : PagedView.INVALID_PAGE;
         // When undoing the removal of the last item on a page, return to that page.
         // Since we are just resetting the current page without user interaction,
         // override the previous page so we don't log the page switch.
diff --git a/src/com/android/launcher3/LauncherPageRestoreHelper.java b/src/com/android/launcher3/LauncherPageRestoreHelper.java
deleted file mode 100644
index e679a12..0000000
--- a/src/com/android/launcher3/LauncherPageRestoreHelper.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/**
- * 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.launcher3;
-
-import android.os.Bundle;
-import android.util.Log;
-
-import androidx.annotation.VisibleForTesting;
-
-import com.android.launcher3.util.IntSet;
-
-import static androidx.annotation.VisibleForTesting.PACKAGE_PRIVATE;
-
-/**
- * There's a logic which prioritizes the binding for the current page and defers the other pages'
- * binding. If two panel home is enabled, we want to bind both pages together.
- * LauncherPageRestoreHelper's purpose is to contain the logic for persisting, restoring and
- * calculating which pages to load immediately.
- */
-public class LauncherPageRestoreHelper {
-
-    public static final String TAG = "LauncherPageRestoreHelper";
-
-    // Type: int
-    private static final String RUNTIME_STATE_CURRENT_SCREEN = "launcher.current_screen";
-    // Type: int
-    private static final String RUNTIME_STATE_CURRENT_SCREEN_COUNT =
-            "launcher.current_screen_count";
-
-    private Workspace mWorkspace;
-
-    public LauncherPageRestoreHelper(Workspace workspace) {
-        this.mWorkspace = workspace;
-    }
-
-    /**
-     * Some configuration changes trigger Launcher to recreate itself, and we want to give more
-     * priority to the currently active pages in the restoration process.
-     */
-    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
-    public IntSet getPagesToRestore(Bundle savedInstanceState) {
-        IntSet pagesToRestore = new IntSet();
-
-        if (savedInstanceState == null) {
-            return pagesToRestore;
-        }
-
-        int currentPage = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, -1);
-        int totalPageCount = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN_COUNT, -1);
-        int panelCount = mWorkspace.getPanelCount();
-
-        if (totalPageCount <= 0 || currentPage < 0) {
-            Log.e(TAG, "getPagesToRestore: Invalid input: " + totalPageCount + ", " + currentPage);
-            return pagesToRestore;
-        }
-
-        int newCurrentPage = mWorkspace.getLeftmostVisiblePageForIndex(currentPage);
-        for (int page = newCurrentPage; page < newCurrentPage + panelCount
-                && page < totalPageCount; page++) {
-            pagesToRestore.add(page);
-        }
-
-        return pagesToRestore;
-    }
-
-    /**
-     * This should be called from Launcher's onSaveInstanceState method to persist everything that
-     * is necessary to calculate later which pages need to be initialized first after a
-     * configuration change.
-     */
-    @VisibleForTesting(otherwise = PACKAGE_PRIVATE)
-    public void savePagesToRestore(Bundle outState) {
-        int pageCount = mWorkspace.getChildCount();
-        if (pageCount > 0) {
-            outState.putInt(RUNTIME_STATE_CURRENT_SCREEN, mWorkspace.getCurrentPage());
-            outState.putInt(RUNTIME_STATE_CURRENT_SCREEN_COUNT, pageCount);
-        }
-    }
-}
diff --git a/src/com/android/launcher3/LauncherProvider.java b/src/com/android/launcher3/LauncherProvider.java
index 440e9e3..a8ed6bc 100644
--- a/src/com/android/launcher3/LauncherProvider.java
+++ b/src/com/android/launcher3/LauncherProvider.java
@@ -61,7 +61,6 @@
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.DbDowngradeHelper;
-import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.pm.UserCache;
 import com.android.launcher3.provider.LauncherDbUtils;
 import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
@@ -1090,7 +1089,7 @@
         }
 
         private int initializeMaxScreenId(SQLiteDatabase db) {
-            return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d",
+            return getMaxId(db, "SELECT MAX(%1$s) FROM %2$s WHERE %3$s = %4$d AND %1$s >= 0",
                     Favorites.SCREEN, Favorites.TABLE_NAME, Favorites.CONTAINER,
                     Favorites.CONTAINER_DESKTOP);
         }
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index dff8534..bebbf4f 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -122,7 +122,7 @@
 
     public int getCellContentHeight() {
         return Math.min(getMeasuredHeight(),
-                mActivity.getDeviceProfile().getCellHeight(mContainerType));
+                mActivity.getDeviceProfile().getCellContentHeight(mContainerType));
     }
 
     public void measureChild(View child) {
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 3cabc87..7d818d2 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -381,6 +381,21 @@
     }
 
     /**
+     * Similar to {@link #scaleRectAboutCenter(Rect, float)} except this allows different scales
+     * for X and Y
+     */
+    public static void scaleRectFAboutCenter(RectF r, float scaleX, float scaleY) {
+        float px = r.centerX();
+        float py = r.centerY();
+        r.offset(-px, -py);
+        r.left = r.left * scaleX;
+        r.top = r.top * scaleY;
+        r.right = r.right * scaleX;
+        r.bottom = r.bottom * scaleY;
+        r.offset(px, py);
+    }
+
+    /**
      * Maps t from one range to another range.
      * @param t The value to map.
      * @param fromMin The lower bound of the range that t is being mapped from.
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 6c0e893..faf2dd2 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -102,6 +102,7 @@
 import com.android.launcher3.util.EdgeEffectCompat;
 import com.android.launcher3.util.Executors;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.IntSparseArrayMap;
 import com.android.launcher3.util.ItemInfoMatcher;
 import com.android.launcher3.util.LauncherBindableItemsContainer;
@@ -327,22 +328,6 @@
             setPageSpacing(Math.max(maxInsets, maxPadding));
         }
 
-        if (grid.isTwoPanels) {
-            // Add left widget panel if it isn't already there
-            if (!mWorkspaceScreens.containsKey(LEFT_PANEL_ID)) {
-                int newCurrentPage = mCurrentPage + 1;
-                bindAndInitLeftPanel();
-                setCurrentPage(newCurrentPage);
-            }
-        } else {
-            // Remove left widget panel if it is present
-            if (mWorkspaceScreens.containsKey(LEFT_PANEL_ID)) {
-                int newCurrentPage = mCurrentPage - 1;
-                removeLeftPanel();
-                setCurrentPage(newCurrentPage);
-            }
-        }
-
         int paddingLeftRight = grid.cellLayoutPaddingLeftRightPx;
         int paddingBottom = grid.cellLayoutBottomPaddingPx;
         int twoPanelLandscapeSidePadding = paddingLeftRight * 2;
@@ -570,6 +555,10 @@
         if (!FeatureFlags.QSB_ON_FIRST_SCREEN) {
             return;
         }
+        if (isTwoPanelEnabled()) {
+            insertNewWorkspaceScreen(Workspace.LEFT_PANEL_ID, getChildCount());
+        }
+
         // Add the first page
         CellLayout firstPage = insertNewWorkspaceScreen(Workspace.FIRST_SCREEN_ID, getChildCount());
         // Always add a QSB on the first screen.
@@ -590,19 +579,6 @@
         }
     }
 
-    /**
-     * Initializes and binds the left panel
-     */
-    public void bindAndInitLeftPanel() {
-        if (!FeatureFlags.QSB_ON_FIRST_SCREEN || !isTwoPanelEnabled()
-                || mWorkspaceScreens.containsKey(Workspace.LEFT_PANEL_ID)) {
-            return;
-        }
-
-        insertNewWorkspaceScreen(Workspace.LEFT_PANEL_ID, getChildCount());
-        mLauncher.getModelWriter().setLeftPanelShown(true);
-    }
-
     public void removeAllWorkspaceScreens() {
         // Disable all layout transitions before removing all pages to ensure that we don't get the
         // transition animations competing with us changing the scroll when we add pages
@@ -624,7 +600,6 @@
         mLauncher.mHandler.removeCallbacksAndMessages(DeferredWidgetRefresh.class);
 
         // Ensure that the first page is always present
-        bindAndInitLeftPanel();
         bindAndInitFirstWorkspaceScreen(qsb);
 
         // Re-enable the layout transitions
@@ -645,18 +620,6 @@
         insertNewWorkspaceScreen(screenId, getChildCount());
     }
 
-    private void removeLeftPanel() {
-        if (!mWorkspaceScreens.containsKey(LEFT_PANEL_ID)) {
-            return;
-        }
-        mLauncher.getModelWriter().setLeftPanelShown(false);
-        CellLayout leftPanel = mWorkspaceScreens.get(LEFT_PANEL_ID);
-        mWorkspaceScreens.remove(LEFT_PANEL_ID);
-        removeView(leftPanel);
-        mScreenOrder.removeValue(LEFT_PANEL_ID);
-        updatePageScrollValues();
-    }
-
     public CellLayout insertNewWorkspaceScreen(int screenId, int insertIndex) {
         if (mWorkspaceScreens.containsKey(screenId)) {
             throw new RuntimeException("Screen id " + screenId + " already exists!");
@@ -829,6 +792,10 @@
         return indexOfChild(mWorkspaceScreens.get(screenId));
     }
 
+    public IntSet getCurrentPageScreenIds() {
+        return IntSet.wrap(getScreenIdForPageIndex(getCurrentPage()));
+    }
+
     public int getScreenIdForPageIndex(int index) {
         if (0 <= index && index < mScreenOrder.size()) {
             return mScreenOrder.get(index);
diff --git a/src/com/android/launcher3/anim/PendingAnimation.java b/src/com/android/launcher3/anim/PendingAnimation.java
index 01f7de6..3ab893b 100644
--- a/src/com/android/launcher3/anim/PendingAnimation.java
+++ b/src/com/android/launcher3/anim/PendingAnimation.java
@@ -56,6 +56,10 @@
         mAnim = new AnimatorSet();
     }
 
+    public long getDuration() {
+        return mDuration;
+    }
+
     /**
      * Utility method to sent an interpolator on an animation and add it to the list
      */
diff --git a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
index 6a6603c..74d9a22 100644
--- a/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
+++ b/src/com/android/launcher3/dragndrop/FolderAdaptiveIcon.java
@@ -21,11 +21,11 @@
 import android.annotation.TargetApi;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
+import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
-import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
 import android.util.Log;
@@ -129,10 +129,19 @@
                     canvas.restore();
                 });
 
+        Bitmap bgBitmap = BitmapRenderer.createHardwareBitmap(dragViewSize.x, dragViewSize.y,
+                (canvas) -> {
+                    Paint p = new Paint();
+                    p.setColor(bg.getBgColor());
+
+                    canvas.drawCircle(dragViewSize.x / 2f, dragViewSize.y / 2f, bg.getRadius(), p);
+                });
+
         ShiftedBitmapDrawable badge = new ShiftedBitmapDrawable(badgeBmp, 0, 0);
         ShiftedBitmapDrawable foreground = new ShiftedBitmapDrawable(previewBitmap, 0, 0);
+        ShiftedBitmapDrawable background = new ShiftedBitmapDrawable(bgBitmap, 0, 0);
 
-        return new FolderAdaptiveIcon(new ColorDrawable(bg.getBgColor()), foreground, badge, mask);
+        return new FolderAdaptiveIcon(background, foreground, badge, mask);
     }
 
     @Override
diff --git a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
index 54967a99..8cd91d3 100644
--- a/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
+++ b/src/com/android/launcher3/folder/ClippedFolderIconLayoutRule.java
@@ -7,7 +7,9 @@
 
     private static final float MIN_SCALE = 0.44f;
     private static final float MAX_SCALE = 0.51f;
-    private static final float MAX_RADIUS_DILATION = 0.1f;
+    private static final float MAX_RADIUS_DILATION = 0.25f;
+    // The max amount of overlap the preview items can go outside of the background bounds.
+    public static final float ICON_OVERLAP_FACTOR = 1 + (MAX_RADIUS_DILATION / 2f);
     private static final float ITEM_RADIUS_SCALE_FACTOR = 1.15f;
 
     public static final int EXIT_INDEX = -2;
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index f005b61..60d8cdb 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
 import static com.android.launcher3.folder.PreviewItemManager.INITIAL_ITEM_ANIMATION_DURATION;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_AUTO_LABELED;
@@ -236,6 +237,8 @@
     public void getPreviewBounds(Rect outBounds) {
         mPreviewItemManager.recomputePreviewDrawingParams();
         mBackground.getBounds(outBounds);
+        // The preview items go outside of the bounds of the background.
+        Utilities.scaleRectAboutCenter(outBounds, ICON_OVERLAP_FACTOR);
     }
 
     public float getBackgroundStrokeWidth() {
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index 460521f..18d0b10 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3.folder;
 
+import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ICON_OVERLAP_FACTOR;
 import static com.android.launcher3.graphics.IconShape.getShape;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
 
@@ -186,7 +187,7 @@
         outBounds.set(left, top, right, bottom);
     }
 
-    int getRadius() {
+    public int getRadius() {
         return previewSize / 2;
     }
 
@@ -348,7 +349,12 @@
 
     public Path getClipPath() {
         mPath.reset();
-        getShape().addToPath(mPath, getOffsetX(), getOffsetY(), getScaledRadius());
+        float radius = getScaledRadius() * ICON_OVERLAP_FACTOR;
+        // Find the difference in radius so that the clip path remains centered.
+        float radiusDifference = radius - getRadius();
+        float offsetX = basePreviewOffsetX - radiusDifference;
+        float offsetY = basePreviewOffsetY - radiusDifference;
+        getShape().addToPath(mPath, offsetX, offsetY, radius);
         return mPath;
     }
 
diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
index 13c83be..f3087c0 100644
--- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
+++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java
@@ -175,7 +175,6 @@
                             query);
 
                     MAIN_EXECUTOR.execute(() -> {
-                        mBgDataModel.isLeftPanelShown = deviceProfile.isTwoPanels;
                         renderView(previewContext, mBgDataModel, mWidgetProvidersMap);
                         mOnDestroyCallbacks.add(previewContext::onDestroy);
                     });
diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
index b2b0010..4f12d0b 100644
--- a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
+++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java
@@ -15,6 +15,9 @@
  */
 package com.android.launcher3.model;
 
+import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
+import static com.android.launcher3.WorkspaceLayoutManager.LEFT_PANEL_ID;
+
 import android.content.Intent;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
@@ -27,6 +30,7 @@
 import com.android.launcher3.LauncherAppState;
 import com.android.launcher3.LauncherModel.CallbackTask;
 import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.FileLog;
 import com.android.launcher3.model.BgDataModel.Callbacks;
 import com.android.launcher3.model.data.AppInfo;
@@ -38,6 +42,7 @@
 import com.android.launcher3.pm.PackageInstallInfo;
 import com.android.launcher3.util.GridOccupancy;
 import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.PackageManagerHelper;
 
 import java.util.ArrayList;
@@ -291,11 +296,15 @@
         boolean found = false;
 
         int screenCount = workspaceScreens.size();
-        int firstScreenToCheck = dataModel.isLeftPanelShown ? 2 : 1;
-        // Search on the screens for empty space
-        for (int screen = firstScreenToCheck; screen < screenCount; screen++) {
+        // First check the preferred screen.
+        IntSet screensToExclude = IntSet.wrap(LEFT_PANEL_ID);
+        if (FeatureFlags.QSB_ON_FIRST_SCREEN) {
+            screensToExclude.add(FIRST_SCREEN_ID);
+        }
+
+        for (int screen = 0; screen < screenCount; screen++) {
             screenId = workspaceScreens.get(screen);
-            if (findNextAvailableIconSpaceInScreen(
+            if (!screensToExclude.contains(screenId) && findNextAvailableIconSpaceInScreen(
                     app, screenItems.get(screenId), coordinates, spanX, spanY)) {
                 // We found a space for it
                 found = true;
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLoaderResults.java
index c202d8d..0e132c2 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLoaderResults.java
@@ -165,25 +165,7 @@
         }
 
         private void bind() {
-            IntSet currentScreenIndices;
-            {
-                // Create an anonymous scope to calculate currentScreen as it has to be a
-                // final variable.
-                IntSet screenIndices = mCallbacks.getPagesToBindSynchronously();
-                if (screenIndices == null || screenIndices.isEmpty()
-                        || screenIndices.getArray().get(screenIndices.size() - 1)
-                        >= mOrderedScreenIds.size()) {
-                    // There maybe no workspace screens (just hotseat items and an empty page).
-                    // Also we want to prevent IndexOutOfBoundsExceptions.
-                    screenIndices = new IntSet();
-                }
-                currentScreenIndices = screenIndices;
-            }
-
-
-            IntSet currentScreenIds  = new IntSet();
-            currentScreenIndices.forEach(
-                    index -> currentScreenIds.add(mOrderedScreenIds.get(index)));
+            IntSet currentScreenIds = mCallbacks.getPagesToBindSynchronously(mOrderedScreenIds);
 
             // Separate the items that are on the current screen, and all the other remaining items
             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
@@ -218,7 +200,7 @@
             Executor pendingExecutor = pendingTasks::add;
             bindWorkspaceItems(otherWorkspaceItems, pendingExecutor);
             bindAppWidgets(otherAppWidgets, pendingExecutor);
-            executeCallbacksTask(c -> c.finishBindingItems(currentScreenIndices), pendingExecutor);
+            executeCallbacksTask(c -> c.finishBindingItems(currentScreenIds), pendingExecutor);
             pendingExecutor.execute(
                     () -> {
                         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
@@ -229,7 +211,7 @@
             executeCallbacksTask(
                     c -> {
                         MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-                        c.onInitialBindComplete(currentScreenIndices, pendingTasks);
+                        c.onInitialBindComplete(currentScreenIds, pendingTasks);
                     }, mUiExecutor);
         }
 
diff --git a/src/com/android/launcher3/model/BgDataModel.java b/src/com/android/launcher3/model/BgDataModel.java
index dd4c3c3..13ad90e 100644
--- a/src/com/android/launcher3/model/BgDataModel.java
+++ b/src/com/android/launcher3/model/BgDataModel.java
@@ -117,11 +117,6 @@
     public int lastBindId = 0;
 
     /**
-     * Value that indicates if left widget panel is shown or not.
-     */
-    public boolean isLeftPanelShown = false;
-
-    /**
      * Clears all the data
      */
     public synchronized void clear() {
@@ -146,14 +141,6 @@
         if (FeatureFlags.QSB_ON_FIRST_SCREEN || screenSet.isEmpty()) {
             screenSet.add(Workspace.FIRST_SCREEN_ID);
         }
-
-        if (isLeftPanelShown) {
-            // We should add it even though there are no items on it.
-            screenSet.add(Workspace.LEFT_PANEL_ID);
-        } else {
-            // We should NOT add it even though there are items on it.
-            screenSet.remove(Workspace.LEFT_PANEL_ID);
-        }
         return screenSet.getArray();
     }
 
@@ -459,10 +446,11 @@
         int FLAG_QUIET_MODE_CHANGE_PERMISSION = 1 << 2;
 
         /**
-         * Returns an IntSet of page numbers to bind first, synchronously if possible
+         * Returns an IntSet of page ids to bind first, synchronously if possible
          * or an empty IntSet
+         * @param orderedScreenIds All the page ids to be bound
          */
-        default IntSet getPagesToBindSynchronously() {
+        default IntSet getPagesToBindSynchronously(IntArray orderedScreenIds) {
             return new IntSet();
         }
 
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 31ca6e7..43f9be5 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -16,6 +16,7 @@
 
 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;
@@ -86,6 +87,7 @@
 import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.IOUtils;
+import com.android.launcher3.util.IntArray;
 import com.android.launcher3.util.IntSet;
 import com.android.launcher3.util.LooperIdleLock;
 import com.android.launcher3.util.PackageManagerHelper;
@@ -175,11 +177,17 @@
     private void sendFirstScreenActiveInstallsBroadcast() {
         ArrayList<ItemInfo> firstScreenItems = new ArrayList<>();
         ArrayList<ItemInfo> allItems = mBgDataModel.getAllWorkspaceItems();
-        // Screen set is never empty
-        final int firstScreen = mBgDataModel.collectWorkspaceScreens().get(0);
-        // TODO(b/185515153): support two panel home.
 
-        filterCurrentWorkspaceItems(IntSet.wrap(firstScreen), allItems, firstScreenItems,
+        // Screen set is never empty
+        IntArray allScreens = mBgDataModel.collectWorkspaceScreens();
+        final int firstScreen = allScreens.get(0);
+
+        IntSet firstScreens = IntSet.wrap(firstScreen);
+        if (firstScreen == LEFT_PANEL_ID && allScreens.size() >= 2) {
+            firstScreens.add(allScreens.get(1));
+        }
+
+        filterCurrentWorkspaceItems(firstScreens, allItems, firstScreenItems,
                 new ArrayList<>() /* otherScreenItems are ignored */);
         mFirstScreenBroadcast.sendBroadcasts(mApp.getContext(), firstScreenItems);
     }
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
index 55edfd4..0439e75 100644
--- a/src/com/android/launcher3/model/ModelWriter.java
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -121,13 +121,6 @@
         }
     }
 
-    /**
-     * Sets the value that indicates if left widget panel is shown or not.
-     */
-    public void setLeftPanelShown(boolean value) {
-        mBgDataModel.isLeftPanelShown = value;
-    }
-
     private void checkItemInfoLocked(int itemId, ItemInfo item, StackTraceElement[] stackTrace) {
         ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
         if (modelItem != null && item != modelItem) {
diff --git a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
index d047eca..816e5dc 100644
--- a/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
+++ b/src/com/android/launcher3/touch/LandscapePagedViewHandler.java
@@ -205,6 +205,16 @@
     }
 
     @Override
+    public void setPrimaryScale(View view, float scale) {
+        view.setScaleY(scale);
+    }
+
+    @Override
+    public void setSecondaryScale(View view, float scale) {
+        view.setScaleX(scale);
+    }
+
+    @Override
     public int getChildStart(View view) {
         return view.getTop();
     }
@@ -353,6 +363,25 @@
     }
 
     @Override
+    public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
+            SplitPositionOption splitPositionOption, Rect out) {
+        // In fake land/seascape, the placeholder always needs to go to the "top" of the device,
+        // which is the same bounds as 0 rotation.
+        int width = dp.widthPx;
+        out.set(0, 0, width, placeholderHeight);
+    }
+
+    @Override
+    public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+            SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
+        // In fake land/seascape, the window bounds are always top and bottom half
+        int screenHeight = dp.heightPx;
+        int screenWidth = dp.widthPx;
+        out1.set(0, 0, screenWidth, screenHeight / 2  - splitDividerSize);
+        out2.set(0, screenHeight / 2  + splitDividerSize, screenWidth, screenHeight);
+    }
+
+    @Override
     public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
             DeviceProfile deviceProfile) {
         return primary;
diff --git a/src/com/android/launcher3/touch/PagedOrientationHandler.java b/src/com/android/launcher3/touch/PagedOrientationHandler.java
index 266e05f..dae2dde 100644
--- a/src/com/android/launcher3/touch/PagedOrientationHandler.java
+++ b/src/com/android/launcher3/touch/PagedOrientationHandler.java
@@ -99,6 +99,8 @@
     boolean getRecentsRtlSetting(Resources resources);
     float getDegreesRotated();
     int getRotation();
+    void setPrimaryScale(View view, float scale);
+    void setSecondaryScale(View view, float scale);
 
     <T> T getPrimaryValue(T x, T y);
     <T> T getSecondaryValue(T x, T y);
@@ -114,6 +116,22 @@
             DeviceProfile deviceProfile);
     int getDistanceToBottomOfRect(DeviceProfile dp, Rect rect);
     List<SplitPositionOption> getSplitPositionOptions(DeviceProfile dp);
+    /**
+     * @param splitholderSize height of placeholder view in portrait, width in landscape
+     */
+    void getInitialSplitPlaceholderBounds(int splitholderSize, DeviceProfile dp,
+            SplitPositionOption splitPositionOption, Rect out);
+
+    /**
+     * @param splitDividerSize height of split screen drag handle in portrait, width in landscape
+     * @param initialSplitOption the split position option (top/left, bottom/right) of the first
+     *                           task selected for entering split
+     * @param out1 the bounds for where the first selected app will be
+     * @param out2 the bounds for where the second selected app will be, complimentary to
+     *             {@param out1} based on {@param initialSplitOption}
+     */
+    void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+            SplitPositionOption initialSplitOption, Rect out1, Rect out2);
 
     // Overview TaskMenuView methods
     float getTaskMenuX(float x, View thumbnailView, int overScroll);
diff --git a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
index dd97af5..1253589 100644
--- a/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
+++ b/src/com/android/launcher3/touch/PortraitPagedViewHandler.java
@@ -24,6 +24,7 @@
 import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
 
 import android.content.res.Resources;
+import android.graphics.Matrix;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
@@ -47,6 +48,9 @@
 
 public class PortraitPagedViewHandler implements PagedOrientationHandler {
 
+    private final Matrix mTmpMatrix = new Matrix();
+    private final RectF mTmpRectF = new RectF();
+
     @Override
     public <T> T getPrimaryValue(T x, T y) {
         return x;
@@ -207,6 +211,16 @@
     }
 
     @Override
+    public void setPrimaryScale(View view, float scale) {
+        view.setScaleX(scale);
+    }
+
+    @Override
+    public void setSecondaryScale(View view, float scale) {
+        view.setScaleY(scale);
+    }
+
+    @Override
     public int getChildStart(View view) {
         return view.getLeft();
     }
@@ -398,6 +412,62 @@
     }
 
     @Override
+    public void getInitialSplitPlaceholderBounds(int placeholderHeight, DeviceProfile dp,
+            SplitPositionOption splitPositionOption, Rect out) {
+        int width = dp.widthPx;
+        out.set(0, 0, width, placeholderHeight);
+        if (!dp.isLandscape) {
+            // portrait, phone or tablet - spans width of screen, nothing else to do
+            return;
+        }
+
+        // Now we rotate the portrait rect depending on what side we want pinned
+        boolean pinToRight = splitPositionOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
+
+        int screenHeight = dp.heightPx;
+        float postRotateScale = (float) screenHeight / width;
+        mTmpMatrix.reset();
+        mTmpMatrix.postRotate(pinToRight ? 90 : 270);
+        mTmpMatrix.postTranslate(pinToRight ? width : 0, pinToRight ? 0 : width);
+        // The placeholder height stays constant after rotation, so we don't change width scale
+        mTmpMatrix.postScale(1, postRotateScale);
+
+        mTmpRectF.set(out);
+        mTmpMatrix.mapRect(mTmpRectF);
+        mTmpRectF.roundOut(out);
+    }
+
+    @Override
+    public void getFinalSplitPlaceholderBounds(int splitDividerSize, DeviceProfile dp,
+            SplitPositionOption initialSplitOption, Rect out1, Rect out2) {
+        int screenHeight = dp.heightPx;
+        int screenWidth = dp.widthPx;
+        out1.set(0, 0, screenWidth, screenHeight / 2 - splitDividerSize);
+        out2.set(0, screenHeight / 2 + splitDividerSize, screenWidth, screenHeight);
+        if (!dp.isLandscape) {
+            // Portrait - the window bounds are always top and bottom half
+            return;
+        }
+
+        // Now we rotate the portrait rect depending on what side we want pinned
+        boolean pinToRight = initialSplitOption.mStagePosition == STAGE_POSITION_BOTTOM_OR_RIGHT;
+        float postRotateScale = (float) screenHeight / screenWidth;
+
+        mTmpMatrix.reset();
+        mTmpMatrix.postRotate(pinToRight ? 90 : 270);
+        mTmpMatrix.postTranslate(pinToRight ? screenHeight : 0, pinToRight ? 0 : screenWidth);
+        mTmpMatrix.postScale(1 / postRotateScale, postRotateScale);
+
+        mTmpRectF.set(out1);
+        mTmpMatrix.mapRect(mTmpRectF);
+        mTmpRectF.roundOut(out1);
+
+        mTmpRectF.set(out2);
+        mTmpMatrix.mapRect(mTmpRectF);
+        mTmpRectF.roundOut(out2);
+    }
+
+    @Override
     public FloatProperty getSplitSelectTaskOffset(FloatProperty primary, FloatProperty secondary,
             DeviceProfile dp) {
         if (dp.isLandscape) { // or seascape
diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
index 5e1a534..e4ce27c 100644
--- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
+++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java
@@ -15,6 +15,8 @@
  */
 package com.android.launcher3.widget.picker;
 
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
 import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
@@ -520,7 +522,12 @@
                                 mNoWidgetsView.getText().length(), noWidgetsViewTextBounds);
                 noWidgetsViewHeight = noWidgetsViewTextBounds.height();
             }
-            float maxTableHeight = (mActivityContext.getDeviceProfile().availableHeightPx
+            doMeasure(
+                    makeMeasureSpec(mActivityContext.getDeviceProfile().availableWidthPx,
+                            MeasureSpec.EXACTLY),
+                    makeMeasureSpec(mActivityContext.getDeviceProfile().availableHeightPx,
+                            MeasureSpec.EXACTLY));
+            float maxTableHeight = (mContent.getMeasuredHeight()
                     - mTabsHeight - mViewPagerTopPadding - getHeaderViewHeight()
                     - noWidgetsViewHeight) * RECOMMENDATION_TABLE_HEIGHT_RATIO;
 
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index dc59bdd..5fbf847 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -61,13 +61,23 @@
             out.putNextEntry(new ZipEntry("visible_windows.zip"));
             dumpCommand("cmd window dump-visible-window-views", out);
             out.closeEntry();
-        } catch (IOException ex) { }
+        } catch (IOException ex) {
+        }
 
         Log.e(TAG, "Failed test " + description.getMethodName()
                 + ",\nscreenshot will be saved to " + sceenshot
                 + ",\nUI dump at: " + hierarchy
                 + " (use go/web-hv to open the dump file)", e);
         device.takeScreenshot(sceenshot);
+
+        // Dump accessibility hierarchy
+        final File accessibilityHierarchyFile = new File(parentFile,
+                "AccessibilityHierarchy-" + description.getMethodName() + ".uix");
+        try {
+            device.dumpWindowHierarchy(accessibilityHierarchyFile);
+        } catch (IOException ex) {
+            Log.e(TAG, "Failed to save accessibility hierarchy", ex);
+        }
     }
 
     private static void dumpStringCommand(String cmd, OutputStream out) throws IOException {
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index a15131d..7ec5208 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -56,24 +56,29 @@
     protected abstract String launchableType();
 
     private Background launch(BySelector selector) {
-        LauncherInstrumentation.log("Launchable.launch before click "
-                + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
-        final String label = mObject.getText();
+        try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                "want to launch an app from " + launchableType())) {
+            LauncherInstrumentation.log("Launchable.launch before click "
+                    + mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(mObject));
+            final String label = mObject.getText();
 
-        mLauncher.executeAndWaitForEvent(
-                () -> {
-                    mLauncher.clickLauncherObject(mObject);
-                    expectActivityStartEvents();
-                },
-                event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
-                () -> "Launching an app didn't open a new window: " + label,
-                "clicking " + launchableType());
+            mLauncher.executeAndWaitForEvent(
+                    () -> {
+                        mLauncher.clickLauncherObject(mObject);
+                        expectActivityStartEvents();
+                    },
+                    event -> event.getEventType() == TYPE_WINDOW_STATE_CHANGED,
+                    () -> "Launching an app didn't open a new window: " + label,
+                    "clicking " + launchableType());
 
-        mLauncher.assertTrue(
-                "App didn't start: " + label + " (" + selector + ")",
-                TestHelpers.wait(Until.hasObject(selector),
-                        LauncherInstrumentation.WAIT_TIME_MS));
-        return new Background(mLauncher);
+            try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer("clicked")) {
+                mLauncher.assertTrue(
+                        "App didn't start: " + label + " (" + selector + ")",
+                        TestHelpers.wait(Until.hasObject(selector),
+                                LauncherInstrumentation.WAIT_TIME_MS));
+                return new Background(mLauncher);
+            }
+        }
     }
 
     /**
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 1f64131..5cd03e4 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -406,12 +406,15 @@
     }
 
     private String getVisiblePackages() {
-        return mDevice.findObjects(getAnyObjectSelector())
+        final String apps = mDevice.findObjects(getAnyObjectSelector())
                 .stream()
                 .map(LauncherInstrumentation::getApplicationPackageSafe)
                 .distinct()
-                .filter(pkg -> pkg != null && !"com.android.systemui".equals(pkg))
+                .filter(pkg -> pkg != null && !SYSTEMUI_PACKAGE.equals(pkg))
                 .collect(Collectors.joining(", "));
+        return !apps.isEmpty()
+                ? "active app: " + apps
+                : "the test doesn't see views from any app, including Launcher";
     }
 
     private static String getApplicationPackageSafe(UiObject2 object) {