Merge "Align OverviewActionsView for 3 button taskbar" into sc-v2-dev
diff --git a/quickstep/res/drawable/ic_screenshot.xml b/quickstep/res/drawable/ic_screenshot.xml
index d97eae1..9ee6c44 100644
--- a/quickstep/res/drawable/ic_screenshot.xml
+++ b/quickstep/res/drawable/ic_screenshot.xml
@@ -13,11 +13,20 @@
      limitations under the License.
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24">
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="20"
+    android:viewportHeight="20">
   <path
-      android:fillColor="#FF000000"
-      android:pathData="M17,1.01L7,1c-1.1,0 -2,0.9 -2,2v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,21L7,21v-1h10v1zM17,18L7,18L7,6h10v12zM17,4L7,4L7,3h10v1zM9.5,8.5L12,8.5L12,7L8,7v4h1.5zM12,17h4v-4h-1.5v2.5L12,15.5z"/>
-</vector>
+      android:pathData="M5.8334,1.666H8.3334V3.3327H5.8334V6.666H4.1667V3.3327C4.1667,2.4122 4.9129,1.666 5.8334,1.666Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M4.1667,13.3327V16.666C4.1667,17.5865 4.9129,18.3327 5.8334,18.3327H8.3334V16.666H5.8334V13.3327H4.1667Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M14.1667,13.3327V16.666H11.6667V18.3327H14.1667C15.0872,18.3327 15.8334,17.5865 15.8334,16.666V13.3327H14.1667Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M15.8334,6.666V3.3327C15.8334,2.4122 15.0872,1.666 14.1667,1.666H11.6667V3.3327H14.1667V6.666H15.8334Z"
+      android:fillColor="#000000"/>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/layout/overview_actions_container.xml b/quickstep/res/layout/overview_actions_container.xml
index 1c7b509..0fda0bf 100644
--- a/quickstep/res/layout/overview_actions_container.xml
+++ b/quickstep/res/layout/overview_actions_container.xml
@@ -38,13 +38,13 @@
             android:layout_height="wrap_content"
             android:drawableStart="@drawable/ic_screenshot"
             android:text="@string/action_screenshot"
-            android:theme="@style/ThemeControlHighlightWorkspaceColor"
-            android:visibility="gone" />
+            android:theme="@style/ThemeControlHighlightWorkspaceColor" />
 
         <Space
-            android:layout_width="0dp"
+            android:id="@+id/action_split_space"
+            android:layout_width="@dimen/overview_actions_button_spacing"
             android:layout_height="1dp"
-            android:layout_weight="1" />
+            android:visibility="gone" />
 
         <Button
             android:id="@+id/action_split"
@@ -56,11 +56,9 @@
             android:visibility="gone" />
 
         <Space
-            android:id="@+id/action_split_space"
             android:layout_width="0dp"
             android:layout_height="1dp"
-            android:layout_weight="1"
-            android:visibility="gone" />
+            android:layout_weight="1" />
 
         <Space
             android:id="@+id/oav_three_button_space"
diff --git a/quickstep/res/values-sw720dp/dimens.xml b/quickstep/res/values-sw720dp/dimens.xml
new file mode 100644
index 0000000..2831a6f
--- /dev/null
+++ b/quickstep/res/values-sw720dp/dimens.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+<resources>
+    <dimen name="overview_grid_row_spacing">44dp</dimen>
+    <dimen name="overview_page_spacing_grid_portrait">44dp</dimen>
+    <dimen name="overview_page_spacing_grid_landscape">44dp</dimen>
+</resources>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 4e6b7b9..3b2d44b 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -36,9 +36,12 @@
     <dimen name="overview_task_margin">16dp</dimen>
     <dimen name="overview_task_margin_focused">12dp</dimen>
     <dimen name="overview_task_margin_grid">4dp</dimen>
+    <item name="overview_max_scale" format="float" type="dimen">0.7</item>
 
     <!-- Overrideable in overlay that provides the Overview Actions. -->
     <dimen name="overview_actions_height">48dp</dimen>
+    <dimen name="overview_actions_button_spacing">32dp</dimen>
+    <dimen name="overview_actions_button_spacing_grid">36dp</dimen>
     <dimen name="overview_actions_margin_gesture">28dp</dimen>
     <dimen name="overview_actions_top_margin_gesture_grid_portrait">19.37dp</dimen>
     <dimen name="overview_actions_bottom_margin_gesture_grid_portrait">22dp</dimen>
@@ -49,9 +52,10 @@
 
     <dimen name="overview_grid_side_margin_portrait">60dp</dimen>
     <dimen name="overview_grid_side_margin_landscape">52dp</dimen>
-    <dimen name="overview_grid_row_spacing_portrait">17.13dp</dimen>
-    <dimen name="overview_grid_row_spacing_landscape">13.38dp</dimen>
-    <dimen name="overview_grid_focus_vertical_margin">0dp</dimen>
+    <dimen name="overview_grid_row_spacing">36dp</dimen>
+    <dimen name="overview_page_spacing">16dp</dimen>
+    <dimen name="overview_page_spacing_grid_portrait">36dp</dimen>
+    <dimen name="overview_page_spacing_grid_landscape">38dp</dimen>
 
     <!-- These speeds are in dp/s -->
     <dimen name="max_task_dismiss_drag_velocity">2.25dp</dimen>
@@ -59,8 +63,6 @@
     <dimen name="default_task_dismiss_drag_velocity_grid">1dp</dimen>
     <dimen name="default_task_dismiss_drag_velocity_grid_focus_task">5dp</dimen>
 
-    <dimen name="recents_page_spacing">16dp</dimen>
-    <dimen name="recents_page_spacing_grid">36dp</dimen>
     <dimen name="recents_clear_all_deadzone_vertical_margin">70dp</dimen>
 
     <!-- The speed in dp/s at which the user needs to be scrolling in recents such that we start
