Implement calculations of Responsive Grid in DeviceProfile

When a grid has a responsive spec and the feature flag is on, use the new calculations for the sizes in Workspace. This shouldn't affect Scalable grids.

Fix: 277064708
Test: HomeScreenImageTest
Test: DeviceProfileDumpTest
Test: ResponsiveHomeScreenImageTest
Test: DeviceProfileResponsiveDumpTest
Flag: ENABLE_RESPONSIVE_WORKSPACE
Change-Id: Idef3d17fbfa1b2e0c82c12e7784a7584f1ba1b88
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index f920d75..05d434e 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -210,7 +210,7 @@
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.iconTextSizePx);
             setCompoundDrawablePadding(grid.iconDrawablePaddingPx);
             defaultIconSize = grid.iconSizePx;
-            setCenterVertically(grid.isScalableGrid);
+            setCenterVertically(grid.iconCenterVertically);
         } else if (mDisplay == DISPLAY_ALL_APPS) {
             setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.allAppsIconTextSizePx);
             setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx);
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index fb41044..1085e4c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -103,7 +103,7 @@
 
     public final float aspectRatio;
 
-    public final boolean isScalableGrid;
+    private final boolean mIsScalableGrid;
     private final int mTypeIndex;
 
     // Responsive grid
@@ -152,13 +152,14 @@
     public int iconTextSizePx;
     public int iconDrawablePaddingPx;
     public int iconDrawablePaddingOriginalPx;
+    public boolean iconCenterVertically;
 
     public float cellScaleToFit;
     public int cellWidthPx;
     public int cellHeightPx;
     public int workspaceCellPaddingXPx;
 
-    public int cellYPaddingPx;
+    public int cellYPaddingPx = -1;
 
     // Folder
     public float folderLabelTextScale;
@@ -305,7 +306,7 @@
         // TODO(b/241386436): shouldn't change any launcher behaviour
         mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE;
 
-        isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
+        mIsScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
         // Determine device posture.
         mInfo = info;
         isTablet = info.isTablet(windowBounds);
@@ -342,14 +343,6 @@
             }
         }
 
-        if (mIsResponsiveGrid) {
-            mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId));
-            mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns,
-                    availableWidthPx);
-            mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows,
-                    availableHeightPx);
-        }
-
         if (DisplayController.isTransientTaskbar(context)) {
             float invTransientIconSizeDp = inv.transientTaskbarIconSize[mTypeIndex];
             taskbarIconSize = pxFromDp(invTransientIconSizeDp, mMetrics);
@@ -372,8 +365,6 @@
         edgeMarginPx = res.getDimensionPixelSize(R.dimen.dynamic_grid_edge_margin);
         workspaceContentScale = res.getFloat(R.dimen.workspace_content_scale);
 
-        desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
-        desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
         gridVisualizationPaddingX = res.getDimensionPixelSize(
                 R.dimen.grid_visualization_horizontal_cell_spacing);
         gridVisualizationPaddingY = res.getDimensionPixelSize(
@@ -406,7 +397,7 @@
 
         folderLabelTextScale = res.getFloat(R.dimen.folder_label_text_scale);
 
-        if (isScalableGrid && inv.folderStyle != INVALID_RESOURCE_HANDLE) {
+        if (mIsScalableGrid && inv.folderStyle != INVALID_RESOURCE_HANDLE) {
             TypedArray folderStyle = context.obtainStyledAttributes(inv.folderStyle,
                     R.styleable.FolderStyle);
             // These are re-set in #updateFolderCellSize if the grid is not scalable
@@ -428,8 +419,6 @@
             folderContentPaddingTop = res.getDimensionPixelSize(R.dimen.folder_top_padding_default);
         }
 
-        cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv);
-        cellLayoutBorderSpaceOriginalPx = new Point(cellLayoutBorderSpacePx);
         allAppsBorderSpacePx = new Point(
                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].x, mMetrics),
                 pxFromDp(inv.allAppsBorderSpaces[mTypeIndex].y, mMetrics));
@@ -479,7 +468,7 @@
                 || inv.inlineQsb[INDEX_TWO_PANEL_LANDSCAPE]
                 : inv.inlineQsb[INDEX_DEFAULT] || inv.inlineQsb[INDEX_LANDSCAPE])
                 && hotseatQsbHeight > 0;
