Add border spacing and fixed cell height to grid.

- Border spacing is the spacing between the cells.
- Workspace cell height is now fixed, and we allocate
  all the "extra" space to three different variable height
  areas.

* Built behind ENABLE_FOUR_COLUMNS flag because it hinders the
default grid.

Bug: 175329686
Test: - set border spacing to 0 and confirm matches prior layout
      - test drag and drop still worked
      - test reordering
      - test widgets
      - test folders
      - test multiwindow

Change-Id: Ic6f3dff577d28ff214bda4b0a787ec7fd08c108b
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index e593fb4..0adde81 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -135,6 +135,16 @@
         <attr name="demoModeLayoutId" format="reference" />
     </declare-styleable>
 
+    <declare-styleable name="DevicePadding">
+        <attr name="maxEmptySpace" format="dimension" />
+    </declare-styleable>
+
+    <declare-styleable name="DevicePaddingFormula">
+        <attr name="a" format="float|dimension" />
+        <attr name="b" format="float|dimension" />
+        <attr name="c" format="float|dimension" />
+    </declare-styleable>
+
     <declare-styleable name="ProfileDisplayOption">
         <attr name="name" />
         <attr name="minWidthDps" format="float" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index eaf7a5f..1ce7840 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -20,12 +20,14 @@
 
     <!-- Dynamic Grid -->
     <dimen name="dynamic_grid_edge_margin">8dp</dimen>
-    <dimen name="dynamic_grid_icon_drawable_padding">8dp</dimen>
+    <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>
 
+    <dimen name="dynamic_grid_cell_border_spacing">16dp</dimen>
     <dimen name="dynamic_grid_cell_layout_padding">5.5dp</dimen>
     <dimen name="dynamic_grid_cell_padding_x">8dp</dimen>
+    <dimen name="dynamic_grid_cell_padding_y">7dp</dimen>
 
     <!-- Hotseat -->
     <dimen name="dynamic_grid_hotseat_top_padding">8dp</dimen>
diff --git a/res/xml/size_limits.xml b/res/xml/size_limits.xml
new file mode 100644
index 0000000..ba57014
--- /dev/null
+++ b/res/xml/size_limits.xml
@@ -0,0 +1,75 @@
+<?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.
+-->
+
+<device-paddings xmlns:launcher="http://schemas.android.com/apk/res-auto" >
+
+    <device-padding
+        launcher:maxEmptySpace="88dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="0"/>
+        <workspaceBottomPadding
+            launcher:a="0.52"
+            launcher:b="0"/>
+        <hotseatBottomPadding
+            launcher:a="0.48"
+            launcher:b="0"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="97dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="16dp"/>
+        <workspaceBottomPadding
+            launcher:a="0.50"
+            launcher:b="0"
+            launcher:c="-16dp"/>
+        <hotseatBottomPadding
+            launcher:a="0.50"
+            launcher:b="0"
+            launcher:c="16dp"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="107dp">
+        <workspaceTopPadding
+            launcher:a="0"
+            launcher:b="16dp"/>
+        <workspaceBottomPadding
+            launcher:a="0"
+            launcher:b="36dp"/>
+        <hotseatBottomPadding
+            launcher:a="1"
+            launcher:b="0"
+            launcher:c="52dp"/>
+    </device-padding>
+
+    <device-padding
+        launcher:maxEmptySpace="9999dp">
+        <workspaceTopPadding
+            launcher:a="0.38"
+            launcher:c="36dp"/>
+        <workspaceBottomPadding
+            launcher:a="0.62"
+            launcher:c="36dp"/>
+        <hotseatBottomPadding
+            launcher:a="0"
+            launcher:b="36dp"/>
+    </device-padding>
+
+</device-paddings>
\ No newline at end of file
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index bc3e341..1330ed4 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -279,8 +279,9 @@
      *  Based on the current deltas, we determine if and how to resize the widget.
      */
     private void resizeWidgetIfNeeded(boolean onDismiss) {
-        float xThreshold = mCellLayout.getCellWidth();
-        float yThreshold = mCellLayout.getCellHeight();
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        float xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacingPx;
+        float yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacingPx;
 
         int hSpanInc = getSpanIncrement((mDeltaX + mDeltaXAddOn) / xThreshold - mRunningHInc);
         int vSpanInc = getSpanIncrement((mDeltaY + mDeltaYAddOn) / yThreshold - mRunningVInc);
@@ -364,13 +365,18 @@
         final float density = context.getResources().getDisplayMetrics().density;
         final Point[] cellSize = CELL_SIZE.get(context);
 
+        final int borderSpacing = context.getResources()
+                .getDimensionPixelSize(R.dimen.dynamic_grid_cell_border_spacing);
+        final float hBorderSpacing = (spanX - 1) * borderSpacing;
+        final float vBorderSpacing = (spanY - 1) * borderSpacing;
+
         // Compute landscape size
-        int landWidth = (int) ((spanX * cellSize[0].x) / density);
-        int landHeight = (int) ((spanY * cellSize[0].y) / density);
+        int landWidth = (int) (((spanX * cellSize[0].x) + hBorderSpacing) / density);
+        int landHeight = (int) (((spanY * cellSize[0].y) + vBorderSpacing) / density);
 
         // Compute portrait size
-        int portWidth = (int) ((spanX * cellSize[1].x) / density);
-        int portHeight = (int) ((spanY * cellSize[1].y) / density);
+        int portWidth = (int) (((spanX * cellSize[1].x) + hBorderSpacing) / density);
+        int portHeight = (int) (((spanY * cellSize[1].y) + vBorderSpacing) / density);
         rect.set(portWidth, landHeight, landWidth, portHeight);
         return rect;
     }
