Merge "Update drop target button alignment across devices." into tm-dev
diff --git a/res/values-sw720dp/dimens.xml b/res/values-sw720dp/dimens.xml
index fad8c95..8e985bd 100644
--- a/res/values-sw720dp/dimens.xml
+++ b/res/values-sw720dp/dimens.xml
@@ -28,6 +28,7 @@
     <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 098c694..e02ee8a 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -24,6 +24,8 @@
     <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>
@@ -58,13 +60,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">36dp</dimen>
+    <dimen name="drop_target_top_margin">32dp</dimen>
     <dimen name="drop_target_bottom_margin">16dp</dimen>
 
-    <!-- Button drop target bar -->
-    <dimen name="button_drop_target_min_text_size">10sp</dimen>
-    <dimen name="button_drop_target_resize_text_increment">1sp</dimen>
-
 <!-- App Widget resize frame -->
     <dimen name="widget_handle_margin">13dp</dimen>
     <dimen name="resize_frame_background_padding">24dp</dimen>
@@ -229,7 +227,9 @@
     <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">22dp</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>
 
     <!-- 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 0b07c95..8da4f05 100644
--- a/src/com/android/launcher3/ButtonDropTarget.java
+++ b/src/com/android/launcher3/ButtonDropTarget.java
@@ -24,6 +24,7 @@
 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;
@@ -49,6 +50,8 @@
     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;
@@ -72,6 +75,8 @@
     protected CharSequence mText;
     protected Drawable mDrawable;
     private boolean mTextVisible = true;
+    private boolean mIconVisible = true;
+    private boolean mTextMultiLine = true;
 
     private PopupWindow mToolTip;
     private int mToolTipLocation;
@@ -109,8 +114,7 @@
         // drawableLeft and drawableStart.
         mDrawable = getContext().getDrawable(resId).mutate();
         mDrawable.setTintList(getTextColors());
-        centerIcon();
-        setCompoundDrawablesRelative(mDrawable, null, null, null);
+        updateIconVisibility();
     }
 
     public void setDropTargetBar(DropTargetBar dropTargetBar) {
@@ -306,13 +310,49 @@
         if (mTextVisible != isVisible || !TextUtils.equals(newText, getText())) {
             mTextVisible = isVisible;
             setText(newText);
-            centerIcon();
-            setCompoundDrawablesRelative(mDrawable, null, null, null);
-            int drawablePadding = mTextVisible ? mDrawablePadding : 0;
-            setCompoundDrawablePadding(drawablePadding);
+            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(mIconVisible ? mDrawable : null, null, null, null);
+        setCompoundDrawablePadding(mIconVisible && mTextVisible ? mDrawablePadding : 0);
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
@@ -324,40 +364,6 @@
         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/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index be180a6..f028d3c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -108,6 +108,7 @@
     public float workspaceSpringLoadShrunkTop;
     public float workspaceSpringLoadShrunkBottom;
     public final int workspaceSpringLoadedBottomSpace;
+    public final int workspaceSpringLoadedMinNextPageVisiblePx;
 
     private final int extraSpace;
     public int workspaceTopPadding;
@@ -214,6 +215,8 @@
     public int dropTargetHorizontalPaddingPx;
     public int dropTargetVerticalPaddingPx;
     public int dropTargetGapPx;
+    public int dropTargetButtonWorkspaceEdgeGapPx;
+    public int dropTargetButtonScreenEdgeGapPx;
 
     // Insets
     private final Rect mInsets = new Rect();
@@ -343,9 +346,15 @@
         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);
 
@@ -501,7 +510,7 @@
      */
     private int calculateQsbWidth() {
         if (isQsbInline) {
-            int columns = isTwoPanels ? inv.numColumns * 2 : inv.numColumns;
+            int columns = getPanelCount() * inv.numColumns;
             return getIconToIconWidthForColumns(columns)
                     - iconSizePx * numShownHotseatIcons
                     - hotseatBorderSpace * numShownHotseatIcons;
@@ -955,13 +964,6 @@
     }
 
     /**
-     * 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() {
@@ -972,8 +974,7 @@
         // Reduce scale if next pages would not be visible after scaling the workspace
         int workspaceWidth = availableWidthPx;
         float scaledWorkspaceWidth = workspaceWidth * scale;
-        float maxAvailableWidth =
-                workspaceWidth - (2 * getWorkspaceSpringLoadedMinimumNextPageVisible());
+        float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx);
         if (scaledWorkspaceWidth > maxAvailableWidth) {
             scale *= maxAvailableWidth / scaledWorkspaceWidth;
         }
@@ -1412,11 +1413,19 @@
         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 2e3f26c..6a8ba1b 100644
--- a/src/com/android/launcher3/DropTargetBar.java
+++ b/src/com/android/launcher3/DropTargetBar.java
@@ -39,8 +39,6 @@
 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.
  */
@@ -53,6 +51,8 @@
     private final Runnable mFadeAnimationEndRunnable =
             () -> updateVisibility(DropTargetBar.this);
 
+    private final Launcher mLauncher;
+
     @ViewDebug.ExportedProperty(category = "launcher")
     protected boolean mDeferOnDragEnd;
 
@@ -60,16 +60,19 @@
     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
@@ -80,12 +83,13 @@
             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 = Launcher.getLauncher(getContext()).getDeviceProfile();
+        DeviceProfile grid = mLauncher.getDeviceProfile();
         mIsVertical = grid.isVerticalBarLayout();
 
         lp.leftMargin = insets.left;
@@ -116,10 +120,15 @@
         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);
         }
     }
 