diff --git a/quickstep/res/values/strings.xml b/quickstep/res/values/strings.xml
index 151b8e4..a4bbae0 100644
--- a/quickstep/res/values/strings.xml
+++ b/quickstep/res/values/strings.xml
@@ -218,10 +218,10 @@
     <string name="taskbar_edu_closed">Taskbar education closed</string>
     <!-- Text in dialog that lets a user know how they can use the taskbar to switch apps on their device.
          [CHAR_LIMIT=60] -->
-    <string name="taskbar_edu_switch_apps" translatable="false">Use the taskbar to switch apps</string>
+    <string name="taskbar_edu_switch_apps">Use the taskbar to switch apps</string>
     <!-- Text in dialog that lets a user know how they can use the taskbar to use multiple apps at once on their device.
          [CHAR_LIMIT=60] -->
-    <string name="taskbar_edu_splitscreen" translatable="false">Drag to the side to use two apps at once</string>
+    <string name="taskbar_edu_splitscreen">Drag to the side to use two apps at once</string>
     <!-- Text in dialog that lets a user know how they can hide the taskbar on their device.
          [CHAR_LIMIT=60] -->
     <string name="taskbar_edu_stashing">Touch &amp; hold to hide the taskbar</string>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 51d7914..0123c4f 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1214,14 +1214,14 @@
     }
 
     /**
-     * Returns view on the workspace that corresponds to the closing app in the list of app targets
+     * Returns view on launcher that corresponds to the closing app in the list of app targets
      */
-    private @Nullable View findWorkspaceView(RemoteAnimationTargetCompat[] appTargets) {
+    private @Nullable View findLauncherView(RemoteAnimationTargetCompat[] appTargets) {
         for (RemoteAnimationTargetCompat appTarget : appTargets) {
             if (appTarget.mode == MODE_CLOSING) {
-                View workspaceView = findWorkspaceView(appTarget);
-                if (workspaceView != null) {
-                    return workspaceView;
+                View launcherView = findLauncherView(appTarget);
+                if (launcherView != null) {
+                    return launcherView;
                 }
             }
         }
@@ -1229,9 +1229,9 @@
     }
 
     /**
-     * Returns view on the workspace that corresponds to the {@param runningTaskTarget}.
+     * Returns view on launcher that corresponds to the {@param runningTaskTarget}.
      */
-    private @Nullable View findWorkspaceView(RemoteAnimationTargetCompat runningTaskTarget) {
+    private @Nullable View findLauncherView(RemoteAnimationTargetCompat runningTaskTarget) {
         if (runningTaskTarget == null || runningTaskTarget.taskInfo == null) {
             return null;
         }
@@ -1269,7 +1269,7 @@
             }
         }
 
-        return mLauncher.getWorkspace().getFirstMatchForAppClose(launchCookieItemId,
+        return mLauncher.getFirstMatchForAppClose(launchCookieItemId,
                 packageName, UserHandle.of(runningTaskTarget.taskInfo.userId));
     }
 
@@ -1292,7 +1292,7 @@
      * Closing animator that animates the window into its final location on the workspace.
      */
     private void getClosingWindowAnimators(AnimatorSet animation,
-            RemoteAnimationTargetCompat[] targets, View workspaceView) {
+            RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS) {
         FloatingIconView floatingIconView = null;
         FloatingWidgetView floatingWidget = null;
         RectF targetRect = new RectF();
@@ -1308,17 +1308,17 @@
         }
 
         // Get floating view and target rect.
-        if (workspaceView instanceof LauncherAppWidgetHostView) {
+        if (launcherView instanceof LauncherAppWidgetHostView) {
             Size windowSize = new Size(mDeviceProfile.availableWidthPx,
                     mDeviceProfile.availableHeightPx);
             int fallbackBackgroundColor =
                     FloatingWidgetView.getDefaultBackgroundColor(mLauncher, runningTaskTarget);
             floatingWidget = FloatingWidgetView.getFloatingWidgetView(mLauncher,
-                    (LauncherAppWidgetHostView) workspaceView, targetRect, windowSize,
+                    (LauncherAppWidgetHostView) launcherView, targetRect, windowSize,
                     mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher),
                     isTransluscent, fallbackBackgroundColor);
-        } else if (workspaceView != null) {
-            floatingIconView = getFloatingIconView(mLauncher, workspaceView,
+        } else if (launcherView != null) {
+            floatingIconView = getFloatingIconView(mLauncher, launcherView,
                     true /* hideOriginal */, targetRect, false /* isOpening */);
         } else {
             targetRect.set(getDefaultWindowTargetRect());
@@ -1373,15 +1373,10 @@
         }
 
         // Use a fixed velocity to start the animation.
-        float velocityPxPerS = DynamicResource.provider(mLauncher)
-                .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
-        PointF velocity = new PointF(0, -velocityPxPerS);
-        animation.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
-                true /* animateOverviewScrim */, workspaceView).getAnimators());
         animation.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(Animator animation) {
-                anim.start(mLauncher, velocity);
+                anim.start(mLauncher, velocityPxPerS);
             }
         });
     }
@@ -1556,22 +1551,30 @@
             if (anim == null) {
                 anim = new AnimatorSet();
 
-                View workspaceView = findWorkspaceView(appTargets);
-                boolean isWorkspaceViewVisible = workspaceView != null
-                        && !mLauncher.isInState(LauncherState.ALL_APPS)
-                        && !mLauncher.getWorkspace().isOverlayShown();
-                boolean playFallBackAnimation = !isWorkspaceViewVisible
-                        && (launcherIsATargetWithMode(appTargets, MODE_OPENING)
-                        || mLauncher.isForceInvisible());
+                final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
+                        || launcherIsATargetWithMode(appTargets, MODE_OPENING);
+
+                View launcherView = findLauncherView(appTargets);
+                boolean playFallBackAnimation = (launcherView == null
+                        && launcherIsForceInvisibleOrOpening)
+                        || mLauncher.getWorkspace().isOverlayShown();
 
                 boolean playWorkspaceReveal = true;
                 if (mFromUnlock) {
                     anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
                 } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
                         && !playFallBackAnimation) {
-                    getClosingWindowAnimators(anim, appTargets, workspaceView);
-                    // We play StaggeredWorkspaceAnim as a part of the closing window animation.
-                    playWorkspaceReveal = false;
+                    // Use a fixed velocity to start the animation.
+                    float velocityPxPerS = DynamicResource.provider(mLauncher)
+                            .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
+                    PointF velocity = new PointF(0, -velocityPxPerS);
+                    getClosingWindowAnimators(anim, appTargets, launcherView, velocity);
+                    if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+                        anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
+                                true /* animateOverviewScrim */, launcherView).getAnimators());
+                        // We play StaggeredWorkspaceAnim as a part of the closing window animation.
+                        playWorkspaceReveal = false;
+                    }
                 } else {
                     anim.play(getFallbackClosingWindowAnimators(appTargets));
                 }
