[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: a879c82938 -s ours

am skip reason: subject contains skip directive

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/18515846

Change-Id: I534a9961fe6c55be02a56ac4e1f41f0005b3e1e0
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
index 2239102..95d6dd0 100644
--- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java
@@ -92,6 +92,9 @@
 import com.android.systemui.unfold.UnfoldTransitionFactory;
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
 import com.android.systemui.unfold.config.UnfoldTransitionConfig;
+import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider;
+import com.android.systemui.unfold.system.DeviceStateManagerFoldProvider;
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -343,15 +346,17 @@
     }
 
     private void initUnfoldTransitionProgressProvider() {
-        final UnfoldTransitionConfig config = UnfoldTransitionFactory.createConfig(this);
+        final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
         if (config.isEnabled()) {
             mUnfoldTransitionProgressProvider =
                     UnfoldTransitionFactory.createUnfoldTransitionProgressProvider(
-                            this,
+                            /* context= */ this,
                             config,
                             ProxyScreenStatusProvider.INSTANCE,
-                            getSystemService(DeviceStateManager.class),
-                            getSystemService(ActivityManager.class),
+                            new DeviceStateManagerFoldProvider(
+                                    getSystemService(DeviceStateManager.class), /* context */this),
+                            new ActivityManagerActivityTypeProvider(
+                                    getSystemService(ActivityManager.class)),
                             getSystemService(SensorManager.class),
                             getMainThreadHandler(),
                             getMainExecutor(),
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 052c695..dc0ef27 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -30,7 +30,6 @@
 
 import com.android.launcher3.AbstractFloatingView;
 import com.android.launcher3.BaseQuickstepLauncher;
-import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.LauncherState;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.statemanager.StateManager;
@@ -407,18 +406,13 @@
         boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
                 || (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
 
+        updateIconAlignment(alignment);
+
         // Sync the first frame where we swap taskbar and hotseat.
         if (firstFrameVisChanged && mCanSyncViews && !Utilities.IS_RUNNING_IN_TEST_HARNESS) {
-            DeviceProfile dp = mLauncher.getDeviceProfile();
-
-            // Do all the heavy work before the sync.
-            mControllers.taskbarViewController.createIconAlignmentControllerIfNotExists(dp);
-
             ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(),
                     mControllers.taskbarActivityContext.getDragLayer(),
-                    () -> updateIconAlignment(alignment));
-        } else {
-            updateIconAlignment(alignment);
+                    () -> {});
         }
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 3dd7932..3562f5b 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -205,23 +205,15 @@
     }
 
     /**
-     * Creates the icon alignment controller if it does not already exist.
-     * @param launcherDp Launcher device profile.
-     */
-    public void createIconAlignmentControllerIfNotExists(DeviceProfile launcherDp) {
-        if (mIconAlignControllerLazy == null) {
-            mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
-        }
-    }
-
-    /**
      * Sets the taskbar icon alignment relative to Launcher hotseat icons
      * @param alignmentRatio [0, 1]
      *                       0 => not aligned
      *                       1 => fully aligned
      */
     public void setLauncherIconAlignment(float alignmentRatio, DeviceProfile launcherDp) {
-        createIconAlignmentControllerIfNotExists(launcherDp);
+        if (mIconAlignControllerLazy == null) {
+            mIconAlignControllerLazy = createIconAlignmentController(launcherDp);
+        }
         mIconAlignControllerLazy.setPlayFraction(alignmentRatio);
         if (alignmentRatio <= 0 || alignmentRatio >= 1) {
             // Cleanup lazy controller so that it is created again in next animation
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index f2583fb..d7ee3cb 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -51,6 +51,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputEvent;
@@ -127,6 +128,9 @@
 
     private static final String TAG = "TouchInteractionService";
 
+    private static final boolean BUBBLES_HOME_GESTURE_ENABLED =
+            SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", false);
+
     private static final String KEY_BACK_NOTIFICATION_COUNT = "backNotificationCount";
     private static final String NOTIFY_ACTION_BACK = "com.android.quickstep.action.BACK_GESTURE";
     private static final String HAS_ENABLED_QUICKSTEP_ONCE = "launcher.has_enabled_quickstep_once";
@@ -698,16 +702,30 @@
                 base = new TaskbarStashInputConsumer(this, base, mInputMonitorCompat, tac);
             }
 
-            // If Bubbles is expanded, use the overlay input consumer, which will close Bubbles
-            // instead of going all the way home when a swipe up is detected.
-            // Notification panel can be expanded on top of expanded bubbles. Bubbles remain
-            // expanded in the back. Make sure swipe up is not passed to bubbles in this case.
-            if ((mDeviceState.isBubblesExpanded() && !mDeviceState.isNotificationPanelExpanded())
-                    || mDeviceState.isSystemUiDialogShowing()) {
+            if (mDeviceState.isBubblesExpanded()) {
+                if (BUBBLES_HOME_GESTURE_ENABLED) {
+                    // Bubbles can handle home gesture itself.
+                    base = getDefaultInputConsumer();
+                } else {
+                    // If Bubbles is expanded, use the overlay input consumer, which will close
+                    // Bubbles instead of going all the way home when a swipe up is detected.
+                    // Notification panel can be expanded on top of expanded bubbles. Bubbles remain
+                    // expanded in the back. Make sure swipe up is not passed to bubbles in this
+                    // case.
+                    if (!mDeviceState.isNotificationPanelExpanded()) {
+                        base = new SysUiOverlayInputConsumer(
+                                getBaseContext(), mDeviceState, mInputMonitorCompat);
+                    }
+                }
+            }
+
+            if (mDeviceState.isSystemUiDialogShowing()) {
                 base = new SysUiOverlayInputConsumer(
                         getBaseContext(), mDeviceState, mInputMonitorCompat);
             }
 
+
+
             if (mDeviceState.isScreenPinningActive()) {
                 // Note: we only allow accessibility to wrap this, and it replaces the previous
                 // base input consumer (which should be NO_OP anyway since topTaskLocked == true).
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
index 11f0ff3..10b4ff9 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/OtherActivityInputConsumer.java
@@ -27,6 +27,7 @@
 import static com.android.launcher3.PagedView.DEBUG_FAILED_QUICKSWITCH;
 import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
 import static com.android.launcher3.Utilities.squaredHypot;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
 import static com.android.launcher3.util.TraceHelper.FLAG_CHECK_FOR_RACE_CONDITIONS;
 import static com.android.launcher3.util.VelocityUtils.PX_PER_MS;
 import static com.android.quickstep.util.ActiveGestureLog.INTENT_EXTRA_LOG_TRACE_ID;
@@ -47,6 +48,7 @@
 import androidx.annotation.UiThread;
 
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.tracing.InputConsumerProto;
@@ -58,7 +60,9 @@
 import com.android.quickstep.GestureState;
 import com.android.quickstep.InputConsumer;
 import com.android.quickstep.RecentsAnimationCallbacks;
+import com.android.quickstep.RecentsAnimationController;
 import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.RecentsAnimationTargets;
 import com.android.quickstep.RotationTouchHelper;
 import com.android.quickstep.TaskAnimationManager;
 import com.android.quickstep.util.ActiveGestureLog;
@@ -107,6 +111,7 @@
     private VelocityTracker mVelocityTracker;
 
     private AbsSwipeUpHandler mInteractionHandler;
+    private final FinishImmediatelyHandler mCleanupHandler = new FinishImmediatelyHandler();
 
     private final boolean mIsDeferredDownTarget;
     private final PointF mDownPos = new PointF();
@@ -377,6 +382,7 @@
 
         if (mTaskAnimationManager.isRecentsAnimationRunning()) {
             mActiveCallbacks = mTaskAnimationManager.continueRecentsAnimation(mGestureState);
+            mActiveCallbacks.removeListener(mCleanupHandler);
             mActiveCallbacks.addListener(mInteractionHandler);
             mTaskAnimationManager.notifyRecentsAnimationState(mInteractionHandler);
             notifyGestureStarted(true /*isLikelyToStartNewTask*/);
@@ -414,7 +420,19 @@
             }
         } else {
             // Since we start touch tracking on DOWN, we may reach this state without actually
-            // starting the gesture. In that case, just cleanup immediately.
+            // starting the gesture. In that case, we need to clean-up an unfinished or un-started
+            // animation.
+            if (mActiveCallbacks != null && mInteractionHandler != null) {
+                if (mTaskAnimationManager.isRecentsAnimationRunning()) {
+                    // The animation started, but with no movement, in this case, there will be no
+                    // animateToProgress so we have to manually finish here.
+                    mTaskAnimationManager.finishRunningRecentsAnimation(false /* toHome */);
+                } else {
+                    // The animation hasn't started yet, so insert a replacement handler into the
+                    // callbacks which immediately finishes the animation after it starts.
+                    mActiveCallbacks.addListener(mCleanupHandler);
+                }
+            }
             onConsumerAboutToBeSwitched();
             onInteractionGestureFinished();
 
@@ -457,7 +475,7 @@
     }
 
     private void removeListener() {
-        if (mActiveCallbacks != null) {
+        if (mActiveCallbacks != null && mInteractionHandler != null) {
             mActiveCallbacks.removeListener(mInteractionHandler);
         }
     }
@@ -483,4 +501,19 @@
             mInteractionHandler.writeToProto(inputConsumerProto);
         }
     }
+
+    /**
+     * A listener which just finishes the animation immediately after starting. Replaces
+     * AbsSwipeUpHandler if the gesture itself finishes before the animation even starts.
+     */
+    private static class FinishImmediatelyHandler
+            implements RecentsAnimationCallbacks.RecentsAnimationListener {
+
+        public void onRecentsAnimationStart(RecentsAnimationController controller,
+                RecentsAnimationTargets targets) {
+            Utilities.postAsyncCallback(MAIN_EXECUTOR.getHandler(), () -> {
+                controller.finish(false /* toRecents */, null);
+            });
+        }
+    }
 }
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index f3b3053..aa7c998 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -56,6 +56,7 @@
                     android:layout_height="@dimen/x_icon_size"
                     android:layout_gravity="center"
                     android:padding="@dimen/x_icon_padding"
+                    android:contentDescription="@string/accessibility_close"
                     android:src="@drawable/ic_remove_no_shadow" />
             </FrameLayout>
         </RelativeLayout>
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index 7b2ed8b..c16792a 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -31,7 +31,6 @@
     <dimen name="drop_target_button_drawable_horizontal_padding">24dp</dimen>
     <dimen name="drop_target_button_drawable_vertical_padding">20dp</dimen>
     <dimen name="drop_target_button_gap">32dp</dimen>
-    <dimen name="drop_target_button_workspace_edge_gap">32dp</dimen>
     <dimen name="drop_target_top_margin">110dp</dimen>
     <dimen name="drop_target_bottom_margin">48dp</dimen>
 
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 2612a7d..99346f3 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -24,8 +24,6 @@
     <dimen name="dynamic_grid_icon_drawable_padding">7dp</dimen>
     <!-- Minimum space between workspace and hotseat in spring loaded mode -->
     <dimen name="dynamic_grid_min_spring_loaded_space">8dp</dimen>
-    <!-- Minimum amount of next page visible in spring loaded mode -->
-    <dimen name="dynamic_grid_spring_loaded_min_next_space_visible">24dp</dimen>
 
     <dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
     <dimen name="cell_layout_padding">10.77dp</dimen>
@@ -60,10 +58,9 @@
     <!-- Drop target bar -->
     <dimen name="dynamic_grid_drop_target_size">56dp</dimen>
     <dimen name="drop_target_vertical_gap">20dp</dimen>
-    <dimen name="drop_target_top_margin">32dp</dimen>
+    <dimen name="drop_target_top_margin">36dp</dimen>
     <dimen name="drop_target_bottom_margin">16dp</dimen>
 
-    <!-- App Widget resize frame -->
     <!-- Button drop target bar -->
     <dimen name="button_drop_target_min_text_size">10sp</dimen>
     <dimen name="button_drop_target_resize_text_increment">1sp</dimen>
@@ -240,9 +237,7 @@
     <dimen name="drop_target_button_drawable_padding">8dp</dimen>
     <dimen name="drop_target_button_drawable_horizontal_padding">16dp</dimen>
     <dimen name="drop_target_button_drawable_vertical_padding">8dp</dimen>
-    <dimen name="drop_target_button_gap">28dp</dimen>
-    <dimen name="drop_target_button_workspace_edge_gap">0dp</dimen>
-    <dimen name="drop_target_button_screen_edge_gap">28dp</dimen>
+    <dimen name="drop_target_button_gap">22dp</dimen>
 
     <!-- the distance an icon must be dragged before button drop targets accept it -->
     <dimen name="drag_distanceThreshold">30dp</dimen>
diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java
index 8da4f05..0b07c95 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -24,7 +24,6 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.text.InputType;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -50,8 +49,6 @@
     private static final int[] sTempCords = new int[2];
     private static final int DRAG_VIEW_DROP_DURATION = 285;
     private static final float DRAG_VIEW_HOVER_OVER_OPACITY = 0.65f;
-    private static final int MAX_LINES_TEXT_MULTI_LINE = 2;
-    private static final int MAX_LINES_TEXT_SINGLE_LINE = 1;
 
     public static final int TOOLTIP_DEFAULT = 0;
     public static final int TOOLTIP_LEFT = 1;
@@ -75,8 +72,6 @@
     protected CharSequence mText;
     protected Drawable mDrawable;
     private boolean mTextVisible = true;
-    private boolean mIconVisible = true;
-    private boolean mTextMultiLine = true;
 
     private PopupWindow mToolTip;
     private int mToolTipLocation;
@@ -114,7 +109,8 @@
         // drawableLeft and drawableStart.
         mDrawable = getContext().getDrawable(resId).mutate();
         mDrawable.setTintList(getTextColors());
-        updateIconVisibility();
+        centerIcon();
+        setCompoundDrawablesRelative(mDrawable, null, null, null);
     }
 
     public void setDropTargetBar(DropTargetBar dropTargetBar) {
@@ -310,47 +306,11 @@
         if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) {
             mTextVisible = isVisible;
             setText(newText);
-            updateIconVisibility();
-        }
-    }
-
-    /**
-     * Display button text over multiple lines when isMultiLine is true, single line otherwise.
-     */
-    public void setTextMultiLine(boolean isMultiLine) {
-        if (mTextMultiLine != isMultiLine) {
-            mTextMultiLine = isMultiLine;
-            setSingleLine(!isMultiLine);
-            setMaxLines(isMultiLine ? MAX_LINES_TEXT_MULTI_LINE : MAX_LINES_TEXT_SINGLE_LINE);
-            int inputType = InputType.TYPE_CLASS_TEXT;
-            if (isMultiLine) {
-                inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
-
-            }
-            setInputType(inputType);
-        }
-    }
-
-    protected boolean isTextMultiLine() {
-        return mTextMultiLine;
-    }
-
-    /**
-     * Sets the button icon visible when isVisible is true, hides it otherwise.
-     */
-    public void setIconVisible(boolean isVisible) {
-        if (mIconVisible != isVisible) {
-            mIconVisible = isVisible;
-            updateIconVisibility();
-        }
-    }
-
-    private void updateIconVisibility() {
-        if (mIconVisible) {
             centerIcon();
+            setCompoundDrawablesRelative(mDrawable, null, null, null);
+            int drawablePadding = mTextVisible ? mDrawablePadding : 0;
+            setCompoundDrawablePadding(drawablePadding);
         }
-        setCompoundDrawablesRelative(mIconVisible ? mDrawable : null, null, null, null);
-        setCompoundDrawablePadding(mIconVisible && mTextVisible ? mDrawablePadding : 0);
     }
 
     @Override