-        isQsbInline = isScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline;
+        isQsbInline = mIsScalableGrid && inv.inlineQsb[mTypeIndex] && canQsbInline;
 
         areNavButtonsInline = isTaskbarPresent && !isGestureMode;
         numShownHotseatIcons =
@@ -534,6 +523,21 @@
             hotseatBarEndOffset = 0;
         }
 
+        // Needs to be calculated after hotseatBarSizePx is correct,
+        // for the available height to be correct
+        if (mIsResponsiveGrid) {
+            mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId));
+            mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns,
+                    availableWidthPx);
+            mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows,
+                    // don't use availableHeightPx because it subtracts bottom padding,
+                    // but the hotseat go behind it
+                    heightPx - mInsets.top - hotseatBarSizePx);
+        }
+
+        desiredWorkspaceHorizontalMarginPx = getHorizontalMarginPx(inv, res);
+        desiredWorkspaceHorizontalMarginOriginalPx = desiredWorkspaceHorizontalMarginPx;
+
         overviewTaskMarginPx = res.getDimensionPixelSize(R.dimen.overview_task_margin);
         overviewTaskIconSizePx = res.getDimensionPixelSize(R.dimen.task_thumbnail_icon_size);
         overviewTaskIconDrawableSizePx =
@@ -554,21 +558,7 @@
         // Calculate all of the remaining variables.
         extraSpace = updateAvailableDimensions(res);
 
-        // Now that we have all of the variables calculated, we can tune certain sizes.
-        if (isScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) {
-            // Paddings were created assuming no scaling, so we first unscale the extra space.
-            int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
-            DevicePaddings devicePaddings = new DevicePaddings(context, inv.devicePaddingId);
-            DevicePadding padding = devicePaddings.getDevicePadding(unscaledExtraSpace);
-            maxEmptySpace = padding.getMaxEmptySpacePx();
-
-            int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
-            int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
-            int paddingHotseatBottom = padding.getHotseatBottomPadding(unscaledExtraSpace);
-
-            workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
-            workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
-        }
+        calculateAndSetWorkspaceVerticalPadding(context, inv, extraSpace);
 
         int cellLayoutPadding =
                 isTwoPanels ? cellLayoutBorderSpacePx.x / 2 : res.getDimensionPixelSize(
@@ -649,15 +639,40 @@
     }
 
     private int getHorizontalMarginPx(InvariantDeviceProfile idp, Resources res) {
+        if (mIsResponsiveGrid) {
+            return mResponsiveWidthSpec.getStartPaddingPx();
+        }
+
         if (isVerticalBarLayout()) {
             return 0;
         }
 
-        return isScalableGrid
+        return mIsScalableGrid
                 ? pxFromDp(idp.horizontalMargin[mTypeIndex], mMetrics)
                 : res.getDimensionPixelSize(R.dimen.dynamic_grid_left_right_margin);
     }
 
