Adding match workspace to SizeSpec for responsive grid support
Added matchWorkspace property to support responsive grid implementation for AllApps and Folders. This property indicates whether the attribute value will be used from the workspace instead of defining a fixed value in the AllApps and Folders XML. The class was updated to be a data class and added a auxiliar constructor new make it more flexible for testing and to add other initializers to support AllApps and Folders.
Bug: 284155638
Flag: ENABLE_RESPONSIVE_WORKSPACE
Test: WorkspaceSpecsTest
Test: SizeSpecTest
Change-Id: I65b71e66be7b8236a1dee62b56a487b87881d991
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 2e74d7d..fc23eaa 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -204,6 +204,7 @@
<!-- File that contains the specs for the workspace.
Needs FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE enabled -->
<attr name="workspaceSpecsId" format="reference" />
+
<!-- By default all categories are enabled -->
<attr name="deviceCategory" format="integer">
<!-- Enable on phone only -->
@@ -251,10 +252,11 @@
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
- <declare-styleable name="SpecSize">
+ <declare-styleable name="SizeSpec">
<attr name="fixedSize" format="dimension" />
<attr name="ofAvailableSpace" format="float" />
<attr name="ofRemainderSpace" format="float" />
+ <attr name="matchWorkspace" format="boolean" />
</declare-styleable>
<declare-styleable name="ProfileDisplayOption">
diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt
new file mode 100644
index 0000000..bf5ca1c
--- /dev/null
+++ b/src/com/android/launcher3/responsive/SizeSpec.kt
@@ -0,0 +1,73 @@
+package com.android.launcher3.responsive
+
+import android.content.res.TypedArray
+import android.util.AttributeSet
+import android.util.Log
+import android.util.TypedValue
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceHelper
+
+data class SizeSpec(
+ val fixedSize: Float,
+ val ofAvailableSpace: Float,
+ val ofRemainderSpace: Float,
+ val matchWorkspace: Boolean
+) {
+
+ fun isValid(): Boolean {
+ // All attributes are empty
+ if (fixedSize < 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f && !matchWorkspace) {
+ Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
+ return false
+ }
+
+ // More than one attribute is filled
+ val attrCount =
+ (if (fixedSize > 0) 1 else 0) +
+ (if (ofAvailableSpace > 0) 1 else 0) +
+ (if (ofRemainderSpace > 0) 1 else 0) +
+ (if (matchWorkspace) 1 else 0)
+ if (attrCount > 1) {
+ Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled")
+ return false
+ }
+
+ // Values should be between 0 and 1
+ if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) {
+ Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1")
+ return false
+ }
+
+ // Invalid fixed size
+ if (fixedSize < 0f) {
+ Log.e(TAG, "SizeSpec#isValid - values should be bigger or equal to zero.")
+ return false
+ }
+
+ return true
+ }
+
+ companion object {
+ private const val TAG = "WorkspaceSpecs::SizeSpec"
+ private fun getValue(a: TypedArray, index: Int): Float {
+ return when (a.getType(index)) {
+ TypedValue.TYPE_DIMENSION -> a.getDimensionPixelSize(index, 0).toFloat()
+ TypedValue.TYPE_FLOAT -> a.getFloat(index, 0f)
+ else -> 0f
+ }
+ }
+
+ fun create(resourceHelper: ResourceHelper, attrs: AttributeSet): SizeSpec {
+ val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SizeSpec)
+
+ val fixedSize = getValue(styledAttrs, R.styleable.SizeSpec_fixedSize)
+ val ofAvailableSpace = getValue(styledAttrs, R.styleable.SizeSpec_ofAvailableSpace)
+ val ofRemainderSpace = getValue(styledAttrs, R.styleable.SizeSpec_ofRemainderSpace)
+ val matchWorkspace = styledAttrs.getBoolean(R.styleable.SizeSpec_matchWorkspace, false)
+
+ styledAttrs.recycle()
+
+ return SizeSpec(fixedSize, ofAvailableSpace, ofRemainderSpace, matchWorkspace)
+ }
+ }
+}
diff --git a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
index dc5ae47..4815924 100644
--- a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
+++ b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
@@ -16,13 +16,12 @@
package com.android.launcher3.workspace
-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 com.android.launcher3.R
+import com.android.launcher3.responsive.SizeSpec
import com.android.launcher3.util.ResourceHelper
import java.io.IOException
import kotlin.math.roundToInt
@@ -95,16 +94,16 @@
if (type == XmlPullParser.START_TAG) {
when (parser.name) {
XmlTags.START_PADDING -> {
- startPadding = SizeSpec(resourceHelper, attr)
+ startPadding = SizeSpec.create(resourceHelper, attr)
}
XmlTags.END_PADDING -> {
- endPadding = SizeSpec(resourceHelper, attr)
+ endPadding = SizeSpec.create(resourceHelper, attr)
}
XmlTags.GUTTER -> {
- gutter = SizeSpec(resourceHelper, attr)
+ gutter = SizeSpec.create(resourceHelper, attr)
}
XmlTags.CELL_SIZE -> {
- cellSize = SizeSpec(resourceHelper, attr)
+ cellSize = SizeSpec.create(resourceHelper, attr)
}
}
}
@@ -270,61 +269,12 @@
}
private fun allSpecsAreValid(): Boolean =
- startPadding.isValid() && endPadding.isValid() && gutter.isValid() && cellSize.isValid()
-}
-
-class SizeSpec(resourceHelper: ResourceHelper, attrs: AttributeSet) {
- val fixedSize: Float
- val ofAvailableSpace: Float
- val ofRemainderSpace: Float
-
- init {
- val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SpecSize)
-
- fixedSize = getValue(styledAttrs, R.styleable.SpecSize_fixedSize)
- ofAvailableSpace = getValue(styledAttrs, R.styleable.SpecSize_ofAvailableSpace)
- ofRemainderSpace = getValue(styledAttrs, R.styleable.SpecSize_ofRemainderSpace)
-
- styledAttrs.recycle()
- }
-
- private fun getValue(a: TypedArray, index: Int): Float {
- if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
- return a.getDimensionPixelSize(index, 0).toFloat()
- } else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
- return a.getFloat(index, 0f)
- }
- return 0f
- }
-
- fun isValid(): Boolean {
- // All attributes are empty
- if (fixedSize < 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
- Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
- return false
- }
-
- // More than one attribute is filled
- val attrCount =
- (if (fixedSize > 0) 1 else 0) +
- (if (ofAvailableSpace > 0) 1 else 0) +
- (if (ofRemainderSpace > 0) 1 else 0)
- if (attrCount > 1) {
- Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled")
- return false
- }
-
- // Values should be between 0 and 1
- if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) {
- Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1")
- return false
- }
-
- return true
- }
-
- override fun toString(): String {
- return "SizeSpec(fixedSize=$fixedSize, ofAvailableSpace=$ofAvailableSpace, " +
- "ofRemainderSpace=$ofRemainderSpace)"
- }
+ startPadding.isValid() &&
+ endPadding.isValid() &&
+ gutter.isValid() &&
+ cellSize.isValid() &&
+ !startPadding.matchWorkspace &&
+ !endPadding.matchWorkspace &&
+ !gutter.matchWorkspace &&
+ !cellSize.matchWorkspace
}
diff --git a/tests/res/values/attrs.xml b/tests/res/values/attrs.xml
index 2310d9e..cb6da3b 100644
--- a/tests/res/values/attrs.xml
+++ b/tests/res/values/attrs.xml
@@ -26,10 +26,10 @@
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
- <declare-styleable name="SpecSize">
+ <declare-styleable name="SizeSpec">
<attr name="fixedSize" format="dimension" />
<attr name="ofAvailableSpace" format="float" />
<attr name="ofRemainderSpace" format="float" />
+ <attr name="matchWorkspace" format="boolean" />
</declare-styleable>
-
</resources>
diff --git a/tests/res/xml/invalid_workspace_file_case_4.xml b/tests/res/xml/invalid_workspace_file_case_4.xml
new file mode 100644
index 0000000..9e74c85
--- /dev/null
+++ b/tests/res/xml/invalid_workspace_file_case_4.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+
+<workspaceSpecs xmlns:launcher="http://schemas.android.com/apk/res-auto">
+ <workspaceSpec
+ launcher:specType="height"
+ launcher:maxAvailableSize="648dp">
+ <startPadding
+ launcher:ofAvailableSpace="0.0125" />
+ <endPadding
+ launcher:ofAvailableSpace="0.05" />
+ <!-- value in workspace spec using matchWorkspace -->
+ <gutter
+ launcher:matchWorkspace="true" />
+ <cellSize
+ launcher:ofRemainderSpace="0.2" />
+ </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" />
+ </workspaceSpec>
+
+ <!-- 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" />
+ </workspaceSpec>
+</workspaceSpecs>
diff --git a/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt b/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
new file mode 100644
index 0000000..426777d
--- /dev/null
+++ b/tests/src/com/android/launcher3/responsive/SizeSpecTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.responsive
+
+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.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SizeSpecTest : AbstractDeviceProfileTest() {
+ override val runningContext: Context = InstrumentationRegistry.getInstrumentation().context
+
+ @Before
+ fun setup() {
+ initializeVarsForPhone(deviceSpecs["phone"]!!)
+ }
+
+ @Test
+ fun valid_values() {
+ val combinations =
+ listOf(
+ SizeSpec(100f, 0f, 0f, false),
+ SizeSpec(0f, 1f, 0f, false),
+ SizeSpec(0f, 0f, 1f, false),
+ SizeSpec(0f, 0f, 0f, false),
+ SizeSpec(0f, 0f, 0f, true)
+ )
+
+ for (instance in combinations) {
+ assertThat(instance.isValid()).isEqualTo(true)
+ }
+ }
+
+ @Test
+ fun multiple_values_assigned() {
+ val combinations =
+ listOf(
+ SizeSpec(1f, 1f, 0f, false),
+ SizeSpec(1f, 0f, 1f, false),
+ SizeSpec(1f, 0f, 0f, true),
+ SizeSpec(0f, 1f, 1f, false),
+ SizeSpec(0f, 1f, 0f, true),
+ SizeSpec(0f, 0f, 1f, true),
+ SizeSpec(1f, 1f, 1f, true)
+ )
+
+ for (instance in combinations) {
+ assertThat(instance.isValid()).isEqualTo(false)
+ }
+ }
+
+ @Test
+ fun invalid_values() {
+ val combinations =
+ listOf(
+ SizeSpec(0f, 1.1f, 0f, false),
+ SizeSpec(0f, -0.1f, 0f, false),
+ SizeSpec(0f, 0f, 1.1f, false),
+ SizeSpec(0f, 0f, -0.1f, false),
+ SizeSpec(-1f, 0f, 0f, false)
+ )
+
+ for (instance in combinations) {
+ assertThat(instance.isValid()).isEqualTo(false)
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/util/TestResourceHelper.kt b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
index fb03fe1..3f0054e 100644
--- a/tests/src/com/android/launcher3/util/TestResourceHelper.kt
+++ b/tests/src/com/android/launcher3/util/TestResourceHelper.kt
@@ -27,7 +27,7 @@
ResourceHelper(context, specsFileId) {
override fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
var clone = styleId.clone()
- if (styleId == R.styleable.SpecSize) clone = TestR.styleable.SpecSize
+ if (styleId == R.styleable.SizeSpec) clone = TestR.styleable.SizeSpec
else if (styleId == R.styleable.WorkspaceSpec) clone = TestR.styleable.WorkspaceSpec
return context.obtainStyledAttributes(attrs, clone)
}
diff --git a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
index 9cd0a2e..51808e3 100644
--- a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
+++ b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
@@ -50,16 +50,20 @@
"specType=HEIGHT, " +
"startPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"endPadding=SizeSpec(fixedSize=84.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"cellSize=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.15808, " +
- "ofRemainderSpace=0.0)" +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false)" +
")"
)
assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
@@ -69,16 +73,20 @@
"specType=HEIGHT, " +
"startPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"endPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=1.0), " +
+ "ofRemainderSpace=1.0, " +
+ "matchWorkspace=false), " +
"gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"cellSize=SizeSpec(fixedSize=273.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0)" +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false)" +
")"
)
assertThat(workspaceSpecs.workspaceHeightSpecList[2].toString())
@@ -88,16 +96,20 @@
"specType=HEIGHT, " +
"startPadding=SizeSpec(fixedSize=21.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"endPadding=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=1.0), " +
+ "ofRemainderSpace=1.0, " +
+ "matchWorkspace=false), " +
"gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"cellSize=SizeSpec(fixedSize=273.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0)" +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false)" +
")"
)
assertThat(workspaceSpecs.workspaceWidthSpecList.size).isEqualTo(1)
@@ -108,16 +120,20 @@
"specType=WIDTH, " +
"startPadding=SizeSpec(fixedSize=58.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"endPadding=SizeSpec(fixedSize=58.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"gutter=SizeSpec(fixedSize=42.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.0), " +
+ "ofRemainderSpace=0.0, " +
+ "matchWorkspace=false), " +
"cellSize=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.25)" +
+ "ofRemainderSpace=0.25, " +
+ "matchWorkspace=false)" +
")"
)
}
@@ -136,4 +152,9 @@
fun parseInvalidFile_valueBiggerThan1_throwsError() {
WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_3))
}
+
+ @Test(expected = IllegalStateException::class)
+ fun parseInvalidFile_matchWorkspace_true_throwsError() {
+ WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_4))
+ }
}