@@ -364,6 +324,40 @@
         hideTooltip();
     }
 
+
+    /**
+     * Reduce the size of the text until it fits or reaches a minimum.
+     *
+     * The minimum size is defined by {@code R.dimen.button_drop_target_min_text_size} and
+     * it diminishes by intervals defined by
+     * {@code R.dimen.button_drop_target_resize_text_increment}
+     * This functionality is very similar to the option
+     * {@link TextView#setAutoSizeTextTypeWithDefaults(int)} but can't be used in this view because
+     * the layout width is {@code WRAP_CONTENT}.
+     *
+     * @param availableWidth Available width in the button to fit the text, used in
+     *        {@code ButtonDropTarget#isTextTruncated(int)}
+     * @return The biggest text size in SP that makes the text fit or if the text can't fit returns
+     *         the min available value
+     */
+    public float resizeTextToFit(int availableWidth) {
+        float minSize = Utilities.pxToSp(getResources()
+                .getDimensionPixelSize(R.dimen.button_drop_target_min_text_size));
+        float step = Utilities.pxToSp(getResources()
+                .getDimensionPixelSize(R.dimen.button_drop_target_resize_text_increment));
+        float textSize = Utilities.pxToSp(getTextSize());
+
+        while (textSize > minSize) {
+            if (isTextTruncated(availableWidth)) {
+                textSize -= step;
+                setTextSize(textSize);
+            } else {
+                return textSize;
+            }
+        }
+        return minSize;
+    }
+
     public boolean isTextTruncated(int availableWidth) {
         availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth()
                 + getCompoundDrawablePadding());
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 76e945d..300e7bf 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -149,8 +149,6 @@
     private boolean mVisualizeDropLocation = true;
     private RectF mVisualizeGridRect = new RectF();
     private Paint mVisualizeGridPaint = new Paint();