+    private void calculateAndSetWorkspaceVerticalPadding(Context context,
+            InvariantDeviceProfile inv,
+            int extraSpace) {
+        if (mIsResponsiveGrid) {
+            workspaceTopPadding = mResponsiveHeightSpec.getStartPaddingPx();
+            workspaceBottomPadding = mResponsiveHeightSpec.getEndPaddingPx();
+        } else if (mIsScalableGrid && inv.devicePaddingId != INVALID_RESOURCE_HANDLE) {
+            // Paddings were created assuming no scaling, so we first unscale the extra space.
+            int unscaledExtraSpace = (int) (extraSpace / cellScaleToFit);
+            DevicePaddings devicePaddings = new DevicePaddings(context, inv.devicePaddingId);
+            DevicePadding padding = devicePaddings.getDevicePadding(unscaledExtraSpace);
+            maxEmptySpace = padding.getMaxEmptySpacePx();
+
+            int paddingWorkspaceTop = padding.getWorkspaceTopPadding(unscaledExtraSpace);
+            int paddingWorkspaceBottom = padding.getWorkspaceBottomPadding(unscaledExtraSpace);
+
+            workspaceTopPadding = Math.round(paddingWorkspaceTop * cellScaleToFit);
+            workspaceBottomPadding = Math.round(paddingWorkspaceBottom * cellScaleToFit);
+        }
+    }
+
     /** Updates hotseatCellHeightPx and hotseatBarSizePx */
     private void updateHotseatSizes(int hotseatIconSizePx) {
         // Ensure there is enough space for folder icons, which have a slightly larger radius.
@@ -682,7 +697,7 @@
      * necessary.
      */
     public void recalculateHotseatWidthAndBorderSpace() {
-        if (!isScalableGrid) return;
+        if (!mIsScalableGrid) return;
 
         int columns = inv.hotseatColumnSpan[mTypeIndex];
         float hotseatWidthPx = getIconToIconWidthForColumns(columns);
@@ -735,12 +750,16 @@
     }
 
     private Point getCellLayoutBorderSpace(InvariantDeviceProfile idp, float scale) {
-        if (!isScalableGrid) {
-            return new Point(0, 0);
-        }
+        int horizontalSpacePx = 0;
+        int verticalSpacePx = 0;
 
-        int horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
-        int verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
+        if (mIsResponsiveGrid) {
+            horizontalSpacePx = mResponsiveWidthSpec.getGutterPx();
+            verticalSpacePx = mResponsiveHeightSpec.getGutterPx();
+        } else if (mIsScalableGrid) {
+            horizontalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].x, mMetrics, scale);
+            verticalSpacePx = pxFromDp(idp.borderSpaces[mTypeIndex].y, mMetrics, scale);
+        }
 
         return new Point(horizontalSpacePx, verticalSpacePx);
     }
@@ -861,6 +880,7 @@
         float invIconTextSizeSp = inv.iconTextSize[mTypeIndex];
         iconSizePx = Math.max(1, pxFromDp(invIconSizeDp, mMetrics));
         iconTextSizePx = pxFromSp(invIconTextSizeSp, mMetrics);
+        iconCenterVertically = mIsScalableGrid || mIsResponsiveGrid;
 
         updateIconSize(1f, res);
 
@@ -874,7 +894,7 @@
         boolean shouldScale = scaleY < 1f;
 
         float scaleX = 1f;
