From c98b7815c66b844b8345c10c9f78fd94ad4b75db Mon Sep 17 00:00:00 2001 From: Thales Lima Date: Fri, 14 Apr 2023 15:04:23 +0100 Subject: [PATCH] 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 --- src/com/android/launcher3/DeviceProfile.java | 25 +++- .../launcher3/workspace/WorkspaceSpecs.kt | 74 +++++++++++- tests/res/xml/valid_workspace_file.xml | 52 +++++---- .../launcher3/AbstractDeviceProfileTest.kt | 2 +- .../workspace/CalculatedWorkspaceSpecTest.kt | 107 ++++++++++++++++++ .../launcher3/workspace/WorkspaceSpecsTest.kt | 61 ++++++---- 6 files changed, 268 insertions(+), 53 deletions(-) create mode 100644 tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 0231090d7e..fb41044875 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -55,7 +55,10 @@ import com.android.launcher3.model.data.ItemInfo; 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 class DeviceProfile { 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 @@ public class DeviceProfile { 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 @@ public class DeviceProfile { } } + 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 @@ public class DeviceProfile { 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 971fd9b578..ac0a166b18 100644 --- a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt +++ b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt @@ -25,6 +25,7 @@ import android.util.Xml 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 @@ class WorkspaceSpecs(resourceHelper: ResourceHelper) { } } } + + /** + * 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 @@ class SizeSpec(resourceHelper: ResourceHelper, attrs: AttributeSet) { 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 91a3e48d21..1f97314927 100644 --- a/tests/res/xml/valid_workspace_file.xml +++ b/tests/res/xml/valid_workspace_file.xml @@ -15,43 +15,45 @@ --> + - - - - + launcher:maxAvailableSize="584dp"> + + + + + + + + + + + + - - - - + + + + + + - - - - + + + + + diff --git a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt index 01f494b202..3de4d555ee 100644 --- a/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt +++ b/tests/src/com/android/launcher3/AbstractDeviceProfileTest.kt @@ -66,7 +66,7 @@ abstract class AbstractDeviceProfileTest { class DeviceSpec( val naturalSize: Pair, - 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 0000000000..7f03ba2f77 --- /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 0fd8a5460e..9cd0a2e4a3 100644 --- a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt +++ b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt @@ -42,43 +42,62 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() { 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 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() { "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)" + ")" ) }