-    private int mGridVisualizationPaddingX;
-    private int mGridVisualizationPaddingY;
     private int mGridVisualizationRoundingRadius;
     private float mGridAlpha = 0f;
     private int mGridColor = 0;
@@ -262,10 +260,6 @@
         mBackground.setAlpha(0);
 
         mGridColor = Themes.getAttrColor(getContext(), R.attr.workspaceAccentColor);
-        mGridVisualizationPaddingX = res.getDimensionPixelSize(
-                R.dimen.grid_visualization_horizontal_cell_spacing);
-        mGridVisualizationPaddingY = res.getDimensionPixelSize(
-                R.dimen.grid_visualization_vertical_cell_spacing);
         mGridVisualizationRoundingRadius =
                 res.getDimensionPixelSize(R.dimen.grid_visualization_rounding_radius);
         mReorderPreviewAnimationMagnitude = (REORDER_PREVIEW_MAGNITUDE * deviceProfile.iconSizePx);
@@ -595,8 +589,8 @@
 
     protected void visualizeGrid(Canvas canvas) {
         DeviceProfile dp = mActivity.getDeviceProfile();
-        int paddingX = Math.min((mCellWidth - dp.iconSizePx) / 2, mGridVisualizationPaddingX);
-        int paddingY = Math.min((mCellHeight - dp.iconSizePx) / 2, mGridVisualizationPaddingY);
+        int paddingX = Math.min((mCellWidth - dp.iconSizePx) / 2, dp.gridVisualizationPaddingX);
+        int paddingY = Math.min((mCellHeight - dp.iconSizePx) / 2, dp.gridVisualizationPaddingY);
         mVisualizeGridRect.set(paddingX, paddingY,
                 mCellWidth - paddingX,
                 mCellHeight - paddingY);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 5152217..8343aa6 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -100,6 +100,8 @@
     // Workspace
     public final int desiredWorkspaceHorizontalMarginOriginalPx;
     public int desiredWorkspaceHorizontalMarginPx;
+    public int gridVisualizationPaddingX;
+    public int gridVisualizationPaddingY;
     public Point cellLayoutBorderSpaceOriginalPx;
     public Point cellLayoutBorderSpacePx;
     public Rect cellLayoutPaddingPx = new Rect();
@@ -108,7 +110,6 @@
     public float workspaceSpringLoadShrunkTop;
     public float workspaceSpringLoadShrunkBottom;
     public final int workspaceSpringLoadedBottomSpace;
-    public final int workspaceSpringLoadedMinNextPageVisiblePx;
 
     private final int extraSpace;
     public int workspaceTopPadding;
@@ -215,8 +216,6 @@
     public int dropTargetHorizontalPaddingPx;
     public int dropTargetVerticalPaddingPx;
     public int dropTargetGapPx;
-    public int dropTargetButtonWorkspaceEdgeGapPx;
-    public int dropTargetButtonScreenEdgeGapPx;
 
     // Insets
     private final Rect mInsets = new Rect();
@@ -303,6 +302,10 @@
 
         desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
         desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
+        gridVisualizationPaddingX = res.getDimensionPixelSize(
+                R.dimen.grid_visualization_horizontal_cell_spacing);
+        gridVisualizationPaddingY = res.getDimensionPixelSize(
+                R.dimen.grid_visualization_vertical_cell_spacing);
 
         bottomSheetTopPadding = mInsets.top // statusbar height
                 + res.getDimensionPixelSize(R.dimen.bottom_sheet_extra_top_padding)
@@ -344,15 +347,9 @@
         dropTargetVerticalPaddingPx = res.getDimensionPixelSize(
                 R.dimen.drop_target_button_drawable_vertical_padding);
         dropTargetGapPx = res.getDimensionPixelSize(R.dimen.drop_target_button_gap);
-        dropTargetButtonWorkspaceEdgeGapPx = res.getDimensionPixelSize(
-                R.dimen.drop_target_button_workspace_edge_gap);
-        dropTargetButtonScreenEdgeGapPx = res.getDimensionPixelSize(
-                R.dimen.drop_target_button_screen_edge_gap);
 
         workspaceSpringLoadedBottomSpace =
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_min_spring_loaded_space);
-        workspaceSpringLoadedMinNextPageVisiblePx = res.getDimensionPixelSize(
-                R.dimen.dynamic_grid_spring_loaded_min_next_space_visible);
 
         workspaceCellPaddingXPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_x);
 