-        if (isScalableGrid) {
+        if (mIsScalableGrid) {
             // We scale to fit the cellWidth and cellHeight in the available space.
             // The benefit of scalable grids is that we can get consistent aspect ratios between
             // devices.
@@ -919,8 +939,18 @@
         final boolean isVerticalLayout = isVerticalBarLayout();
         iconDrawablePaddingPx = (int) (iconDrawablePaddingOriginalPx * iconScale);
         cellLayoutBorderSpacePx = getCellLayoutBorderSpace(inv, scale);
+        int cellTextAndPaddingHeight =
+                iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
 
-        if (isScalableGrid) {
+        if (mIsResponsiveGrid) {
+            int cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
+
+            cellWidthPx = mResponsiveWidthSpec.getCellSizePx();
+            cellHeightPx = mResponsiveHeightSpec.getCellSizePx();
+            cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
+
+            // TODO(b/283929701): decrease icon size if content doesn't fit on cell
+        } else if (mIsScalableGrid) {
             cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
             cellHeightPx = pxFromDp(inv.minCellSize[mTypeIndex].y, mMetrics, scale);
 
@@ -942,8 +972,6 @@
                 }
             }
 
-            int cellTextAndPaddingHeight =
-                    iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
             int cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
             if (cellHeightPx < cellContentHeight) {
                 // If cellHeight no longer fit iconSize, reduce borderSpace to make cellHeight
@@ -1041,7 +1069,7 @@
                 + allAppsBorderSpacePx.y;
         // but width is just the cell,
         // the border is added in #updateAllAppsContainerWidth
-        if (isScalableGrid) {
+        if (mIsScalableGrid) {
             allAppsIconSizePx = pxFromDp(inv.allAppsIconSize[mTypeIndex], mMetrics);
             allAppsIconTextSizePx = pxFromSp(inv.allAppsIconTextSize[mTypeIndex], mMetrics);
             allAppsIconDrawablePaddingPx = iconDrawablePaddingOriginalPx;
@@ -1124,7 +1152,7 @@
 
         int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx);
 
-        if (isScalableGrid) {
+        if (mIsScalableGrid) {
             if (inv.folderStyle == INVALID_RESOURCE_HANDLE) {
                 folderCellWidthPx = roundPxValueFromFloat(getCellSize().x * scale);
                 folderCellHeightPx = roundPxValueFromFloat(getCellSize().y * scale);
@@ -1299,10 +1327,12 @@
         } else {
             // Pad the bottom of the workspace with hotseat bar
             // and leave a bit of space in case a widget go all the way down
-            int paddingBottom = hotseatBarSizePx + workspaceBottomPadding
-                    + workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace
-                    - mInsets.bottom;
-            int paddingTop = workspaceTopPadding + (isScalableGrid ? 0 : edgeMarginPx);
+            int paddingBottom = hotseatBarSizePx + workspaceBottomPadding - mInsets.bottom;
+            if (!mIsResponsiveGrid) {
+                paddingBottom +=
+                        workspacePageIndicatorHeight - mWorkspacePageIndicatorOverlapWorkspace;
+            }
+            int paddingTop = workspaceTopPadding + (mIsScalableGrid ? 0 : edgeMarginPx);
             int paddingSide = desiredWorkspaceHorizontalMarginPx;
 
             padding.set(paddingSide, paddingTop, paddingSide, paddingBottom);
@@ -1378,7 +1408,7 @@
                 hotseatBarPadding.right = endSpacing;
             }
 
-        } else if (isScalableGrid) {
+        } else if (mIsScalableGrid) {
             int sideSpacing = (availableWidthPx - hotseatQsbWidth) / 2;
             hotseatBarPadding.set(sideSpacing,
                     0,
@@ -1598,7 +1628,7 @@
         writer.println(prefix + "\taspectRatio:" + aspectRatio);
 
         writer.println(prefix + "\tisResponsiveGrid:" + mIsResponsiveGrid);
-        writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
+        writer.println(prefix + "\tisScalableGrid:" + mIsScalableGrid);
 
         writer.println(prefix + "\tinv.numRows: " + inv.numRows);
         writer.println(prefix + "\tinv.numColumns: " + inv.numColumns);
@@ -1752,6 +1782,10 @@
                 getWorkspaceSpringLoadScale(context)));
         writer.println(prefix + pxToDpStr("getCellLayoutHeight()", getCellLayoutHeight()));
         writer.println(prefix + pxToDpStr("getCellLayoutWidth()", getCellLayoutWidth()));
+        if (mIsResponsiveGrid) {
+            writer.println(prefix + "\tmResponsiveHeightSpec:" + mResponsiveHeightSpec.toString());
+            writer.println(prefix + "\tmResponsiveWidthSpec:" + mResponsiveWidthSpec.toString());
+        }
     }
 
     /** Returns a reduced representation of this DeviceProfile. */
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index a0ceefb..ba6dc26 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -154,9 +154,10 @@
                     mBorderSpace);
             // Center the icon/folder
             int cHeight = getCellContentHeight();
-            int cellPaddingY = dp.isScalableGrid && mContainerType == WORKSPACE
-                    ? dp.cellYPaddingPx
-                    : (int) Math.max(0, ((lp.height - cHeight) / 2f));
+            int cellPaddingY =
+                    dp.cellYPaddingPx >= 0 && mContainerType == WORKSPACE
+                            ? dp.cellYPaddingPx
+                            : (int) Math.max(0, ((lp.height - cHeight) / 2f));
 
             // No need to add padding when cell layout border spacing is present.
             boolean noPaddingX =
diff --git a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
index ac0a166..dc5ae47 100644
--- a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
+++ b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
@@ -231,6 +231,13 @@
         if (workspaceSpec.cellSize.ofRemainderSpace > 0)
             cellSizePx = (workspaceSpec.cellSize.ofRemainderSpace * remainderSpace).roundToInt()
     }
+
+    override fun toString(): String {
+        return "CalculatedWorkspaceSpec(availableSpace=$availableSpace, " +
+            "cells=$cells, startPaddingPx=$startPaddingPx, endPaddingPx=$endPaddingPx, " +
+            "gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
+            "workspaceSpec.maxAvailableSize=${workspaceSpec.maxAvailableSize})"
+    }
 }
 
 data class WorkspaceSpec(