Calculate sizes for responsive grid

This shouldn't change anything in the grids, only calculate the sizes of the grid.

Bug: 277064708
Test: CalculatedWorkspaceSpecTest
Test: WorkspaceSpecsTest
Flag: ENABLE_RESPONSIVE_WORKSPACE
Change-Id: Id1f90ef44f5b869113d063bad17589e7e88d1d20
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 0231090..fb41044 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -55,7 +55,10 @@
 import com.android.launcher3.uioverrides.ApiWrapper;
 import com.android.launcher3.util.DisplayController;
 import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.ResourceHelper;
 import com.android.launcher3.util.WindowBounds;
+import com.android.launcher3.workspace.CalculatedWorkspaceSpec;
+import com.android.launcher3.workspace.WorkspaceSpecs;
 
 import java.io.PrintWriter;
 import java.util.Locale;
@@ -101,9 +104,14 @@
     public final float aspectRatio;
 
     public final boolean isScalableGrid;
-    public final boolean isResponsiveGrid;
     private final int mTypeIndex;
 
+    // Responsive grid
+    private final boolean mIsResponsiveGrid;
+    private WorkspaceSpecs mWorkspaceSpecs;
+    private CalculatedWorkspaceSpec mResponsiveWidthSpec;
+    private CalculatedWorkspaceSpec mResponsiveHeightSpec;
+
     /**
      * The maximum amount of left/right workspace padding as a percentage of the screen width.
      * To be clear, this means that up to 7% of the screen width can be used as left padding, and
@@ -294,9 +302,8 @@
         this.rotationHint = windowBounds.rotationHint;
         mInsets.set(windowBounds.insets);
 
-        // TODO(b/241386436):
-        //  for testing that the flag works only, shouldn't change any launcher behaviour
-        isResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE;
+        // TODO(b/241386436): shouldn't change any launcher behaviour
+        mIsResponsiveGrid = inv.workspaceSpecsId != INVALID_RESOURCE_HANDLE;
 
         isScalableGrid = inv.isScalable && !isVerticalBarLayout() && !isMultiWindowMode;
         // Determine device posture.
@@ -335,6 +342,14 @@
             }
         }
 
+        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);
@@ -1582,7 +1597,7 @@
 
         writer.println(prefix + "\taspectRatio:" + aspectRatio);
 
-        writer.println(prefix + "\tisResponsiveGrid:" + isResponsiveGrid);
+        writer.println(prefix + "\tisResponsiveGrid:" + mIsResponsiveGrid);
         writer.println(prefix + "\tisScalableGrid:" + isScalableGrid);
 
         writer.println(prefix + "\tinv.numRows: " + inv.numRows);
diff --git a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
index 971fd9b..ac0a166 100644
--- a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
+++ b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
@@ -25,6 +25,7 @@
 import com.android.launcher3.R
 import com.android.launcher3.util.ResourceHelper
 import java.io.IOException
+import kotlin.math.roundToInt
 import org.xmlpull.v1.XmlPullParser
 import org.xmlpull.v1.XmlPullParserException
 
@@ -159,6 +160,77 @@
             }
         }
     }
+
+    /**
+     * Returns the CalculatedWorkspaceSpec for width, based on the available width and the
+     * WorkspaceSpecs.
+     */
+    fun getCalculatedWidthSpec(columns: Int, availableWidth: Int): CalculatedWorkspaceSpec {
+        val widthSpec = workspaceWidthSpecList.first { availableWidth <= it.maxAvailableSize }
+
+        return CalculatedWorkspaceSpec(availableWidth, columns, widthSpec)
+    }
+
+    /**
+     * Returns the CalculatedWorkspaceSpec for height, based on the available height and the
+     * WorkspaceSpecs.
+     */
+    fun getCalculatedHeightSpec(rows: Int, availableHeight: Int): CalculatedWorkspaceSpec {
+        val heightSpec = workspaceHeightSpecList.first { availableHeight <= it.maxAvailableSize }
+
+        return CalculatedWorkspaceSpec(availableHeight, rows, heightSpec)
+    }
+}
+
+class CalculatedWorkspaceSpec(
+    val availableSpace: Int,
+    val cells: Int,
+    val workspaceSpec: WorkspaceSpec
+) {
+    var startPaddingPx: Int = 0
+        private set
+    var endPaddingPx: Int = 0
+        private set
+    var gutterPx: Int = 0
+        private set
+    var cellSizePx: Int = 0
+        private set
+    init {
+        // Calculate all fixed size first
+        if (workspaceSpec.startPadding.fixedSize > 0)
+            startPaddingPx = workspaceSpec.startPadding.fixedSize.roundToInt()
+        if (workspaceSpec.endPadding.fixedSize > 0)
+            endPaddingPx = workspaceSpec.endPadding.fixedSize.roundToInt()
+        if (workspaceSpec.gutter.fixedSize > 0)
+            gutterPx = workspaceSpec.gutter.fixedSize.roundToInt()
+        if (workspaceSpec.cellSize.fixedSize > 0)
+            cellSizePx = workspaceSpec.cellSize.fixedSize.roundToInt()
+
+        // Calculate all available space next
+        if (workspaceSpec.startPadding.ofAvailableSpace > 0)
+            startPaddingPx =
+                (workspaceSpec.startPadding.ofAvailableSpace * availableSpace).roundToInt()
+        if (workspaceSpec.endPadding.ofAvailableSpace > 0)
+            endPaddingPx = (workspaceSpec.endPadding.ofAvailableSpace * availableSpace).roundToInt()
+        if (workspaceSpec.gutter.ofAvailableSpace > 0)
+            gutterPx = (workspaceSpec.gutter.ofAvailableSpace * availableSpace).roundToInt()
+        if (workspaceSpec.cellSize.ofAvailableSpace > 0)
+            cellSizePx = (workspaceSpec.cellSize.ofAvailableSpace * availableSpace).roundToInt()
+
+        // Calculate remainder space last
+        val gutters = cells - 1
+        val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
+        val remainderSpace = availableSpace - usedSpace
+        if (workspaceSpec.startPadding.ofRemainderSpace > 0)
+            startPaddingPx =
+                (workspaceSpec.startPadding.ofRemainderSpace * remainderSpace).roundToInt()
+        if (workspaceSpec.endPadding.ofRemainderSpace > 0)
+            endPaddingPx = (workspaceSpec.endPadding.ofRemainderSpace * remainderSpace).roundToInt()
+        if (workspaceSpec.gutter.ofRemainderSpace > 0)
+            gutterPx = (workspaceSpec.gutter.ofRemainderSpace * remainderSpace).roundToInt()
+        if (workspaceSpec.cellSize.ofRemainderSpace > 0)
+            cellSizePx = (workspaceSpec.cellSize.ofRemainderSpace * remainderSpace).roundToInt()
+    }
 }
 
 data class WorkspaceSpec(
@@ -220,7 +292,7 @@
 
     fun isValid(): Boolean {
         // All attributes are empty
-        if (fixedSize <= 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
+        if (fixedSize < 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
             Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
             return false
         }