@@ -135,36 +144,83 @@
     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 = 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;
-                }
-                if (button.isTextTruncated(availableWidth)) {
-                    textSize = Math.min(textSize, button.resizeTextToFit(availableWidth));
-                    textResized = true;
-                }
-                textVisible = textVisible && !button.isTextTruncated(availableWidth);
-            }
+        int visibleCount = getVisibleButtons(mTempTargets);
+        if (visibleCount == 1) {
+            int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST);
 
-            if (textResized) {
-                for (ButtonDropTarget button : mDropTargets) {
-                    button.setTextSize(textSize);
-                }
-            }
+            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;
 
-            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);
+            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 widthSpec = MeasureSpec.makeMeasureSpec(availableWidth - buttonGap,
+                        MeasureSpec.AT_MOST);
+
+                // First button's width is at most the drop target bar's total width less the button
+                // gap.
+                firstButton.measure(widthSpec, heightSpec);
+
+                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);
                 }
             }
         }
@@ -173,98 +229,79 @@
 
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        int visibleCount = getVisibleButtonsCount();
+        int visibleCount = getVisibleButtons(mTempTargets);
         if (visibleCount == 0) {
             return;
         }
 
-        Launcher launcher = Launcher.getLauncher(getContext());
-        Workspace<?> workspace = launcher.getWorkspace();
-        DeviceProfile dp = launcher.getDeviceProfile();
-        int buttonHorizontalPadding = dp.dropTargetHorizontalPaddingPx;
-        int buttonVerticalPadding = dp.dropTargetVerticalPaddingPx;
+        DeviceProfile dp = mLauncher.getDeviceProfile();
         int barCenter = (right - left) / 2;
-
-        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 (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;
+        }
 
         if (visibleCount == 1) {
-            ButtonDropTarget button = visibleButtons[0];
+            ButtonDropTarget button = mTempTargets[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 = visibleButtons[0];
+                ButtonDropTarget leftButton = mTempTargets[0];
                 leftButton.layout(barCenter - leftButton.getMeasuredWidth() - (buttonGap / 2), 0,
                         barCenter - (buttonGap / 2), leftButton.getMeasuredHeight());
 
-                ButtonDropTarget rightButton = visibleButtons[1];
+                ButtonDropTarget rightButton = mTempTargets[1];
                 rightButton.layout(barCenter + (buttonGap / 2), 0,
-                        barCenter + rightButton.getMeasuredWidth() + (buttonGap / 2),
+                        barCenter + (buttonGap / 2) + rightButton.getMeasuredWidth(),
                         rightButton.getMeasuredHeight());
-            } 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;
+            } 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);
                 }
 
-                leftButton.layout(start, 0, start + leftButton.getMeasuredWidth(),
+                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.getMeasuredHeight());
-                rightButton.layout(end - rightButton.getMeasuredWidth(), 0, end,
+
+                int rightButtonStart = start + leftButtonWidth + buttonGap;
+                rightButton.layout(rightButtonStart, 0, rightButtonStart + rightButtonWidth,
                         rightButton.getMeasuredHeight());
             }
         }
     }
 
-    private int getVisibleButtonsCount() {
+    private int getVisibleButtons(ButtonDropTarget[] outVisibleButtons) {
         int visibleCount = 0;
-        for (ButtonDropTarget buttons : mDropTargets) {
-            if (buttons.getVisibility() != GONE) {
+        for (ButtonDropTarget button : mDropTargets) {
+            if (button.getVisibility() != GONE) {
+                outVisibleButtons[visibleCount] = button;
                 visibleCount++;
             }
         }