@@ -384,8 +390,9 @@
     }
 
     private void onTouchUp() {
-        int xThreshold = mCellLayout.getCellWidth();
-        int yThreshold = mCellLayout.getCellHeight();
+        DeviceProfile dp = mLauncher.getDeviceProfile();
+        int xThreshold = mCellLayout.getCellWidth() + dp.cellLayoutBorderSpacingPx;
+        int yThreshold = mCellLayout.getCellHeight() + dp.cellLayoutBorderSpacingPx;
 
         mDeltaXAddOn = mRunningHInc * xThreshold;
         mDeltaYAddOn = mRunningVInc * yThreshold;
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 21297c9..b05a13a 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -17,6 +17,7 @@
 package com.android.launcher3;
 
 import static com.android.launcher3.FastBitmapDrawable.newIcon;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
 import static com.android.launcher3.graphics.IconShape.getShape;
 import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
 import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
@@ -198,6 +199,7 @@
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
             defaultIconSize = grid.iconSizePx;
+            setCenterVertically(ENABLE_FOUR_COLUMNS.get());
         } else if (mDisplay == DISPLAY_ALL_APPS) {
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
@@ -232,7 +234,6 @@
         int shadowSize = context.getResources().getDimensionPixelSize(
                 R.dimen.blur_size_click_shadow);
         mHighlightShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.INNER);
-
     }
 
     @Override
@@ -554,6 +555,14 @@
         outBounds.set(left, top, right, bottom);
     }
 
+
+    /**
+     * Sets whether to vertically center the content.
+     */
+    public void setCenterVertically(boolean centerVertically) {
+        mCenterVertically = centerVertically;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         if (mCenterVertically) {
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index 452207d..29812fd 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -88,6 +88,8 @@
     @Thunk int mCellHeight;
     private int mFixedCellWidth;
     private int mFixedCellHeight;
+    @ViewDebug.ExportedProperty(category = "launcher")
+    private final int mBorderSpacing;
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private int mCountX;
@@ -208,6 +210,7 @@
 
         DeviceProfile grid = mActivity.getDeviceProfile();
 
+        mBorderSpacing = grid.cellLayoutBorderSpacingPx;
         mCellWidth = mCellHeight = -1;
         mFixedCellWidth = mFixedCellHeight = -1;
 
@@ -288,7 +291,8 @@
         }
 
         mShortcutsAndWidgets = new ShortcutAndWidgetContainer(context, mContainerType);
-        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
+                mBorderSpacing);
         addView(mShortcutsAndWidgets);
     }
 
@@ -345,7 +349,8 @@
     public void setCellDimensions(int width, int height) {
         mFixedCellWidth = mCellWidth = width;
         mFixedCellHeight = mCellHeight = height;
-        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
+                mBorderSpacing);
     }
 
     public void setGridSize(int x, int y) {
@@ -354,7 +359,8 @@
         mOccupied = new GridOccupancy(mCountX, mCountY);
         mTmpOccupied = new GridOccupancy(mCountX, mCountY);
         mTempRectStack.clear();
-        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
+        mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
+                mBorderSpacing);
         requestLayout();
     }
 
@@ -475,8 +481,8 @@
             for (int j = 0; j < mCountY; j++) {
                 canvas.save();
 
-                int transX = i * mCellWidth;
-                int transY = j * mCellHeight;
+                int transX = i * mCellWidth + (i * mBorderSpacing);
+                int transY = j * mCellHeight + (j * mBorderSpacing);
 
                 canvas.translate(getPaddingLeft() + transX, getPaddingTop() + transY);
 
@@ -591,6 +597,7 @@
         if (child instanceof BubbleTextView) {
             BubbleTextView bubbleChild = (BubbleTextView) child;
             bubbleChild.setTextVisibility(mContainerType != HOTSEAT);
+            bubbleChild.setCenterVertically(mContainerType != HOTSEAT);
         }
 
         child.setScaleX(mChildScale);
@@ -706,11 +713,9 @@
      * @param result Array of 2 ints to hold the x and y coordinate of the point
      */
     void cellToPoint(int cellX, int cellY, int[] result) {
-        final int hStartPadding = getPaddingLeft();
-        final int vStartPadding = getPaddingTop();
-
-        result[0] = hStartPadding + cellX * mCellWidth;
-        result[1] = vStartPadding + cellY * mCellHeight;
+        cellToRect(cellX, cellY, 1, 1, mTempRect);
+        result[0] = mTempRect.left;
+        result[1] = mTempRect.top;
     }
 
     /**
@@ -734,25 +739,9 @@
      * @param result Array of 2 ints to hold the x and y coordinate of the point
      */
     void regionToCenterPoint(int cellX, int cellY, int spanX, int spanY, int[] result) {
-        final int hStartPadding = getPaddingLeft();
-        final int vStartPadding = getPaddingTop();
-        result[0] = hStartPadding + cellX * mCellWidth + (spanX * mCellWidth) / 2;
-        result[1] = vStartPadding + cellY * mCellHeight + (spanY * mCellHeight) / 2;
-    }
-
-     /**
-     * Given a cell coordinate and span fills out a corresponding pixel rect
-     *
-     * @param cellX X coordinate of the cell
-     * @param cellY Y coordinate of the cell
-     * @param result Rect in which to write the result
-     */
-     void regionToRect(int cellX, int cellY, int spanX, int spanY, Rect result) {
-        final int hStartPadding = getPaddingLeft();
-        final int vStartPadding = getPaddingTop();
-        final int left = hStartPadding + cellX * mCellWidth;
-        final int top = vStartPadding + cellY * mCellHeight;
-        result.set(left, top, left + (spanX * mCellWidth), top + (spanY * mCellHeight));
+        cellToRect(cellX, cellY, spanX, spanY, mTempRect);
+        result[0] = mTempRect.centerX();
+        result[1] = mTempRect.centerY();
     }
 
     public float getDistanceFromCell(float x, float y, int[] cell) {
@@ -783,12 +772,15 @@
         int childHeightSize = heightSize - (getPaddingTop() + getPaddingBottom());
 
         if (mFixedCellWidth < 0 || mFixedCellHeight < 0) {
-            int cw = DeviceProfile.calculateCellWidth(childWidthSize, mCountX);
-            int ch = DeviceProfile.calculateCellHeight(childHeightSize, mCountY);
+            int cw = DeviceProfile.calculateCellWidth(childWidthSize, mBorderSpacing,
+                    mCountX);
+            int ch = DeviceProfile.calculateCellHeight(childHeightSize, mBorderSpacing,
+                    mCountY);
             if (cw != mCellWidth || ch != mCellHeight) {
                 mCellWidth = cw;
                 mCellHeight = ch;
-                mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY);
+                mShortcutsAndWidgets.setCellDimensions(mCellWidth, mCellHeight, mCountX, mCountY,
+                        mBorderSpacing);
             }
         }
 
@@ -838,10 +830,11 @@
     /**
      * Returns the amount of space left over after subtracting padding and cells. This space will be
      * very small, a few pixels at most, and is a result of rounding down when calculating the cell
-     * width in {@link DeviceProfile#calculateCellWidth(int, int)}.
+     * width in {@link DeviceProfile#calculateCellWidth(int, int, int)}.
      */
     public int getUnusedHorizontalSpace() {
-        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth);
+        return getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - (mCountX * mCellWidth)
+                - ((mCountX - 1) * mBorderSpacing);
     }
 
     public Drawable getScrimBackground() {
@@ -857,8 +850,8 @@
         return mShortcutsAndWidgets;
     }
 