@@ -508,7 +505,7 @@
      */
     private int calculateQsbWidth() {
         if (isQsbInline) {
-            int columns = getPanelCount() * inv.numColumns;
+            int columns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
             return getIconToIconWidthForColumns(columns)
                     - iconSizePx * numShownHotseatIcons
                     - hotseatBorderSpace * numShownHotseatIcons;
@@ -961,6 +958,13 @@
     }
 
     /**
+     * Gets the minimum visible amount of the next workspace page when in the spring-loaded state.
+     */
+    private float getWorkspaceSpringLoadedMinimumNextPageVisible() {
+        return getCellSize().x / 2f;
+    }
+
+    /**
      * Gets the scale of the workspace for the spring-loaded edit state.
      */
     public float getWorkspaceSpringLoadScale() {
@@ -971,7 +975,8 @@
         // Reduce scale if next pages would not be visible after scaling the workspace
         int workspaceWidth = availableWidthPx;
         float scaledWorkspaceWidth = workspaceWidth * scale;
-        float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx);
+        float maxAvailableWidth =
+                workspaceWidth - (2 * getWorkspaceSpringLoadedMinimumNextPageVisible());
         if (scaledWorkspaceWidth > maxAvailableWidth) {
             scale *= maxAvailableWidth / scaledWorkspaceWidth;
         }
@@ -1069,24 +1074,23 @@
                         mInsets.right + hotseatBarSidePaddingStartPx, paddingBottom);
             }
         } else if (isTaskbarPresent) {
-            boolean isRtl = Utilities.isRtl(context.getResources());
-            int hotseatHeight = workspacePadding.bottom;
-            int taskbarOffset = getTaskbarOffsetY();
+            // Center the QSB vertically with hotseat
+            int hotseatBottomPadding = getHotseatBottomPadding();
+            int hotseatTopPadding =
+                    workspacePadding.bottom - hotseatBottomPadding - hotseatCellHeightPx;
+
             // Push icons to the side
             int additionalQsbSpace = isQsbInline ? qsbWidth + hotseatBorderSpace : 0;
-
-            // Center the QSB vertically with hotseat
-            int hotseatTopPadding = hotseatHeight - taskbarOffset - hotseatCellHeightPx;
-
-            int endOffset = ApiWrapper.getHotseatEndOffset(context);
             int requiredWidth = iconSizePx * numShownHotseatIcons
                     + hotseatBorderSpace * (numShownHotseatIcons - 1)
                     + additionalQsbSpace;
-
+            int endOffset = ApiWrapper.getHotseatEndOffset(context);
             int hotseatWidth = Math.min(requiredWidth, availableWidthPx - endOffset);
             int sideSpacing = (availableWidthPx - hotseatWidth) / 2;
-            mHotseatPadding.set(sideSpacing, hotseatTopPadding, sideSpacing, taskbarOffset);
 
+            mHotseatPadding.set(sideSpacing, hotseatTopPadding, sideSpacing, hotseatBottomPadding);
+
+            boolean isRtl = Utilities.isRtl(context.getResources());
             if (isRtl) {
                 mHotseatPadding.right += additionalQsbSpace;
             } else {
@@ -1146,10 +1150,7 @@
         }
     }
 