@@ -1584,8 +1587,7 @@
                 // targets list because it is already visible). In that case, we force
                 // invisibility on touch down, and only reset it after the animation to home
                 // is initialized.
-                if (launcherIsATargetWithMode(appTargets, MODE_OPENING)
-                        || mLauncher.isForceInvisible()) {
+                if (launcherIsForceInvisibleOrOpening) {
                     addCujInstrumentation(
                             anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
                     // Only register the content animation for cancellation when state changes
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index f3c8cf3..b2c29b2 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -195,7 +195,7 @@
         if (hasAnyFlag(changedFlags, FLAG_RESUMED)) {
             boolean isResumed = isResumed();
             ObjectAnimator anim = mIconAlignmentForResumedState
-                    .animateToValue(getCurrentIconAlignmentRatio(), isResumed ? 1 : 0)
+                    .animateToValue(isResumed ? 1 : 0)
                     .setDuration(duration);
 
             anim.addListener(new AnimatorListenerAdapter() {
@@ -351,9 +351,16 @@
 
         private void endGestureStateOverride(boolean finishedToApp) {
             mCallbacks.removeListener(this);
+
+            // Update the resumed state immediately to ensure a seamless handoff
+            boolean launcherResumed = !finishedToApp;
+            mIconAlignmentForResumedState.updateValue(launcherResumed ? 1 : 0);
+
             updateStateForFlag(FLAG_RECENTS_ANIMATION_RUNNING, false);
+            updateStateForFlag(FLAG_RESUMED, launcherResumed);
             applyState();
 
+
             TaskbarStashController controller = mControllers.taskbarStashController;
             controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
             controller.applyState();
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 4984b95..b733007 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -74,8 +74,7 @@
         return super.getVisibleElements(launcher)
                 & ~OVERVIEW_ACTIONS
                 & ~CLEAR_ALL_BUTTON
-                & ~VERTICAL_SWIPE_INDICATOR
-                | TASKBAR;
+                & ~VERTICAL_SWIPE_INDICATOR;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index d36e76b..edfb921 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -53,6 +53,6 @@
 
     @Override
     public int getVisibleElements(Launcher launcher) {
-        return TASKBAR;
+        return NONE;
     }
 }
diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
index a566765..1d4ed4c 100644
--- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java
+++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java
@@ -221,6 +221,7 @@
 
             PointF taskDimension = getTaskDimension(context, dp);
             float scale = gridRect.height() / taskDimension.y;
+            scale = Math.min(scale, res.getFloat(R.dimen.overview_max_scale));
             int outWidth = Math.round(scale * taskDimension.x);
             int outHeight = Math.round(scale * taskDimension.y);
 
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index 5541a46..a72935b 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -244,7 +244,7 @@
             }
         }
 
-        return mActivity.getWorkspace().getFirstMatchForAppClose(launchCookieItemId,
+        return mActivity.getFirstMatchForAppClose(launchCookieItemId,
                 runningTaskView.getTask().key.getComponent().getPackageName(),
                 UserHandle.of(runningTaskView.getTask().key.userId));
     }
diff --git a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
index cbbe3eb..fcc6272 100644
--- a/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
+++ b/quickstep/src/com/android/quickstep/views/OverviewActionsView.java
@@ -24,8 +24,10 @@
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.View.OnClickListener;
+import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.FrameLayout;
+import android.widget.LinearLayout;
 
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
@@ -247,6 +249,13 @@
     public void setDp(DeviceProfile dp) {
         mDp = dp;
         updateVerticalMargin(SysUINavigationMode.getMode(getContext()));
+
+        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                dp.isVerticalBarLayout() ? 0 : dp.overviewActionsButtonSpacing,
+                ViewGroup.LayoutParams.MATCH_PARENT);
+        params.weight = dp.isVerticalBarLayout() ? 1 : 0;
+        findViewById(R.id.action_split_space).setLayoutParams(params);
+
         requestLayout();
 
         mSplitButton.setCompoundDrawablesWithIntrinsicBounds(
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 93be6fb..7c503be 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -324,14 +324,14 @@
         if (!mLauncher.isTablet()) {
             return;
         }
-        for (int i = 2; i <= 12; i++) {
+        for (int i = 2; i <= 14; i++) {
             startTestActivity(i);
         }
 
         Overview overview = mLauncher.pressHome().switchToOverview();
         executeOnLauncher(
-                launcher -> assertTrue("Don't have at least 11 tasks",
-                        getTaskCount(launcher) >= 11));
+                launcher -> assertTrue("Don't have at least 13 tasks",
+                        getTaskCount(launcher) >= 13));
 
         // Test scroll the first task off screen
         overview.scrollCurrentTaskOffScreen();
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2dfd5cc..6d223e7 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -332,6 +332,8 @@
     <dimen name="overview_task_margin">0dp</dimen>
     <dimen name="overview_task_margin_focused">0dp</dimen>
     <dimen name="overview_task_margin_grid">0dp</dimen>
+    <dimen name="overview_actions_button_spacing">0dp</dimen>
+    <dimen name="overview_actions_button_spacing_grid">0dp</dimen>
     <dimen name="overview_actions_margin_gesture">0dp</dimen>
     <dimen name="overview_actions_top_margin_gesture_grid_portrait">0dp</dimen>
     <dimen name="overview_actions_bottom_margin_gesture_grid_portrait">0dp</dimen>
@@ -340,10 +342,10 @@
     <dimen name="overview_actions_margin_three_button">0dp</dimen>
     <dimen name="overview_grid_side_margin_portrait">0dp</dimen>
     <dimen name="overview_grid_side_margin_landscape">0dp</dimen>
-    <dimen name="overview_grid_row_spacing_portrait">0dp</dimen>
-    <dimen name="overview_grid_row_spacing_landscape">0dp</dimen>
-    <dimen name="recents_page_spacing">0dp</dimen>
-    <dimen name="recents_page_spacing_grid">0dp</dimen>
+    <dimen name="overview_grid_row_spacing">0dp</dimen>
+    <dimen name="overview_page_spacing">0dp</dimen>
+    <dimen name="overview_page_spacing_grid_portrait">0dp</dimen>
+    <dimen name="overview_page_spacing_grid_landscape">0dp</dimen>
     <dimen name="split_placeholder_size">110dp</dimen>
     <dimen name="task_menu_width_grid">200dp</dimen>
 
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index ff19918..6a399ac 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -187,6 +187,7 @@
     public final int overviewActionsMarginThreeButtonPx;
     public final int overviewActionsTopMarginGesturePx;
     public final int overviewActionsBottomMarginGesturePx;
+    public final int overviewActionsButtonSpacing;
     public int overviewPageSpacing;
     public int overviewRowSpacing;
     public int overviewGridSideMargin;
@@ -371,25 +372,29 @@
                         R.dimen.overview_actions_top_margin_gesture_grid_landscape);
                 overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
                         R.dimen.overview_actions_bottom_margin_gesture_grid_landscape);
+                overviewPageSpacing = res.getDimensionPixelSize(
+                        R.dimen.overview_page_spacing_grid_landscape);
             } else {
                 overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
                         R.dimen.overview_actions_top_margin_gesture_grid_portrait);
                 overviewActionsBottomMarginGesturePx = res.getDimensionPixelSize(
                         R.dimen.overview_actions_bottom_margin_gesture_grid_portrait);
+                overviewPageSpacing = res.getDimensionPixelSize(
+                        R.dimen.overview_page_spacing_grid_portrait);
             }
+            overviewActionsButtonSpacing = res.getDimensionPixelSize(
+                    R.dimen.overview_actions_button_spacing_grid);
         } else {
             overviewActionsTopMarginGesturePx = res.getDimensionPixelSize(
                     R.dimen.overview_actions_margin_gesture);
             overviewActionsBottomMarginGesturePx = overviewActionsTopMarginGesturePx;
+            overviewActionsButtonSpacing = res.getDimensionPixelSize(
+                    R.dimen.overview_actions_button_spacing);
+            overviewPageSpacing = res.getDimensionPixelSize(R.dimen.overview_page_spacing);
         }
         overviewActionsMarginThreeButtonPx = res.getDimensionPixelSize(
                 R.dimen.overview_actions_margin_three_button);