-    public View getChildAt(int x, int y) {
-        return mShortcutsAndWidgets.getChildAt(x, y);
+    public View getChildAt(int cellX, int cellY) {
+        return mShortcutsAndWidgets.getChildAt(cellX, cellY);
     }
 
     public boolean animateChildToPosition(final View child, int cellX, int cellY, int duration,
@@ -989,11 +982,11 @@
             }
 
             // Center horizontaly
-            left += ((mCellWidth * spanX) - dragOutline.getWidth()) / 2;
+            left += (r.width() - dragOutline.getWidth()) / 2;
 
             if (v != null && v.getViewType() == DraggableView.DRAGGABLE_WIDGET) {
                 // Center vertically
-                top += ((mCellHeight * spanY) - dragOutline.getHeight()) / 2;
+                top += (r.height() - dragOutline.getHeight()) / 2;
             } else if (v != null && v.getViewType() == DraggableView.DRAGGABLE_ICON) {
                 int cHeight = getShortcutsAndWidgets().getCellContentHeight();
                 int cellPaddingY = (int) Math.max(0, ((mCellHeight - cHeight) / 2f));
@@ -2153,7 +2146,7 @@
 
         findNearestArea(dragViewCenterX, dragViewCenterY, spanX, spanY, targetDestination);
         Rect dragRect = new Rect();
-        regionToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
+        cellToRect(targetDestination[0], targetDestination[1], spanX, spanY, dragRect);
         dragRect.offset(dragViewCenterX - dragRect.centerX(), dragViewCenterY - dragRect.centerY());
 
         Rect dropRegionRect = new Rect();
@@ -2163,7 +2156,7 @@
         int dropRegionSpanX = dropRegionRect.width();
         int dropRegionSpanY = dropRegionRect.height();
 
-        regionToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
+        cellToRect(dropRegionRect.left, dropRegionRect.top, dropRegionRect.width(),
                 dropRegionRect.height(), dropRegionRect);
 
         int deltaX = (dropRegionRect.centerX() - dragViewCenterX) / spanX;
@@ -2521,10 +2514,11 @@
         final int hStartPadding = getPaddingLeft();
         final int vStartPadding = getPaddingTop();
 
-        int width = cellHSpan * cellWidth;
-        int height = cellVSpan * cellHeight;
-        int x = hStartPadding + cellX * cellWidth;
-        int y = vStartPadding + cellY * cellHeight;
+        int x = hStartPadding + (cellX * mBorderSpacing) + (cellX * cellWidth);
+        int y = vStartPadding + (cellY * mBorderSpacing) + (cellY * cellHeight);
+
+        int width = cellHSpan * cellWidth + ((cellHSpan - 1) * mBorderSpacing);
+        int height = cellVSpan * cellHeight + ((cellVSpan - 1) * mBorderSpacing);
 
         resultRect.set(x, y, x + width, y + height);
     }
@@ -2542,11 +2536,13 @@
     }
 
     public int getDesiredWidth() {
-        return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth);
+        return getPaddingLeft() + getPaddingRight() + (mCountX * mCellWidth)
+                + ((mCountX - 1) * mBorderSpacing);
     }
 
     public int getDesiredHeight()  {
-        return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight);
+        return getPaddingTop() + getPaddingBottom() + (mCountY * mCellHeight)
+                + ((mCountY - 1) * mBorderSpacing);
     }
 
     public boolean isOccupied(int x, int y) {
@@ -2661,19 +2657,21 @@
             this.cellVSpan = cellVSpan;
         }
 
