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
}
diff --git a/tests/res/xml/valid_workspace_file.xml b/tests/res/xml/valid_workspace_file.xml
index 91a3e48..1f97314 100644
--- a/tests/res/xml/valid_workspace_file.xml
+++ b/tests/res/xml/valid_workspace_file.xml
@@ -15,43 +15,45 @@
-->
<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <!-- 584 grid height -->
<workspaceSpec
launcher:specType="height"
- launcher:maxAvailableSize="648dp">
- <startPadding
- launcher:ofAvailableSpace="0.0125" />
- <endPadding
- launcher:ofAvailableSpace="0.05" />
- <gutter
- launcher:fixedSize="16dp" />
- <cellSize
- launcher:ofRemainderSpace="0.2" />
+ launcher:maxAvailableSize="584dp">
+ <startPadding launcher:fixedSize="0dp" />
+ <endPadding launcher:fixedSize="32dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:ofAvailableSpace="0.15808" />
+ </workspaceSpec>
+
+ <!-- 584 grid height + 28 remainder space -->
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="612dp">
+ <startPadding launcher:fixedSize="0dp" />
+ <endPadding launcher:ofRemainderSpace="1" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="104dp" />
</workspaceSpec>
<workspaceSpec
launcher:specType="height"
launcher:maxAvailableSize="9999dp">
- <startPadding
- launcher:ofAvailableSpace="0.0306" />
- <endPadding
- launcher:ofAvailableSpace="0.068" />
- <gutter
- launcher:fixedSize="16dp" />
- <cellSize
- launcher:ofRemainderSpace="0.2" />
+ <startPadding launcher:fixedSize="8dp" />
+ <endPadding launcher:ofRemainderSpace="1" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:fixedSize="104dp" />
</workspaceSpec>
+ <!-- TODO(b/241386436): other specs here for height ... -->
+
<!-- Width spec is always the same -->
<workspaceSpec
launcher:specType="width"
launcher:maxAvailableSize="9999dp">
- <startPadding
- launcher:ofRemainderSpace="0.21436227" />
- <endPadding
- launcher:ofRemainderSpace="0.21436227" />
- <gutter
- launcher:ofRemainderSpace="0.11425509" />
- <cellSize
- launcher:fixedSize="120dp" />
+ <startPadding launcher:fixedSize="22dp" />
+ <endPadding launcher:fixedSize="22dp" />
+ <gutter launcher:fixedSize="16dp" />
+ <cellSize launcher:ofRemainderSpace="0.25" />
</workspaceSpec>
+
</workspaceSpecs>
diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
index 01f494b..3de4d55 100644
--- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
+++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt
@@ -66,7 +66,7 @@
class DeviceSpec(
val naturalSize: Pair<Int, Int>,
- val densityDpi: Int,
+ var densityDpi: Int,
val statusBarNaturalPx: Int,
val statusBarRotatedPx: Int,
val gesturePx: Int,
diff --git a/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt b/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt
new file mode 100644
index 0000000..7f03ba2
--- /dev/null
+++ b/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 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.workspace
+
+import android.content.Context
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.tests.R as TestR
+import com.android.launcher3.util.TestResourceHelper
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CalculatedWorkspaceSpecTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ /**
+ * This test tests:
+ * - (height spec) gets the correct breakpoint from the XML - skips the first 2 breakpoints
+ * - (height spec) do the correct calculations for available space and fixed size
+ * - (width spec) do the correct calculations for remainder space and fixed size
+ */
+ @Test
+ fun normalPhone_returnsThirdBreakpointSpec() {
+ val deviceSpec = deviceSpecs["phone"]!!
+ initializeVarsForPhone(deviceSpec)
+
+ val availableWidth = deviceSpec.naturalSize.first
+ // Hotseat size is roughly 495px on a real device,
+ // it doesn't need to be precise on unit tests
+ val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 495
+
+ val workspaceSpecs =
+ WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
+ val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+
+ assertThat(widthSpec.availableSpace).isEqualTo(availableWidth)
+ assertThat(widthSpec.cells).isEqualTo(4)
+ assertThat(widthSpec.startPaddingPx).isEqualTo(58)
+ assertThat(widthSpec.endPaddingPx).isEqualTo(58)
+ assertThat(widthSpec.gutterPx).isEqualTo(42)
+ assertThat(widthSpec.cellSizePx).isEqualTo(210)
+
+ assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+ assertThat(heightSpec.cells).isEqualTo(5)
+ assertThat(heightSpec.startPaddingPx).isEqualTo(21)
+ assertThat(heightSpec.endPaddingPx).isEqualTo(233)
+ assertThat(heightSpec.gutterPx).isEqualTo(42)
+ assertThat(heightSpec.cellSizePx).isEqualTo(273)
+ }
+
+ /**
+ * This test tests:
+ * - (height spec) gets the correct breakpoint from the XML - use the first breakpoint
+ * - (height spec) do the correct calculations for remainder space and fixed size
+ * - (width spec) do the correct calculations for remainder space and fixed size
+ */
+ @Test
+ fun smallPhone_returnsFirstBreakpointSpec() {
+ val deviceSpec = deviceSpecs["phone"]!!
+ deviceSpec.densityDpi = 540 // larger display size
+ initializeVarsForPhone(deviceSpec)
+
+ val availableWidth = deviceSpec.naturalSize.first
+ // Hotseat size is roughly 640px on a real device,
+ // it doesn't need to be precise on unit tests
+ val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
+
+ val workspaceSpecs =
+ WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
+ val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
+
+ assertThat(widthSpec.availableSpace).isEqualTo(availableWidth)
+ assertThat(widthSpec.cells).isEqualTo(4)
+ assertThat(widthSpec.startPaddingPx).isEqualTo(74)
+ assertThat(widthSpec.endPaddingPx).isEqualTo(74)
+ assertThat(widthSpec.gutterPx).isEqualTo(54)
+ assertThat(widthSpec.cellSizePx).isEqualTo(193)
+
+ assertThat(heightSpec.availableSpace).isEqualTo(availableHeight)
+ assertThat(heightSpec.cells).isEqualTo(5)
+ assertThat(heightSpec.startPaddingPx).isEqualTo(0)
+ assertThat(heightSpec.endPaddingPx).isEqualTo(108)
+ assertThat(heightSpec.gutterPx).isEqualTo(54)
+ assertThat(heightSpec.cellSizePx).isEqualTo(260)
+ }
+}
diff --git a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
index 0fd8a54..9cd0a2e 100644
--- a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
+++ b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
@@ -42,43 +42,62 @@
fun parseValidFile() {
val workspaceSpecs =
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
- assertThat(workspaceSpecs.workspaceHeightSpecList.size).isEqualTo(2)
+ assertThat(workspaceSpecs.workspaceHeightSpecList.size).isEqualTo(3)
assertThat(workspaceSpecs.workspaceHeightSpecList[0].toString())
.isEqualTo(
"WorkspaceSpec(" +
- "maxAvailableSize=1701, " +
+ "maxAvailableSize=1533, " +
"specType=HEIGHT, " +
"startPadding=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.0125, " +
+ "ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.0), " +
- "endPadding=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.05, " +
+ "endPadding=SizeSpec(fixedSize=84.0, " +
+ "ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.0), " +
"gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.0), " +
"cellSize=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.2)" +
+ "ofAvailableSpace=0.15808, " +
+ "ofRemainderSpace=0.0)" +
")"
)
assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
.isEqualTo(
"WorkspaceSpec(" +
- "maxAvailableSize=26247, " +
+ "maxAvailableSize=1607, " +
"specType=HEIGHT, " +
"startPadding=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.0306, " +
+ "ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.0), " +
"endPadding=SizeSpec(fixedSize=0.0, " +
- "ofAvailableSpace=0.068, " +
- "ofRemainderSpace=0.0), " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=1.0), " +
"gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
"ofRemainderSpace=0.0), " +
- "cellSize=SizeSpec(fixedSize=0.0, " +
+ "cellSize=SizeSpec(fixedSize=273.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.2)" +
+ "ofRemainderSpace=0.0)" +
+ ")"
+ )
+ assertThat(workspaceSpecs.workspaceHeightSpecList[2].toString())
+ .isEqualTo(
+ "WorkspaceSpec(" +
+ "maxAvailableSize=26247, " +
+ "specType=HEIGHT, " +
+ "startPadding=SizeSpec(fixedSize=21.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0), " +
+ "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=1.0), " +
+ "gutter=SizeSpec(fixedSize=42.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0), " +
+ "cellSize=SizeSpec(fixedSize=273.0, " +
+ "ofAvailableSpace=0.0, " +
+ "ofRemainderSpace=0.0)" +
")"
)
assertThat(workspaceSpecs.workspaceWidthSpecList.size).isEqualTo(1)
@@ -87,18 +106,18 @@
"WorkspaceSpec(" +
"maxAvailableSize=26247, " +
"specType=WIDTH, " +
- "startPadding=SizeSpec(fixedSize=0.0, " +
+ "startPadding=SizeSpec(fixedSize=58.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.21436226), " +
- "endPadding=SizeSpec(fixedSize=0.0, " +
+ "ofRemainderSpace=0.0), " +
+ "endPadding=SizeSpec(fixedSize=58.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.21436226), " +
- "gutter=SizeSpec(fixedSize=0.0, " +
+ "ofRemainderSpace=0.0), " +
+ "gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.11425509), " +
- "cellSize=SizeSpec(fixedSize=315.0, " +
+ "ofRemainderSpace=0.0), " +
+ "cellSize=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0)" +
+ "ofRemainderSpace=0.25)" +
")"
)
}