-    /**
-     * Returns the number of pixels the taskbar is translated from the bottom of the screen.
-     */
-    public int getTaskbarOffsetY() {
+    private int getHotseatBottomPadding() {
         if (isQsbInline) {
             return getQsbOffsetY() - (Math.abs(hotseatQsbHeight - hotseatCellHeightPx) / 2);
         } else {
@@ -1158,6 +1159,16 @@
     }
 
     /**
+     * Returns the number of pixels the taskbar is translated from the bottom of the screen.
+     */
+    public int getTaskbarOffsetY() {
+        int taskbarIconBottomSpace = (taskbarSize - iconSizePx) / 2;
+        int launcherIconBottomSpace =
+                Math.min((hotseatCellHeightPx - iconSizePx) / 2, gridVisualizationPaddingY);
+        return getHotseatBottomPadding() + launcherIconBottomSpace - taskbarIconBottomSpace;
+    }
+
+    /**
      * Returns the number of pixels required below OverviewActions excluding insets.
      */
     public int getOverviewActionsClaimedSpaceBelow() {
@@ -1410,19 +1421,11 @@
         writer.println(prefix + pxToDpStr("dropTargetBarSizePx", dropTargetBarSizePx));
         writer.println(
                 prefix + pxToDpStr("dropTargetBarBottomMarginPx", dropTargetBarBottomMarginPx));
-        writer.println(prefix + pxToDpStr("dropTargetButtonWorkspaceEdgeGapPx",
-                dropTargetButtonWorkspaceEdgeGapPx));
-        writer.println(prefix + pxToDpStr("dropTargetButtonScreenEdgeGapPx",
-                dropTargetButtonScreenEdgeGapPx));
 
         writer.println(
                 prefix + pxToDpStr("workspaceSpringLoadShrunkTop", workspaceSpringLoadShrunkTop));
         writer.println(prefix + pxToDpStr("workspaceSpringLoadShrunkBottom",
                 workspaceSpringLoadShrunkBottom));
-        writer.println(prefix + pxToDpStr("workspaceSpringLoadedBottomSpace",
-                workspaceSpringLoadedBottomSpace));
-        writer.println(prefix + pxToDpStr("workspaceSpringLoadedMinNextPageVisiblePx",
-                workspaceSpringLoadedMinNextPageVisiblePx));
         writer.println(
                 prefix + pxToDpStr("getWorkspaceSpringLoadScale()", getWorkspaceSpringLoadScale()));
     }
diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java
index 6a8ba1b..2e3f26c 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -39,6 +39,8 @@
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.testing.TestProtocol;
 
+import java.util.Arrays;
+
 /*
  * The top bar containing various drop targets: Delete/App Info/Uninstall.
  */
@@ -51,8 +53,6 @@
     private final Runnable mFadeAnimationEndRunnable =
             () -> updateVisibility(DropTargetBar.this);
 
-    private final Launcher mLauncher;
-
     @ViewDebug.ExportedProperty(category = "launcher")
     protected boolean mDeferOnDragEnd;
 
@@ -60,19 +60,16 @@
     protected boolean mVisible = false;
 
     private ButtonDropTarget[] mDropTargets;
-    private ButtonDropTarget[] mTempTargets;
     private ViewPropertyAnimator mCurrentAnimation;
 
     private boolean mIsVertical = true;
 
     public DropTargetBar(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mLauncher = Launcher.getLauncher(context);
     }
 
     public DropTargetBar(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
-        mLauncher = Launcher.getLauncher(context);
     }
 
     @Override
@@ -83,13 +80,12 @@
             mDropTargets[i] = (ButtonDropTarget) getChildAt(i);
             mDropTargets[i].setDropTargetBar(this);
         }
-        mTempTargets = new ButtonDropTarget[getChildCount()];
     }
 
     @Override
     public void setInsets(Rect insets) {
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams();
-        DeviceProfile grid = mLauncher.getDeviceProfile();
+        DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
         mIsVertical = grid.isVerticalBarLayout();
 
         lp.leftMargin = insets.left;
@@ -120,15 +116,10 @@
         lp.height = grid.dropTargetBarSizePx;
         lp.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
 
-        DeviceProfile dp = mLauncher.getDeviceProfile();
-        int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
-        int verticalPadding = dp.dropTargetVerticalPaddingPx;
         setLayoutParams(lp);
         for (ButtonDropTarget button : mDropTargets) {
             button.setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.dropTargetTextSizePx);
             button.setToolTipLocation(tooltipLocation);
-            button.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
-                    verticalPadding);
         }
     }
 
