diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 275a3b81a6..80df78ae54 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)) + } }