Merge "Adding match workspace to SizeSpec for responsive grid support" into udc-qpr-dev
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 275a3b8..80df78a 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))
+ }
}