From 2b8556ce1a955f2b15d6c16878b01f034394145f Mon Sep 17 00:00:00 2001 From: Jordan Silva Date: Wed, 7 Jun 2023 12:10:59 +0100 Subject: [PATCH] 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 --- res/values/attrs.xml | 4 +- .../android/launcher3/responsive/SizeSpec.kt | 73 +++++++++++++++ .../launcher3/workspace/WorkspaceSpecs.kt | 76 +++------------- tests/res/values/attrs.xml | 4 +- .../res/xml/invalid_workspace_file_case_4.xml | 58 ++++++++++++ .../launcher3/responsive/SizeSpecTest.kt | 88 +++++++++++++++++++ .../launcher3/util/TestResourceHelper.kt | 2 +- .../launcher3/workspace/WorkspaceSpecsTest.kt | 53 +++++++---- 8 files changed, 275 insertions(+), 83 deletions(-) create mode 100644 src/com/android/launcher3/responsive/SizeSpec.kt create mode 100644 tests/res/xml/invalid_workspace_file_case_4.xml create mode 100644 tests/src/com/android/launcher3/responsive/SizeSpecTest.kt diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 2e74d7d7dd..fc23eaa02d 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -204,6 +204,7 @@ + @@ -251,10 +252,11 @@ - + + diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt new file mode 100644 index 0000000000..bf5ca1cb89 --- /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 dc5ae47b16..48159249a0 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 @@ class WorkspaceSpecs(resourceHelper: ResourceHelper) { 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 @@ data class WorkspaceSpec( } 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 2310d9ef66..cb6da3ba3f 100644 --- a/tests/res/values/attrs.xml +++ b/tests/res/values/attrs.xml @@ -26,10 +26,10 @@ - + + - 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 0000000000..9e74c852b3 --- /dev/null +++ b/tests/res/xml/invalid_workspace_file_case_4.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000..426777de26 --- /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 fb03fe1b53..3f0054e0c3 100644 --- a/tests/src/com/android/launcher3/util/TestResourceHelper.kt +++ b/tests/src/com/android/launcher3/util/TestResourceHelper.kt @@ -27,7 +27,7 @@ class TestResourceHelper(private val context: Context, private val specsFileId: 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 9cd0a2e4a3..51808e3e53 100644 --- a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt +++ b/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt @@ -50,16 +50,20 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() { "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 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() { "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 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() { "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 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() { "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 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() { 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)) + } }