-        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount) {
-            setup(cellWidth, cellHeight, invertHorizontally, colCount, 1.0f, 1.0f);
+        public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
+                int rowCount, int borderSpacing) {
+            setup(cellWidth, cellHeight, invertHorizontally, colCount, rowCount, 1.0f, 1.0f,
+                    borderSpacing);
         }
 
         /**
-         * Use this method, as opposed to {@link #setup(int, int, boolean, int)}, if the view needs
-         * to be scaled.
+         * Use this method, as opposed to {@link #setup(int, int, boolean, int, int, int)},
+         * if the view needs to be scaled.
          *
          * ie. In multi-window mode, we setup widgets so that they are measured and laid out
          * using their full/invariant device profile sizes.
          */
         public void setup(int cellWidth, int cellHeight, boolean invertHorizontally, int colCount,
-                float cellScaleX, float cellScaleY) {
+                int rowCount, float cellScaleX, float cellScaleY, int borderSpacing) {
             if (isLockedToGrid) {
                 final int myCellHSpan = cellHSpan;
                 final int myCellVSpan = cellVSpan;
@@ -2684,17 +2682,23 @@
                     myCellX = colCount - myCellX - cellHSpan;
                 }
 
-                width = (int) (myCellHSpan * cellWidth / cellScaleX - leftMargin - rightMargin);
-                height = (int) (myCellVSpan * cellHeight / cellScaleY - topMargin - bottomMargin);
-                x = (myCellX * cellWidth + leftMargin);
-                y = (myCellY * cellHeight + topMargin);
+                int hBorderSpacing = (myCellHSpan - 1) * borderSpacing;
+                int vBorderSpacing = (myCellVSpan - 1) * borderSpacing;
+
+                float myCellWidth = ((myCellHSpan * cellWidth) + hBorderSpacing) / cellScaleX;
+                float myCellHeight = ((myCellVSpan * cellHeight) + vBorderSpacing) / cellScaleY;
+
+                width = Math.round(myCellWidth) - leftMargin - rightMargin;
+                height = Math.round(myCellHeight) - topMargin - bottomMargin;
+                x = leftMargin + (myCellX * cellWidth) + (myCellX * borderSpacing);
+                y = topMargin + (myCellY * cellHeight) + (myCellY * borderSpacing);
             }
         }
 
         /**
          * Sets the position to the provided point
          */
-        public void setXY(Point point) {
+        public void setCellXY(Point point) {
             cellX = point.x;
             cellY = point.y;
         }
diff --git a/src/com/android/launcher3/DevicePaddings.java b/src/com/android/launcher3/DevicePaddings.java
new file mode 100644
index 0000000..4827f36
--- /dev/null
+++ b/src/com/android/launcher3/DevicePaddings.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+/**
+ * Workspace items have a fixed height, so we need a way to distribute any unused workspace height.
+ *
+ * The unused or "extra" height is allocated to three different variable heights:
+ * - The space above the workspace
+ * - The space between the workspace and hotseat
+ * - The espace below the hotseat
+ */
+public class DevicePaddings {
+
+    private static final String DEVICE_PADDING = "device-paddings";
+    private static final String DEVICE_PADDINGS = "device-padding";
+
+    private static final String WORKSPACE_TOP_PADDING = "workspaceTopPadding";
+    private static final String WORKSPACE_BOTTOM_PADDING = "workspaceBottomPadding";
+    private static final String HOTSEAT_BOTTOM_PADDING = "hotseatBottomPadding";
+
+    private static final String TAG = DevicePaddings.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    ArrayList<DevicePadding> mDevicePaddings = new ArrayList<>();
+
+    public DevicePaddings(Context context) {
+        try (XmlResourceParser parser = context.getResources().getXml(R.xml.size_limits)) {
+            final int depth = parser.getDepth();
+            int type;
+            while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                    parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
+                if ((type == XmlPullParser.START_TAG) && DEVICE_PADDING.equals(parser.getName())) {
+                    final int displayDepth = parser.getDepth();
+                    while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                            parser.getDepth() > displayDepth)
+                            && type != XmlPullParser.END_DOCUMENT) {
+                        if ((type == XmlPullParser.START_TAG)
+                                && DEVICE_PADDINGS.equals(parser.getName())) {
+                            TypedArray a = context.obtainStyledAttributes(
+                                    Xml.asAttributeSet(parser), R.styleable.DevicePadding);
+                            int maxWidthPx = a.getDimensionPixelSize(
+                                    R.styleable.DevicePadding_maxEmptySpace, 0);
+                            a.recycle();
+
+                            PaddingFormula workspaceTopPadding = null;
+                            PaddingFormula workspaceBottomPadding = null;
+                            PaddingFormula hotseatBottomPadding = null;
+
+                            final int limitDepth = parser.getDepth();
+                            while (((type = parser.next()) != XmlPullParser.END_TAG ||
+                                    parser.getDepth() > limitDepth)
+                                    && type != XmlPullParser.END_DOCUMENT) {
+                                AttributeSet attr = Xml.asAttributeSet(parser);
+                                if ((type == XmlPullParser.START_TAG)) {
+                                    if (WORKSPACE_TOP_PADDING.equals(parser.getName())) {
+                                        workspaceTopPadding = new PaddingFormula(context, attr);
+                                    } else if (WORKSPACE_BOTTOM_PADDING.equals(parser.getName())) {
+                                        workspaceBottomPadding = new PaddingFormula(context, attr);
+                                    } else if (HOTSEAT_BOTTOM_PADDING.equals(parser.getName())) {
+                                        hotseatBottomPadding = new PaddingFormula(context, attr);
+                                    }
+                                }
+                            }
+
+                            if (workspaceTopPadding == null
+                                    || workspaceBottomPadding == null
+                                    || hotseatBottomPadding == null) {
+                                throw new RuntimeException("DevicePadding missing padding.");
+                            }
+
+                            mDevicePaddings.add(new DevicePadding(maxWidthPx, workspaceTopPadding,
+                                    workspaceBottomPadding, hotseatBottomPadding));
+                        }
+                    }
+                }
+            }
+        } catch (IOException | XmlPullParserException e) {
+            throw new RuntimeException(e);
+        }
+
+        // Sort ascending by maxEmptySpacePx
+        mDevicePaddings.sort((sl1, sl2) -> Integer.compare(sl1.maxEmptySpacePx,
+                sl2.maxEmptySpacePx));
+    }
+
+    public DevicePadding getDevicePadding(int extraSpacePx) {
+        for (DevicePadding limit : mDevicePaddings) {
+            if (extraSpacePx <= limit.maxEmptySpacePx) {
+                return limit;
+            }
+        }
+
+        return mDevicePaddings.get(mDevicePaddings.size() - 1);
+    }
+
+    /**
+     * Holds all the formulas to calculate the padding for a particular device based on the
+     * amount of extra space.
+     */
+    public static final class DevicePadding {
+
+        private final int maxEmptySpacePx;
+        private final PaddingFormula workspaceTopPadding;
+        private final PaddingFormula workspaceBottomPadding;
+        private final PaddingFormula hotseatBottomPadding;
+
+        public DevicePadding(int maxEmptySpacePx,
+                PaddingFormula workspaceTopPadding,
+                PaddingFormula workspaceBottomPadding,
+                PaddingFormula hotseatBottomPadding) {
+            this.maxEmptySpacePx = maxEmptySpacePx;
+            this.workspaceTopPadding = workspaceTopPadding;
+            this.workspaceBottomPadding = workspaceBottomPadding;
+            this.hotseatBottomPadding = hotseatBottomPadding;
+        }
+
+        public int getWorkspaceTopPadding(int extraSpacePx) {
+            return workspaceTopPadding.calculate(extraSpacePx);
+        }
+
+        public int getWorkspaceBottomPadding(int extraSpacePx) {
+            return workspaceBottomPadding.calculate(extraSpacePx);
+        }
+
+        public int getHotseatBottomPadding(int extraSpacePx) {
+            return hotseatBottomPadding.calculate(extraSpacePx);
+        }
+    }
+
+    /**
+     * Used to calculate a padding based on three variables: a, b, and c.
+     *
+     * Calculation: a * (extraSpace - c) + b
+     */
+    private static final class PaddingFormula {
+
+        private final float a;
+        private final float b;
+        private final float c;
+
+        public PaddingFormula(Context context, AttributeSet attrs) {
+            TypedArray t = context.obtainStyledAttributes(attrs,
+                    R.styleable.DevicePaddingFormula);
+
+            a = getValue(t, R.styleable.DevicePaddingFormula_a);
+            b = getValue(t, R.styleable.DevicePaddingFormula_b);
+            c = getValue(t, R.styleable.DevicePaddingFormula_c);
+
+            t.recycle();
+        }
+
+        public int calculate(int extraSpacePx) {
+            if (DEBUG) {
+                Log.d(TAG, "a=" + a + " * (" + extraSpacePx + " - " + c + ") + b=" + b);
+            }
+            return Math.round(a * (extraSpacePx - c) + b);
+        }
+
+        private static float getValue(TypedArray a, int index) {
+            if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
+                return a.getDimensionPixelSize(index, 0);
+            } else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
+                return a.getFloat(index, 0);
+            }
+            return 0;
+        }
+
+        @Override
+        public String toString() {
+            return "a=" + a + ", b=" + b + ", c=" + c;
+        }
+    }
+}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 4d5bd5d..19915b7 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.launcher3;
 