-        overviewPageSpacing = overviewShowAsGrid
-                ? res.getDimensionPixelSize(R.dimen.recents_page_spacing_grid)
-                : res.getDimensionPixelSize(R.dimen.recents_page_spacing);
-        overviewRowSpacing = isLandscape
-                ? res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing_landscape)
-                : res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing_portrait);
+        overviewRowSpacing = res.getDimensionPixelSize(R.dimen.overview_grid_row_spacing);
         overviewGridSideMargin = isLandscape
                 ? res.getDimensionPixelSize(R.dimen.overview_grid_side_margin_landscape)
                 : res.getDimensionPixelSize(R.dimen.overview_grid_side_margin_portrait);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 8154168..d60cd31 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -29,6 +29,7 @@
 import static com.android.launcher3.AbstractFloatingView.TYPE_SNACKBAR;
 import static com.android.launcher3.AbstractFloatingView.getTopOpenViewWithType;
 import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
 import static com.android.launcher3.LauncherState.ALL_APPS;
 import static com.android.launcher3.LauncherState.FLAG_CLOSE_POPUPS;
 import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
@@ -39,6 +40,7 @@
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.Utilities.postAsyncCallback;
 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
+import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_LAUNCHER_LOAD;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
@@ -55,6 +57,7 @@
 import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
 import static com.android.launcher3.states.RotationHelper.REQUEST_LOCK;
 import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
+import static com.android.launcher3.util.ItemInfoMatcher.forFolderMatch;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -90,6 +93,7 @@
 import android.os.StrictMode;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.util.Log;
@@ -215,6 +219,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -2674,6 +2679,79 @@
         }
     }
 