@@ -144,83 +135,36 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         int width = MeasureSpec.getSize(widthMeasureSpec);
         int height = MeasureSpec.getSize(heightMeasureSpec);
-        int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
 
-        int visibleCount = getVisibleButtons(mTempTargets);
-        if (visibleCount == 1) {
-            int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
-
-            ButtonDropTarget firstButton = mTempTargets[0];
-            firstButton.setTextVisible(true);
-            firstButton.setIconVisible(true);
-            firstButton.measure(widthSpec, heightSpec);
-        } else if (visibleCount == 2) {
-            DeviceProfile dp = mLauncher.getDeviceProfile();
-            int verticalPadding = dp.dropTargetVerticalPaddingPx;
-            int horizontalPadding = dp.dropTargetHorizontalPaddingPx;
-
-            ButtonDropTarget firstButton = mTempTargets[0];
-            firstButton.setTextVisible(true);
-            firstButton.setIconVisible(true);
-
-            ButtonDropTarget secondButton = mTempTargets[1];
-            secondButton.setTextVisible(true);
-            secondButton.setIconVisible(true);
-            secondButton.setTextMultiLine(false);
-            // Reset second button padding in case it was previously changed to multi-line text.
-            secondButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding,
-                    verticalPadding);
-
-            if (dp.isTwoPanels) {
-                // Both buttons for two panel fit to the width of one Cell Layout (less
-                // half of the center gap between the buttons).
-                float scale = dp.getWorkspaceSpringLoadScale();
-                int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale);
-                int halfButtonGap = dp.dropTargetGapPx / 2;
-                scaledPanelWidth -= halfButtonGap / 2;
-
-                int widthSpec = MeasureSpec.makeMeasureSpec(scaledPanelWidth, MeasureSpec.AT_MOST);
-                firstButton.measure(widthSpec, heightSpec);
-                secondButton.measure(widthSpec, heightSpec);
-            } else {
-                int availableWidth;
-                int buttonGap = dp.dropTargetGapPx;
-                if (mIsVertical) {
-                    // Both buttons plus the button gap do not display past the edge of the
-                    // scaled workspace, less a pre-defined gap from the edge of the workspace.
-                    float scale = dp.getWorkspaceSpringLoadScale();
-                    int panelWidth = (int) (dp.getCellLayoutWidth() * scale);
-                    availableWidth = Math.min(
-                            panelWidth - (2 * dp.dropTargetButtonWorkspaceEdgeGapPx), width);
-                } else {
-                    // Both buttons plus the button gap display up to a pre-defined margin of
-                    // the unscaled workspace edge.
-                    availableWidth = Math.min(
-                            dp.availableWidthPx - (2 * dp.dropTargetButtonScreenEdgeGapPx),
-                            width);
+        int visibleCount = getVisibleButtonsCount();
+        if (visibleCount > 0) {
+            int availableWidth = width / visibleCount;
+            boolean textVisible = true;
+            boolean textResized = false;
+            float textSize = mDropTargets[0].getTextSize();
+            for (ButtonDropTarget button : mDropTargets) {
+                if (button.getVisibility() == GONE) {
+                    continue;
                 }
-                int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth - buttonGap,
-                        MeasureSpec.AT_MOST);
+                if (button.isTextTruncated(availableWidth)) {
+                    textSize = Math.min(textSize, button.resizeTextToFit(availableWidth));
+                    textResized = true;
+                }
+                textVisible = textVisible && !button.isTextTruncated(availableWidth);
+            }
 
-                // First button's width is at most the drop target bar's total width less the button
-                // gap.
-                firstButton.measure(widthSpec, heightSpec);
+            if (textResized) {
+                for (ButtonDropTarget button : mDropTargets) {
+                    button.setTextSize(textSize);
+                }
+            }
 
-                int usedWidth = firstButton.getMeasuredWidth() + buttonGap;
-                int remainingWidth = availableWidth - usedWidth;
-                widthSpec = MeasureSpec.makeMeasureSpec(remainingWidth, MeasureSpec.AT_MOST);
-                secondButton.measure(widthSpec, heightSpec);
-
-                // Remove both icons and put the second button's text on two lines if text is
-                // truncated on phones. We assume first button's text is never truncated, so it
-                // remains single-line.
-                if (secondButton.isTextTruncated(remainingWidth) && !mIsVertical) {
-                    firstButton.setIconVisible(false);
-                    secondButton.setIconVisible(false);
-                    secondButton.setTextMultiLine(true);
-                    secondButton.setPadding(secondButton.getPaddingLeft(),
-                            secondButton.getPaddingTop() / 2, secondButton.getPaddingRight(),
-                            secondButton.getPaddingBottom() / 2);
+            int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST);
+            int heightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+            for (ButtonDropTarget button : mDropTargets) {
+                if (button.getVisibility() != GONE) {
+                    button.setTextVisible(textVisible);
+                    button.measure(widthSpec, heightSpec);
                 }
             }
         }
@@ -229,79 +173,98 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int visibleCount = getVisibleButtons(mTempTargets);
+        int visibleCount = getVisibleButtonsCount();
         if (visibleCount == 0) {
             return;
         }
 
-        DeviceProfile dp = mLauncher.getDeviceProfile();
+        Launcher launcher = Launcher.getLauncher(getContext());
+        Workspace<?> workspace = launcher.getWorkspace();
+        DeviceProfile dp = launcher.getDeviceProfile();
+        int buttonHorizontalPadding = dp.dropTargetHorizontalPaddingPx;
+        int buttonVerticalPadding = dp.dropTargetVerticalPaddingPx;
         int barCenter = (right - left) / 2;
-        if (mIsVertical) {
-            // Center vertical bar over scaled workspace, accounting for hotseat offset.
-            float scale = dp.getWorkspaceSpringLoadScale();
-            Workspace<?> ws = mLauncher.getWorkspace();
-            int workspaceCenter = (ws.getLeft() + ws.getRight()) / 2;
-            int cellLayoutCenter = ((dp.getInsets().left + dp.workspacePadding.left) + (dp.widthPx
-                    - dp.getInsets().right - dp.workspacePadding.right)) / 2;
-            int cellLayoutCenterOffset = (int) ((cellLayoutCenter - workspaceCenter) * scale);
-            barCenter = workspaceCenter + cellLayoutCenterOffset;
-        }
+
+        ButtonDropTarget[] visibleButtons = Arrays.stream(mDropTargets)
+                .filter(b -> b.getVisibility() != GONE)
+                .toArray(ButtonDropTarget[]::new);
+        Arrays.stream(visibleButtons).forEach(
+                b -> b.setPadding(buttonHorizontalPadding, buttonVerticalPadding,
+                        buttonHorizontalPadding, buttonVerticalPadding));
 
         if (visibleCount == 1) {
-            ButtonDropTarget button = mTempTargets[0];
+            ButtonDropTarget button = visibleButtons[0];
             button.layout(barCenter - (button.getMeasuredWidth() / 2), 0,
                     barCenter + (button.getMeasuredWidth() / 2), button.getMeasuredHeight());
         } else if (visibleCount == 2) {
             int buttonGap = dp.dropTargetGapPx;
 
             if (dp.isTwoPanels) {
-                ButtonDropTarget leftButton = mTempTargets[0];
+                ButtonDropTarget leftButton = visibleButtons[0];
                 leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
                         barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
 
-                ButtonDropTarget rightButton = mTempTargets[1];
+                ButtonDropTarget rightButton = visibleButtons[1];
                 rightButton.layout(barCenter + (buttonGap / 2), 0,
-                        barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
+                        barCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
                         rightButton.getMeasuredHeight());
-            } else {
-                int start;
-                int end;
-                if (mIsVertical) {
-                    // Scaled CellLayout width is assumed to not exceed the bounds of left/right.
-                    float scale = dp.getWorkspaceSpringLoadScale();
-                    int panelWidth = (int) (dp.getCellLayoutWidth() * scale);
-                    start = barCenter - (panelWidth / 2) + dp.dropTargetButtonWorkspaceEdgeGapPx;
-                    end = barCenter + (panelWidth / 2) - dp.dropTargetButtonWorkspaceEdgeGapPx;
-                } else {
-                    start = Math.max(dp.dropTargetButtonScreenEdgeGapPx, left);
-                    end = Math.min(dp.availableWidthPx - dp.dropTargetButtonScreenEdgeGapPx, right);
+            } else if (dp.isTablet) {
+                int numberOfMargins = visibleCount - 1;
+                int buttonWidths = Arrays.stream(mDropTargets)
+                        .filter(b -> b.getVisibility() != GONE)
+                        .mapToInt(ButtonDropTarget::getMeasuredWidth)
+                        .sum();
+                int totalWidth = buttonWidths + (numberOfMargins * buttonGap);
+                int buttonsStartMargin = barCenter - (totalWidth / 2);
+
+                int start = buttonsStartMargin;
+                for (ButtonDropTarget button : visibleButtons) {
+                    int margin = (start != buttonsStartMargin) ? buttonGap : 0;
+                    button.layout(start + margin, 0, start + margin + button.getMeasuredWidth(),
+                            button.getMeasuredHeight());
+                    start += button.getMeasuredWidth() + margin;
+                }
+            } else if (mIsVertical) {
+                // Center buttons over workspace, not screen.
+                int verticalCenter = (workspace.getRight() - workspace.getLeft()) / 2;
+                ButtonDropTarget leftButton = visibleButtons[0];
+                leftButton.layout(verticalCenter - leftButton.getMeasuredWidth() - (buttonGap / 2),
+                        0, verticalCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
+
+                ButtonDropTarget rightButton = visibleButtons[1];
+                rightButton.layout(verticalCenter + (buttonGap / 2), 0,
+                        verticalCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
+                        rightButton.getMeasuredHeight());
+            } else if (dp.isPhone) {
+                // Buttons aligned to outer edges of scaled workspace.
+                float scale = dp.getWorkspaceSpringLoadScale();
+
+                int workspaceWidth = (int) (launcher.getWorkspace().getNormalChildWidth() * scale);
+                int start = barCenter - (workspaceWidth / 2);
+                int end = barCenter + (workspaceWidth / 2);
+
+                ButtonDropTarget leftButton = visibleButtons[0];
+                ButtonDropTarget rightButton = visibleButtons[1];
+
+                // If the text within the buttons is too long, the buttons can overlap
+                int overlap = start + leftButton.getMeasuredWidth() + rightButton.getMeasuredWidth()
+                        - end;
+                if (overlap > 0) {
+                    end += overlap;
                 }
 
-                ButtonDropTarget leftButton = mTempTargets[0];
-                ButtonDropTarget rightButton = mTempTargets[1];
-
-                int leftButtonWidth = leftButton.getMeasuredWidth();
-                int rightButtonWidth = rightButton.getMeasuredWidth();
-                int buttonPlusGapWidth = leftButtonWidth + buttonGap + rightButtonWidth;
-
-                int extraSpace = end - start - buttonPlusGapWidth;
-                start = (start - left) + (extraSpace / 2);
-
-                leftButton.layout(start, 0, start + leftButtonWidth,
+                leftButton.layout(start, 0, start + leftButton.getMeasuredWidth(),
                         leftButton.getMeasuredHeight());
-
-                int rightButtonStart = start + leftButtonWidth + buttonGap;
-                rightButton.layout(rightButtonStart, 0, rightButtonStart + rightButtonWidth,
+                rightButton.layout(end - rightButton.getMeasuredWidth(), 0, end,
                         rightButton.getMeasuredHeight());
             }
         }
     }
 
-    private int getVisibleButtons(ButtonDropTarget[] outVisibleButtons) {
+    private int getVisibleButtonsCount() {
         int visibleCount = 0;
-        for (ButtonDropTarget button : mDropTargets) {
-            if (button.getVisibility() != GONE) {
-                outVisibleButtons[visibleCount] = button;
+        for (ButtonDropTarget buttons : mDropTargets) {
+            if (buttons.getVisibility() != GONE) {
                 visibleCount++;
             }
         }
diff --git a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
index ef9250c..a7b0b9d 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationTaskV2.java
@@ -56,6 +56,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * This class takes care of shrinking the workspace (by maximum of one row and one column), as a
@@ -267,38 +268,33 @@
 
     /** Return what's in the src but not in the dest */
     private static List<DbEntry> calcDiff(List<DbEntry> src, List<DbEntry> dest) {
-        Set<String> destIntentSet = new HashSet<>();
-        Set<Map<String, Integer>> destFolderIntentSet = new HashSet<>();
+        Map<String, Integer> destIdSet = new HashMap<>();
         for (DbEntry entry : dest) {
-            if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                destFolderIntentSet.add(getFolderIntents(entry));
+            String entryID = entry.getEntryMigrationId();
+            if (destIdSet.containsKey(entryID)) {
+                destIdSet.put(entryID, destIdSet.get(entryID) + 1);
             } else {
-                destIntentSet.add(entry.mIntent);
+                destIdSet.put(entryID, 1);
             }
         }
         List<DbEntry> diff = new ArrayList<>();
         for (DbEntry entry : src) {
-            if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
-                if (!destFolderIntentSet.contains(getFolderIntents(entry))) {
+            String entryID = entry.getEntryMigrationId();
+            if (destIdSet.containsKey(entryID)) {
+                Integer count = destIdSet.get(entryID);
+                if (count <= 0) {
                     diff.add(entry);
+                    destIdSet.remove(entryID);
+                } else {
+                    destIdSet.put(entryID, count - 1);
                 }
             } else {
-                if (!destIntentSet.contains(entry.mIntent)) {
-                    diff.add(entry);
-                }
+                diff.add(entry);
             }
         }
         return diff;
     }
 
-    private static Map<String, Integer> getFolderIntents(DbEntry entry) {
-        Map<String, Integer> folder = new HashMap<>();
-        for (String intent : entry.mFolderItems.keySet()) {
-            folder.put(intent, entry.mFolderItems.get(intent).size());
-        }
-        return folder;
-    }
-
     private static void insertEntryInDb(SQLiteDatabase db, Context context, DbEntry entry,
             String srcTableName, String destTableName) {
         int id = copyEntryAndUpdate(db, context, entry, srcTableName, destTableName);
@@ -780,5 +776,31 @@
             values.put(LauncherSettings.Favorites.SPANX, spanX);
             values.put(LauncherSettings.Favorites.SPANY, spanY);
         }
+
+        /**
+         * This method should return an id that should be the same for two folders containing the
+         * same elements.
+         */
+        private String getFolderMigrationId() {
+            return mFolderItems.keySet().stream()
+                    .map(intentString -> mFolderItems.get(intentString).size() + intentString)
+                    .sorted()
+                    .collect(Collectors.joining(","));
+        }
+
+        /** This id is not used in the DB is only used while doing the migration and it identifies
+         * an entry on each workspace. For example two calculator icons would have the same
+         * migration id even thought they have different database ids.
+         */
+        public String getEntryMigrationId() {
+            switch (itemType) {
+                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
+                    return getFolderMigrationId();
+                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
+                    return mProvider;
+                default:
+                    return mIntent;
+            }
+        }
     }
 }
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
index 73aa296..a2ab7f9 100644
--- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -53,7 +53,6 @@
     private LauncherModel mModel;
 
     private BaseDragLayer mDragLayer;
-    // TODO(b/216191717): Verify all apps works on secondary display.
     private ActivityAllAppsContainerView<SecondaryDisplayLauncher> mAppsView;
     private View mAppsButton;