+import static com.android.launcher3.config.FeatureFlags.ENABLE_FOUR_COLUMNS;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -25,6 +27,7 @@
 import android.view.Surface;
 
 import com.android.launcher3.CellLayout.ContainerType;
+import com.android.launcher3.DevicePaddings.DevicePadding;
 import com.android.launcher3.graphics.IconShape;
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
@@ -74,12 +77,16 @@
 
     // Workspace
     public final int desiredWorkspaceLeftRightMarginPx;
+    public final int cellLayoutBorderSpacingPx;
     public final int cellLayoutPaddingLeftRightPx;
     public final int cellLayoutBottomPaddingPx;
     public final int edgeMarginPx;
     public float workspaceSpringLoadShrinkFactor;
     public final int workspaceSpringLoadedBottomSpace;
 
+    public int workspaceTopPadding;
+    public int workspaceBottomPadding;
+
     // Workspace page indicator
     public final int workspacePageIndicatorHeight;
     private final int mWorkspacePageIndicatorOverlapWorkspace;
@@ -92,6 +99,7 @@
 
     public int cellWidthPx;
     public int cellHeightPx;
+    public int cellYPaddingPx;
     public int workspaceCellPaddingXPx;
 
     // Folder
@@ -187,6 +195,11 @@
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         desiredWorkspaceLeftRightMarginPx = isVerticalBarLayout() ? 0 : edgeMarginPx;
 
+        cellYPaddingPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_padding_y);
+        cellLayoutBorderSpacingPx = isVerticalBarLayout()
+                || isMultiWindowMode
+                || !ENABLE_FOUR_COLUMNS.get()
+                ? 0 : res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_border_spacing);
         int cellLayoutPaddingLeftRightMultiplier = !isVerticalBarLayout() && isTablet
                 ? PORTRAIT_TABLET_LEFT_RIGHT_PADDING_MULTIPLIER : 1;
         int cellLayoutPadding = res.getDimensionPixelSize(R.dimen.dynamic_grid_cell_layout_padding);