+    /**
+     * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
+     * animation.
+     *
+     * @param preferredItemId The id of the preferred item to match to if it exists.
+     * @param packageName The package name of the app to match.
+     * @param user The user of the app to match.
+     */
+    public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) {
+        final ItemInfoMatcher preferredItem = (info, cn) ->
+                info != null && info.id == preferredItemId;
+        final ItemInfoMatcher packageAndUserAndApp = (info, cn) ->
+                info != null
+                        && info.itemType == ITEM_TYPE_APPLICATION
+                        && info.user.equals(user)
+                        && info.getTargetComponent() != null
+                        && TextUtils.equals(info.getTargetComponent().getPackageName(),
+                        packageName);
+
+        if (isInState(LauncherState.ALL_APPS)) {
+            return getFirstMatch(Collections.singletonList(mAppsView.getActiveRecyclerView()),
+                    preferredItem, packageAndUserAndApp);
+        } else {
+            List<ViewGroup> containers = new ArrayList<>(mWorkspace.getPanelCount() + 1);
+            containers.add(mWorkspace.getHotseat().getShortcutsAndWidgets());
+            mWorkspace.forEachVisiblePage(page
+                    -> containers.add(((CellLayout) page).getShortcutsAndWidgets()));
+
+            // Order: Preferred item by itself or in folder, then by matching package/user
+            if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
+                return getFirstMatch(containers, preferredItem, forFolderMatch(preferredItem),
+                        packageAndUserAndApp, forFolderMatch(packageAndUserAndApp));
+            } else {
+                // Do not use Folder as a criteria, since it'll cause a crash when trying to draw
+                // FolderAdaptiveIcon as the background.
+                return getFirstMatch(containers, preferredItem, packageAndUserAndApp);
+            }
+        }
+    }
+
+    /**
+     * Finds the first view matching the ordered operators across the given viewgroups in order.
+     * @param containers List of ViewGroups to scan, in order of preference.
+     * @param operators List of operators, in order starting from best matching operator.
+     */
+    private static View getFirstMatch(Iterable<ViewGroup> containers,
+            final ItemInfoMatcher... operators) {
+        for (ItemInfoMatcher operator : operators) {
+            for (ViewGroup container : containers) {
+                View match = mapOverViewGroup(container, operator);
+                if (match != null) {
+                    return match;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the first view matching the operator in the given ViewGroups, or null if none.
+     * Forward iteration matters.
+     */
+    private static View mapOverViewGroup(ViewGroup container, ItemInfoMatcher op) {
+        final int itemCount = container.getChildCount();
+        for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
+            View item = container.getChildAt(itemIdx);
+            if (op.matchesInfo((ItemInfo) item.getTag())) {
+                return item;
+            }
+        }
+        return null;
+    }
+
     private ValueAnimator createNewAppBounceAnimation(View v, int i) {
         ValueAnimator bounceAnim = new PropertyListBuilder().alpha(1).scale(1).build(v)
                 .setDuration(ItemInstallQueue.NEW_SHORTCUT_BOUNCE_DURATION);
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 15378e0..9324ea5 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -57,10 +57,9 @@
     public static final int ALL_APPS_CONTENT = 1 << 1;
     public static final int VERTICAL_SWIPE_INDICATOR = 1 << 2;
     public static final int OVERVIEW_ACTIONS = 1 << 3;
-    public static final int TASKBAR = 1 << 4;
-    public static final int CLEAR_ALL_BUTTON = 1 << 5;
-    public static final int WORKSPACE_PAGE_INDICATOR = 1 << 6;
-    public static final int SPLIT_PLACHOLDER_VIEW = 1 << 7;
+    public static final int CLEAR_ALL_BUTTON = 1 << 4;
+    public static final int WORKSPACE_PAGE_INDICATOR = 1 << 5;
+    public static final int SPLIT_PLACHOLDER_VIEW = 1 << 6;
 
     // Flag indicating workspace has multiple pages visible.
     public static final int FLAG_MULTI_PAGE = BaseState.getFlag(0);
@@ -186,7 +185,7 @@
     }
 
     public int getVisibleElements(Launcher launcher) {
-        return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR | TASKBAR;
+        return HOTSEAT_ICONS | WORKSPACE_PAGE_INDICATOR | VERTICAL_SWIPE_INDICATOR;
     }
 
     /**
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 203df0a..281dfea 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -26,7 +26,6 @@
 import static com.android.launcher3.LauncherState.NORMAL;
 import static com.android.launcher3.LauncherState.SPRING_LOADED;
 import static com.android.launcher3.anim.AnimatorListeners.forSuccessCallback;
-import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM;
 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_OVERLAY;
 import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_HOME;
 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SWIPELEFT;
@@ -51,7 +50,6 @@
 import android.os.Message;
 import android.os.Parcelable;
 import android.os.UserHandle;
-import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -3146,62 +3144,6 @@
         return layouts;
     }
 
-    /**
-     * Similar to {@link #getFirstMatch} but optimized to finding a suitable view for the app close
-     * animation.
-     *
-     * @param preferredItemId The id of the preferred item to match to if it exists.
-     * @param packageName The package name of the app to match.
-     * @param user The user of the app to match.
-     */
-    public View getFirstMatchForAppClose(int preferredItemId, String packageName, UserHandle user) {
-        final ItemOperator preferredItem = (ItemInfo info, View view) ->
-                info != null && info.id == preferredItemId;
-        final ItemOperator preferredItemInFolder = (info, view) -> {
-            if (info instanceof FolderInfo) {
-                FolderInfo folderInfo = (FolderInfo) info;
-                for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
-                    if (preferredItem.evaluate(shortcutInfo, view)) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        };
-        final ItemOperator packageAndUserAndApp = (ItemInfo info, View view) ->
-                info != null
-                        && info.itemType == ITEM_TYPE_APPLICATION
-                        && info.user.equals(user)
-                        && info.getTargetComponent() != null
-                        && TextUtils.equals(info.getTargetComponent().getPackageName(),
-                                packageName);
-        final ItemOperator packageAndUserAndAppInFolder = (info, view) -> {
-            if (info instanceof FolderInfo) {
-                FolderInfo folderInfo = (FolderInfo) info;
-                for (WorkspaceItemInfo shortcutInfo : folderInfo.contents) {
-                    if (packageAndUserAndApp.evaluate(shortcutInfo, view)) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        };
-
-        List<CellLayout> cellLayouts = new ArrayList<>(getPanelCount() + 1);
-        cellLayouts.add(getHotseat());
-        forEachVisiblePage(page -> cellLayouts.add((CellLayout) page));
-
-        // Order: Preferred item, App icons in hotseat/workspace, app in folder in hotseat/workspace
-        if (ADAPTIVE_ICON_WINDOW_ANIM.get()) {
-            return getFirstMatch(cellLayouts, preferredItem, preferredItemInFolder,
-                    packageAndUserAndApp, packageAndUserAndAppInFolder);
-        } else {
-            // Do not use Folder as a criteria, since it'll cause a crash when trying to draw
-            // FolderAdaptiveIcon as the background.
-            return getFirstMatch(cellLayouts, preferredItem, packageAndUserAndApp);
-        }
-    }
-
     public View getHomescreenIconByItemId(final int id) {
         return getFirstMatch((info, v) -> info != null && info.id == id);
     }
@@ -3227,23 +3169,6 @@
         return value[0];
     }
 
-    /**
-     * Finds the first view matching the ordered operators across the given cell layouts by order.
-     * @param cellLayouts List of CellLayouts to scan, in order of preference.
-     * @param operators List of operators, in order starting from best matching operator.
-     */
-    View getFirstMatch(Iterable<CellLayout> cellLayouts, final ItemOperator... operators) {
-        for (ItemOperator operator : operators) {
-            for (CellLayout cellLayout : cellLayouts) {
-                View match = mapOverCellLayout(cellLayout, operator);
-                if (match != null) {
-                    return match;
-                }
-            }
-        }
-        return null;
-    }
-
     void clearDropTargets() {
         mapOverItems(new ItemOperator() {
             @Override
diff --git a/src/com/android/launcher3/states/SpringLoadedState.java b/src/com/android/launcher3/states/SpringLoadedState.java
index 5fe5450..d52594e 100644
--- a/src/com/android/launcher3/states/SpringLoadedState.java
+++ b/src/com/android/launcher3/states/SpringLoadedState.java
@@ -90,9 +90,4 @@
     public float getWorkspaceBackgroundAlpha(Launcher launcher) {
         return 0.2f;
     }
-
-    @Override
-    public int getVisibleElements(Launcher launcher) {
-        return (super.getVisibleElements(launcher) | HOTSEAT_ICONS) & ~TASKBAR;
-    }
 }
diff --git a/src/com/android/launcher3/util/ItemInfoMatcher.java b/src/com/android/launcher3/util/ItemInfoMatcher.java
index ab3083d..7917410 100644
--- a/src/com/android/launcher3/util/ItemInfoMatcher.java
+++ b/src/com/android/launcher3/util/ItemInfoMatcher.java
@@ -20,6 +20,7 @@
 import android.os.UserHandle;
 
 import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.shortcuts.ShortcutKey;
 
@@ -85,8 +86,16 @@
     }
 
     static ItemInfoMatcher ofShortcutKeys(Set<ShortcutKey> keys) {
-        return  (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
-                        keys.contains(ShortcutKey.fromItemInfo(info));
+        return (info, cn) -> info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT
+                && keys.contains(ShortcutKey.fromItemInfo(info));
+    }
+
+    /**
+     * Returns a matcher for items within folders.
+     */
+    static ItemInfoMatcher forFolderMatch(ItemInfoMatcher childOperator) {
+        return (info, cn) -> info instanceof FolderInfo && ((FolderInfo) info).contents.stream()
+                .anyMatch(childOperator::matchesInfo);
     }
 
     /**
diff --git a/src/com/android/launcher3/views/FloatingSurfaceView.java b/src/com/android/launcher3/views/FloatingSurfaceView.java
index e2e3be7..f32f904 100644
--- a/src/com/android/launcher3/views/FloatingSurfaceView.java
+++ b/src/com/android/launcher3/views/FloatingSurfaceView.java
@@ -158,7 +158,7 @@
         if (mContract == null) {
             return;
         }
-        View icon = mLauncher.getWorkspace().getFirstMatchForAppClose(-1,
+        View icon = mLauncher.getFirstMatchForAppClose(-1,
                 mContract.componentName.getPackageName(), mContract.user);
 
         boolean iconChanged = mIcon != icon;
@@ -182,7 +182,7 @@
                 lp.topMargin = Math.round(mIconPosition.top);
             }
         }
-        if (iconChanged && !mIconBounds.isEmpty()) {
+        if (mIcon != null && iconChanged && !mIconBounds.isEmpty()) {
             // Record the icon display
             setCurrentIconVisible(true);
             Canvas c = mPicture.beginRecording(mIconBounds.width(), mIconBounds.height());
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index 4390211..618ab41 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -250,5 +250,21 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity-alias>
+        <activity-alias android:name="Activity13" android:exported="true"
+            android:label="TestActivity13"
+            android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
+        <activity-alias android:name="Activity14" android:exported="true"
+            android:label="TestActivity14"
+            android:targetActivity="com.android.launcher3.testcomponent.OtherBaseTestingActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity-alias>
     </application>
 </manifest>
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index c90d283..41c7c37 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -80,12 +80,8 @@
         assertTrue(message, failed);
     }
 
-    private int pagesPerScreen() {
-        return mLauncher.isTwoPanels() ? 2 : 1;
-    }
-
-    private boolean isWorkspaceScrollable(Launcher launcher) {
-        return launcher.getWorkspace().getPageCount() > pagesPerScreen();
+    public static boolean isWorkspaceScrollable(Launcher launcher) {
+        return launcher.getWorkspace().getPageCount() > launcher.getWorkspace().getPanelCount();
     }
 
     private int getCurrentWorkspacePage(Launcher launcher) {
@@ -192,7 +188,7 @@
         executeOnLauncher(
                 launcher -> assertEquals(
                         "Ensuring workspace scrollable didn't switch to next screen",
-                        pagesPerScreen(), getCurrentWorkspacePage(launcher)));
+                        workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
         executeOnLauncher(
                 launcher -> assertTrue("ensureScrollable didn't make workspace scrollable",
                         isWorkspaceScrollable(launcher)));
@@ -209,7 +205,7 @@
         workspace.flingForward();
         executeOnLauncher(
                 launcher -> assertEquals("Flinging forward didn't switch workspace to next screen",
-                        pagesPerScreen(), getCurrentWorkspacePage(launcher)));
+                        workspace.pagesPerScreen(), getCurrentWorkspacePage(launcher)));
         assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
 
         // Test starting a workspace app.
diff --git a/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
new file mode 100644
index 0000000..b048cd4
--- /dev/null
+++ b/tests/src/com/android/launcher3/ui/workspace/TwoPanelWorkspaceTest.java
@@ -0,0 +1,285 @@
+/*
+ * 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.ui.workspace;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.model.data.ItemInfo;
+import com.android.launcher3.tapl.Workspace;
+import com.android.launcher3.ui.AbstractLauncherUiTest;
+import com.android.launcher3.ui.TaplTestsLauncher3;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Tests for two panel workspace.
+ *
+ * Note running these tests will clear the workspace on the device.
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class TwoPanelWorkspaceTest extends AbstractLauncherUiTest {
+
+    Workspace mWorkspace;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        TaplTestsLauncher3.initialize(this);
+        mWorkspace = mLauncher.getWorkspace();
+    }
+
+    @Test
+    public void testDragIconToRightPanel() {
+        if (!mLauncher.isTwoPanels()) {
+            return;
+        }
+
+        // Pre verifying the screens
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1);
+            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertPageEmpty(launcher, 1);
+        });
+
+        mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Chrome"), 1);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1);
+            assertItemsOnPage(launcher, 0, "Maps", "Play Store");
+            assertItemsOnPage(launcher, 1, "Chrome");
+        });
+    }
+
+    @Test
+    public void testDragIconToPage2() {
+        if (!mLauncher.isTwoPanels()) {
+            return;
+        }
+
+        // Pre verifying the screens
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1);
+            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertPageEmpty(launcher, 1);
+        });
+
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), 2);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1, 2, 3);
+            assertItemsOnPage(launcher, 0, "Play Store");
+            assertPageEmpty(launcher, 1);
+            assertItemsOnPage(launcher, 2, "Maps");
+            assertPageEmpty(launcher, 3);
+        });
+    }
+
+    @Test
+    public void testDragIconToPage3() {
+        if (!mLauncher.isTwoPanels()) {
+            return;
+        }
+
+        // Pre verifying the screens
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1);
+            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertPageEmpty(launcher, 1);
+        });
+
+        mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Phone"), 3);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1, 2, 3);
+            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertPageEmpty(launcher, 1);
+            assertPageEmpty(launcher, 2);
+            assertItemsOnPage(launcher, 3, "Phone");
+        });
+    }
+
+
+    @Test
+    public void testEmptyPageDoesNotGetRemovedIfPagePairIsNotEmpty() {
+        if (!mLauncher.isTwoPanels()) {
+            return;
+        }
+
+        // Pre verifying the screens
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1);
+            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertPageEmpty(launcher, 1);
+        });
+
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), 3);
+        mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Chrome"), 0);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1, 2, 3);
+            assertItemsOnPage(launcher, 0, "Play Store");
+            assertPageEmpty(launcher, 1);
+            assertItemsOnPage(launcher, 2, "Chrome");
+            assertItemsOnPage(launcher, 3, "Maps");
+        });
+
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), -1);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1, 2, 3);
+            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 1, "Maps");
+            assertItemsOnPage(launcher, 2, "Chrome");
+            assertPageEmpty(launcher, 3);
+        });
+
+        // Move Chrome to the right panel as well, to make sure pages are not deleted whichever
+        // page is the empty one
+        mWorkspace.flingForward();
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Chrome"), 1);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1, 2, 3);
+            assertItemsOnPage(launcher, 0, "Play Store");
+            assertItemsOnPage(launcher, 1, "Maps");
+            assertPageEmpty(launcher, 2);
+            assertItemsOnPage(launcher, 3, "Chrome");
+        });
+    }
+
+
+    @Test
+    public void testEmptyPagesGetRemovedIfBothPagesAreEmpty() {
+        if (!mLauncher.isTwoPanels()) {
+            return;
+        }
+
+        // Pre verifying the screens
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1);
+            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertPageEmpty(launcher, 1);
+        });
+
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Play Store"), 2);
+        mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Camera"), 1);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1, 2, 3);
+            assertItemsOnPage(launcher, 0, "Maps");
+            assertPageEmpty(launcher, 1);
+            assertItemsOnPage(launcher, 2, "Play Store");
+            assertItemsOnPage(launcher, 3, "Camera");
+        });
+
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Camera"), -1);
+        mWorkspace.flingForward();
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Play Store"), -2);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1);
+            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertItemsOnPage(launcher, 1, "Camera");
+        });
+    }
+
+    @Test
+    public void testMiddleEmptyPagesGetRemoved() {
+        if (!mLauncher.isTwoPanels()) {
+            return;
+        }
+
+        // Pre verifying the screens
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1);
+            assertItemsOnPage(launcher, 0, "Play Store", "Maps");
+            assertPageEmpty(launcher, 1);
+        });
+
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), 2);
+        mWorkspace.dragIcon(mWorkspace.getHotseatAppIcon("Messages"), 3);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1, 2, 3, 4, 5);
+            assertItemsOnPage(launcher, 0, "Play Store");
+            assertPageEmpty(launcher, 1);
+            assertItemsOnPage(launcher, 2, "Maps");
+            assertPageEmpty(launcher, 3);
+            assertPageEmpty(launcher, 4);
+            assertItemsOnPage(launcher, 5, "Messages");
+        });
+
+        mWorkspace.flingBackward();
+        mWorkspace.dragIcon(mWorkspace.getWorkspaceAppIcon("Maps"), 2);
+
+        executeOnLauncher(launcher -> {
+            assertPagesExist(launcher, 0, 1, 4, 5);
+            assertItemsOnPage(launcher, 0, "Play Store");
+            assertPageEmpty(launcher, 1);
+            assertItemsOnPage(launcher, 4, "Maps");
+            assertItemsOnPage(launcher, 5, "Messages");
+        });
+    }
+
+    private void assertPageEmpty(Launcher launcher, int pageId) {
+        CellLayout page = launcher.getWorkspace().getScreenWithId(pageId);
+        assertNotNull("Page " + pageId + " does NOT exist.", page);
+        assertEquals("Page " + pageId + " is NOT empty. Number of items on the page:", 0,
+                page.getShortcutsAndWidgets().getChildCount());
+    }
+
+    private void assertPagesExist(Launcher launcher, int... pageIds) {
+        int pageCount = launcher.getWorkspace().getPageCount();
+        assertEquals("Existing page count does NOT match.", pageIds.length, pageCount);
+        for (int i = 0; i < pageCount; i++) {
+            CellLayout page = (CellLayout) launcher.getWorkspace().getPageAt(i);
+            int pageId = launcher.getWorkspace().getIdForScreen(page);
+            assertEquals("The page's id at index " + i + " does NOT match.", pageId,
+                    pageIds[i]);
+        }
+    }
+
+    private void assertItemsOnPage(Launcher launcher, int pageId, String... itemTitles) {
+        Set<String> itemTitleSet = Arrays.stream(itemTitles).collect(Collectors.toSet());
+        CellLayout page = launcher.getWorkspace().getScreenWithId(pageId);
+        int itemCount = page.getShortcutsAndWidgets().getChildCount();
+        for (int i = 0; i < itemCount; i++) {
+            ItemInfo itemInfo = (ItemInfo) page.getShortcutsAndWidgets().getChildAt(i).getTag();
+            if (itemInfo != null) {
+                assertTrue("There was an extra item on page " + pageId + ": " + itemInfo.title,
+                        itemTitleSet.remove(itemInfo.title));
+            }
+        }
+        assertTrue("Could NOT find some of the items on page " + pageId + ": "
+                        + itemTitleSet.stream().collect(Collectors.joining(",")),
+                itemTitleSet.isEmpty());
+    }
+}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index 0145690..d9f5cc8 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -145,16 +145,7 @@
             if (!isWorkspaceScrollable(workspace)) {
                 try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
                         "dragging icon to a second page of workspace to make it scrollable")) {
-                    dragIconToWorkspace(
-                            mLauncher,
-                            getHotseatAppIcon("Chrome"),
-                            new Point(mLauncher.getDevice().getDisplayWidth(),
-                                    mLauncher.getVisibleBounds(workspace).centerY()),
-                            "popup_container",
-                            false,
-                            false,
-                            () -> mLauncher.expectEvent(
-                                    TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT));
+                    dragIcon(workspace, getHotseatAppIcon("Chrome"), pagesPerScreen());
                     verifyActiveContainer();
                 }
             }
@@ -163,6 +154,48 @@
         }
     }
 
+    /**
+     * Returns the number of pages that are visible on the screen simultaneously.
+     */
+    public int pagesPerScreen() {
+        return mLauncher.isTwoPanels() ? 2 : 1;
+    }
+
+    /**
+     * Drags an icon to the (currentPage + pageDelta) page if the page already exists.
+     * If the target page doesn't exist, the icon will be put onto an existing page that is the
+     * closest to the target page.
+     *
+     * @param appIcon   - icon to drag.
+     * @param pageDelta - how many pages should the icon be dragged from the current page.
+     *                    It can be a negative value.
+     */
+    public void dragIcon(AppIcon appIcon, int pageDelta) {
+        try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+            final UiObject2 workspace = verifyActiveContainer();
+            try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+                    "dragging icon to page with delta: " + pageDelta)) {
+                dragIcon(workspace, appIcon, pageDelta);
+                verifyActiveContainer();
+            }
+        }
+    }
+
+    private void dragIcon(UiObject2 workspace, AppIcon appIcon, int pageDelta) {
+        int pageWidth = mLauncher.getDevice().getDisplayWidth() / pagesPerScreen();
+        int targetX = (pageWidth / 2) + pageWidth * pageDelta;
+        dragIconToWorkspace(
+                mLauncher,
+                appIcon,
+                new Point(targetX, mLauncher.getVisibleBounds(workspace).centerY()),
+                "popup_container",
+                false,
+                false,
+                () -> mLauncher.expectEvent(
+                        TestProtocol.SEQUENCE_MAIN, LONG_CLICK_EVENT));
+        verifyActiveContainer();
+    }
+
     private boolean isWorkspaceScrollable(UiObject2 workspace) {
         return workspace.getChildCount() > (mLauncher.isTwoPanels() ? 2 : 1);
     }
@@ -258,10 +291,26 @@
         try (LauncherInstrumentation.Closable ignored = launcher.addContextLayer(
                 "want to drag icon to workspace")) {
             final long downTime = SystemClock.uptimeMillis();
-            final Point dragStartCenter = dragIconToSpringLoaded(launcher, downTime,
+            Point dragStart = dragIconToSpringLoaded(launcher, downTime,
                     launchable.getObject(), longPressIndicator, expectLongClickEvents);
-            final Point targetDest = dest.get();
-            launcher.movePointer(dragStartCenter, targetDest, 10, downTime, true,
+            Point targetDest = dest.get();
+            int displayX = launcher.getRealDisplaySize().x;
+
+            // Since the destination can be on another page, we need to drag to the edge first
+            // until we reach the target page
+            while (targetDest.x > displayX || targetDest.x < 0) {
+                int edgeX = targetDest.x > 0 ? displayX : 0;
+                Point screenEdge = new Point(edgeX, targetDest.y);
+                launcher.movePointer(dragStart, screenEdge, 10, downTime, true,
+                        LauncherInstrumentation.GestureScope.INSIDE);
+                launcher.waitForIdle(); // Wait for the page change to happen
+                targetDest.x += displayX * (targetDest.x > 0 ? -1 : 1);
+                dragStart = screenEdge;
+            }
+
+            // targetDest.x is now between 0 and displayX so we found the target page,
+            // we just have to put move the icon to the destination and drop it
+            launcher.movePointer(dragStart, targetDest, 10, downTime, true,
                     LauncherInstrumentation.GestureScope.INSIDE);
             dropDraggedIcon(launcher, targetDest, downTime, expectDropEvents);
         }