@@ -220,22 +233,31 @@
                 res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_side_padding);
         // Add a bit of space between nav bar and hotseat in vertical bar layout.
         hotseatBarSidePaddingStartPx = isVerticalBarLayout() ? workspacePageIndicatorHeight : 0;
+        int hotseatExtraVerticalSize =
+                res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size);
         hotseatBarSizePx = ResourceUtils.pxFromDp(inv.iconSize, mInfo.metrics)
                 + (isVerticalBarLayout()
                 ? (hotseatBarSidePaddingStartPx + hotseatBarSidePaddingEndPx)
-                : (res.getDimensionPixelSize(R.dimen.dynamic_grid_hotseat_extra_vertical_size)
-                        + hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx));
+                : (hotseatBarTopPaddingPx + hotseatBarBottomPaddingPx
+                        + (ENABLE_FOUR_COLUMNS.get() ? 0 : hotseatExtraVerticalSize)));
 
         // Calculate all of the remaining variables.
-        updateAvailableDimensions(res);
-
+        int extraSpace = updateAvailableDimensions(res);
         // Now that we have all of the variables calculated, we can tune certain sizes.
-        if (!isVerticalBarLayout() && isPhone && isTallDevice) {
+        if (ENABLE_FOUR_COLUMNS.get()) {
+            DevicePadding padding = inv.devicePaddings.getDevicePadding(extraSpace);
+            workspaceTopPadding = padding.getWorkspaceTopPadding(extraSpace);
+            workspaceBottomPadding = padding.getWorkspaceBottomPadding(extraSpace);
+
+            float hotseatBarBottomPadding = padding.getHotseatBottomPadding(extraSpace);
+            hotseatBarSizePx += hotseatBarBottomPadding;
+            hotseatBarBottomPaddingPx += hotseatBarBottomPadding;
+        } else if (!isVerticalBarLayout() && isPhone && isTallDevice) {
             // We increase the hotseat size when there is extra space.
             // ie. For a display with a large aspect ratio, we can keep the icons on the workspace
             // in portrait mode closer together by adding more height to the hotseat.
             // Note: This calculation was created after noticing a pattern in the design spec.
-            int extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
+            extraSpace = getCellSize().y - iconSizePx - iconDrawablePaddingPx * 2
                     - workspacePageIndicatorHeight;
             hotseatBarSizePx += extraSpace;
             hotseatBarBottomPaddingPx += extraSpace;
@@ -328,17 +350,24 @@
                 + topBottomPadding * 2;
     }
 
-    private void updateAvailableDimensions(Resources res) {
+    /**
+     * Returns the amount of extra (or unused) vertical space.
+     */
+    private int updateAvailableDimensions(Resources res) {
         updateIconSize(1f, res);
 
         // Check to see if the icons fit within the available height.  If not, then scale down.
-        float usedHeight = (cellHeightPx * inv.numRows);
+        float usedHeight = (cellHeightPx * inv.numRows)
+                + (cellLayoutBorderSpacingPx * (inv.numRows - 1));
         int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y);
+        float extraHeight = Math.max(0, maxHeight - usedHeight);
         if (usedHeight > maxHeight) {
             float scale = maxHeight / usedHeight;
             updateIconSize(scale, res);
+            extraHeight = 0;
         }
         updateAvailableFolderCellDimensions(res);
+        return Math.round(extraHeight);
     }
 
     /**
@@ -355,16 +384,23 @@
         iconTextSizePx = (int) (Utilities.pxFromSp(inv.iconTextSize, mInfo.metrics) * scale);
         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * scale);
 
-        cellHeightPx = iconSizePx + iconDrawablePaddingPx
-                + Utilities.calculateTextHeight(iconTextSizePx);
-        int cellYPadding = (getCellSize().y - cellHeightPx) / 2;
-        if (iconDrawablePaddingPx > cellYPadding && !isVerticalLayout
-                && !isMultiWindowMode) {
-            // Ensures that the label is closer to its corresponding icon. This is not an issue
-            // with vertical bar layout or multi-window mode since the issue is handled separately
-            // with their calls to {@link #adjustToHideWorkspaceLabels}.
-            cellHeightPx -= (iconDrawablePaddingPx - cellYPadding);
-            iconDrawablePaddingPx = cellYPadding;
+        if (ENABLE_FOUR_COLUMNS.get()) {
+            cellHeightPx = iconSizePx + iconDrawablePaddingPx
+                    + Utilities.calculateTextHeight(iconTextSizePx)
+                    + (cellYPaddingPx * 2);
+        } else {
+            cellYPaddingPx = 0;
+            cellHeightPx = iconSizePx + iconDrawablePaddingPx
+                    + Utilities.calculateTextHeight(iconTextSizePx);
+            int cellPaddingY = (getCellSize().y - cellHeightPx) / 2;
+            if (iconDrawablePaddingPx > cellPaddingY && !isVerticalLayout
+                    && !isMultiWindowMode) {
+                // Ensures that the label is closer to its corresponding icon. This is not an issue
+                // with vertical bar layout or multi-window mode since the issue is handled
+                // separately with their calls to {@link #adjustToHideWorkspaceLabels}.
+                cellHeightPx -= (iconDrawablePaddingPx - cellPaddingY);
+                iconDrawablePaddingPx = cellPaddingY;
+            }
         }
         cellWidthPx = iconSizePx + iconDrawablePaddingPx;
 
@@ -425,13 +461,15 @@
         Point totalWorkspacePadding = getTotalWorkspacePadding();
 
         // Check if the icons fit within the available height.
-        float contentUsedHeight = folderCellHeightPx * inv.numFolderRows;
+        float contentUsedHeight = folderCellHeightPx * inv.numFolderRows
+                + ((inv.numFolderRows - 1) * cellLayoutBorderSpacingPx);
         int contentMaxHeight = availableHeightPx - totalWorkspacePadding.y - folderBottomPanelSize
                 - folderMargin;
         float scaleY = contentMaxHeight / contentUsedHeight;
 
         // Check if the icons fit within the available width.
-        float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns;
+        float contentUsedWidth = folderCellWidthPx * inv.numFolderColumns
+                + ((inv.numFolderColumns - 1) * cellLayoutBorderSpacingPx);
         int contentMaxWidth = availableWidthPx - totalWorkspacePadding.x - folderMargin;
         float scaleX = contentMaxWidth / contentUsedWidth;
 
@@ -479,9 +517,9 @@
         // not matter.
         Point padding = getTotalWorkspacePadding();
         result.x = calculateCellWidth(availableWidthPx - padding.x
-                - cellLayoutPaddingLeftRightPx * 2, numColumns);
+                - cellLayoutPaddingLeftRightPx * 2, cellLayoutBorderSpacingPx, numColumns);
         result.y = calculateCellHeight(availableHeightPx - padding.y
-                - cellLayoutBottomPaddingPx, numRows);
+                - cellLayoutBottomPaddingPx, cellLayoutBorderSpacingPx, numRows);
         return result;
     }
 
@@ -509,7 +547,7 @@
             }
         } else {
             int paddingBottom = hotseatBarSizePx + workspacePageIndicatorHeight
-                    - mWorkspacePageIndicatorOverlapWorkspace;
+                    + workspaceBottomPadding - mWorkspacePageIndicatorOverlapWorkspace;
             if (isTablet) {
                 // Pad the left and right of the workspace to ensure consistent spacing
                 // between all icons
@@ -526,7 +564,7 @@
             } else {
                 // Pad the top and bottom of the workspace with search/hotseat bar sizes
                 padding.set(desiredWorkspaceLeftRightMarginPx,
-                        edgeMarginPx,
+                        workspaceTopPadding + edgeMarginPx,
                         desiredWorkspaceLeftRightMarginPx,
                         paddingBottom);
             }
@@ -581,11 +619,11 @@
         }
     }
 
-    public static int calculateCellWidth(int width, int countX) {
-        return width / countX;
+    public static int calculateCellWidth(int width, int borderSpacing, int countX) {
+        return (width - ((countX - 1) * borderSpacing)) / countX;
     }
-    public static int calculateCellHeight(int height, int countY) {
-        return height / countY;
+    public static int calculateCellHeight(int height, int borderSpacing, int countY) {
+        return (height - ((countY - 1) * borderSpacing)) / countY;
     }
 
     /**
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index aa3ef9b..2a08c50 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -130,6 +130,8 @@
     public DeviceProfile landscapeProfile;
     public DeviceProfile portraitProfile;
 
+    public DevicePaddings devicePaddings;
+
     public Point defaultWallpaperSize;
     public Rect defaultWidgetPadding;
 
@@ -159,6 +161,7 @@
         demoModeLayoutId = p.demoModeLayoutId;
         mExtraAttrs = p.mExtraAttrs;
         mOverlayMonitor = p.mOverlayMonitor;
+        devicePaddings = p.devicePaddings;
     }
 
     @TargetApi(23)
@@ -210,6 +213,8 @@
         result.landscapeIconSize = defaultDisplayOption.landscapeIconSize;
         result.allAppsIconSize = Math.min(
                 defaultDisplayOption.allAppsIconSize, myDisplayOption.allAppsIconSize);
+
+        devicePaddings = new DevicePaddings(context);
         initGrid(context, myInfo, result);
     }
 
@@ -237,6 +242,7 @@
         ArrayList<DisplayOption> allOptions = getPredefinedDeviceProfiles(context, gridName);
 
         DisplayOption displayOption = invDistWeightedInterpolate(displayInfo, allOptions);
+        devicePaddings = new DevicePaddings(context);
         initGrid(context, displayInfo, displayOption);
         return displayOption.grid.name;
     }
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index ee0c7bb..1c5081c 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -42,8 +42,10 @@
 
     private int mCellWidth;
     private int mCellHeight;
+    private int mBorderSpacing;
 
     private int mCountX;
+    private int mCountY;
 
     private final ActivityContext mActivity;
     private boolean mInvertIfRtl = false;
@@ -55,20 +57,23 @@
         mContainerType = containerType;
     }
 
-    public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY) {
+    public void setCellDimensions(int cellWidth, int cellHeight, int countX, int countY,
+            int borderSpacing) {
         mCellWidth = cellWidth;
         mCellHeight = cellHeight;
         mCountX = countX;
+        mCountY = countY;
+        mBorderSpacing = borderSpacing;
     }
 
-    public View getChildAt(int x, int y) {
+    public View getChildAt(int cellX, int cellY) {
         final int count = getChildCount();
         for (int i = 0; i < count; i++) {
             View child = getChildAt(i);
             CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
 
-            if ((lp.cellX <= x) && (x < lp.cellX + lp.cellHSpan) &&
-                    (lp.cellY <= y) && (y < lp.cellY + lp.cellVSpan)) {
+            if ((lp.cellX <= cellX) && (cellX < lp.cellX + lp.cellHSpan)
+                    && (lp.cellY <= cellY) && (cellY < lp.cellY + lp.cellVSpan)) {
                 return child;
             }
         }
@@ -95,10 +100,11 @@
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
         if (child instanceof LauncherAppWidgetHostView) {
             DeviceProfile profile = mActivity.getDeviceProfile();
-            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
-                    profile.appWidgetScale.x, profile.appWidgetScale.y);
+            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+                    profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing);
         } else {
-            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+                    mBorderSpacing);
         }
     }
 
@@ -117,11 +123,12 @@
         final DeviceProfile profile = mActivity.getDeviceProfile();
 
         if (child instanceof LauncherAppWidgetHostView) {
-            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX,
-                    profile.appWidgetScale.x, profile.appWidgetScale.y);
+            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+                    profile.appWidgetScale.x, profile.appWidgetScale.y, mBorderSpacing);
             // Widgets have their own padding
         } else {
-            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX);
+            lp.setup(mCellWidth, mCellHeight, invertLayoutHorizontally(), mCountX, mCountY,
+                    mBorderSpacing);
             // Center the icon/folder
             int cHeight = getCellContentHeight();
             int cellPaddingY = (int) Math.max(0, ((lp.height - cHeight) / 2f));
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 4f79fb8..aef32d7 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -109,7 +109,8 @@
         int rowWidth = myRequestedWidth - mAppsView.getActiveRecyclerView().getPaddingLeft()
                 - mAppsView.getActiveRecyclerView().getPaddingRight();
 
-        int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.inv.numHotseatIcons);
+        int cellWidth = DeviceProfile.calculateCellWidth(rowWidth, dp.cellLayoutBorderSpacingPx,
+                dp.inv.numHotseatIcons);
         int iconVisibleSize = Math.round(ICON_VISIBLE_AREA_FACTOR * dp.iconSizePx);
         int iconPadding = cellWidth - iconVisibleSize;
 
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index fe310f6..6477de3 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -177,6 +177,8 @@
         icon.setFolder(folder);
 
         icon.setOnFocusChangeListener(launcher.getFocusHandler());
+        icon.mBackground.paddingY = icon.isInHotseat()
+                ? 0 : launcher.getDeviceProfile().cellYPaddingPx;
         return icon;
     }
 
@@ -199,7 +201,7 @@
         icon.mFolderName.setText(folderInfo.title);
         icon.mFolderName.setCompoundDrawablePadding(0);
         FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) icon.mFolderName.getLayoutParams();
-        lp.topMargin = grid.iconSizePx + grid.iconDrawablePaddingPx;
+        lp.topMargin = grid.cellYPaddingPx + grid.iconSizePx + grid.iconDrawablePaddingPx;
 
         icon.setTag(folderInfo);
         icon.setOnClickListener(ItemClickHandler.INSTANCE);
@@ -218,6 +220,7 @@
 
         icon.setAccessibilityDelegate(activity.getAccessibilityDelegate());
 
+        icon.mBackground.paddingY = icon.isInHotseat() ? 0 : grid.cellYPaddingPx;
         icon.mPreviewVerifier = new FolderGridOrganizer(activity.getDeviceProfile().inv);
         icon.mPreviewVerifier.setFolderInfo(folderInfo);
         icon.updatePreviewItems(false);
@@ -579,6 +582,7 @@
     public void setFolderBackground(PreviewBackground bg) {
         mBackground = bg;
         mBackground.setInvalidateDelegate(this);
+        mBackground.paddingY = isInHotseat() ? 0 : mActivity.getDeviceProfile().cellYPaddingPx;
     }
 
     @Override
@@ -745,9 +749,13 @@
         mInfo.removeListener(mFolder);
     }
 
+    private boolean isInHotseat() {
+        return mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT;
+    }
+
     public void clearLeaveBehindIfExists() {
         ((CellLayout.LayoutParams) getLayoutParams()).canReorder = true;
-        if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+        if (isInHotseat()) {
             CellLayout cl = (CellLayout) getParent().getParent();
             cl.clearFolderLeaveBehind();
         }
@@ -757,7 +765,7 @@
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) getLayoutParams();
         // While the folder is open, the position of the icon cannot change.
         lp.canReorder = false;
-        if (mInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
+        if (isInHotseat()) {
             CellLayout cl = (CellLayout) getParent().getParent();
             cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
         }
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index a08dd30..df3e92c 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -193,7 +193,7 @@
         int pageNo = rank / mOrganizer.getMaxItemsPerPage();
 
         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) view.getLayoutParams();
-        lp.setXY(mOrganizer.getPosForRank(rank));
+        lp.setCellXY(mOrganizer.getPosForRank(rank));
         getPageAt(pageNo).addViewToCellLayout(view, -1, item.getViewId(), lp, true);
     }
 
@@ -306,7 +306,7 @@
             if (v != null) {
                 CellLayout.LayoutParams lp = (CellLayout.LayoutParams) v.getLayoutParams();
                 ItemInfo info = (ItemInfo) v.getTag();
-                lp.setXY(mOrganizer.getPosForRank(rank));
+                lp.setCellXY(mOrganizer.getPosForRank(rank));
                 currentPage.addViewToCellLayout(v, -1, info.getViewId(), lp, true);
 
                 if (mOrganizer.isItemInPreview(rank) && v instanceof BubbleTextView) {
diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java
index 27b906b..767fffe 100644
--- a/src/com/android/launcher3/folder/PreviewBackground.java
+++ b/src/com/android/launcher3/folder/PreviewBackground.java
@@ -74,6 +74,7 @@
     int previewSize;
     int basePreviewOffsetX;
     int basePreviewOffsetY;
+    int paddingY;
 
     private CellLayout mDrawingDelegate;
 
@@ -157,7 +158,7 @@
         previewSize = grid.folderIconSizePx;
 
         basePreviewOffsetX = (availableSpaceX - previewSize) / 2;
-        basePreviewOffsetY = topPadding + grid.folderIconOffsetYPx;
+        basePreviewOffsetY = paddingY + topPadding + grid.folderIconOffsetYPx;
 
         // Stroke width is 1dp
         mStrokeWidth = context.getResources().getDisplayMetrics().density;