Improving responsive grid xml parser
Refactors AllAppsSpecs, FolderSpecs and WorkspaceSpecs initialization to use the same code to parse the xml with different map function. This CL improves the readability of the code and remove code duplication.
Fix: 286538013
Flag: ENABLE_RESPONSIVE_WORKSPACE
Test: AllAppsSpecsTes
Test: CalculatedAllAppsSpecTest
Test: CalculatedFolderSpecsTest
Test: CalculatedWorkspaceSpecTest
Test: FolderSpecsTest
Test: WorkspaceSpecsTest
Test: DeviceProfileResponsiveDumpTest
Test: DeviceProfileResponsiveAlternativeDisplaysDumpTest
Change-Id: Iec5863619399efd2e80f3db46b75c4d785e1656f
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 733f527..9803779 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -252,7 +252,7 @@
</declare-styleable>
<!-- Responsive grids attributes -->
- <declare-styleable name="WorkspaceSpec">
+ <declare-styleable name="ResponsiveSpec">
<attr name="specType" format="integer">
<enum name="height" value="0" />
<enum name="width" value="1" />
@@ -260,12 +260,9 @@
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
- <declare-styleable name="SizeSpec">
- <attr name="fixedSize" format="dimension" />
- <attr name="ofAvailableSpace" format="float" />
- <attr name="ofRemainderSpace" format="float" />
- <attr name="matchWorkspace" format="boolean" />
- <attr name="maxSize" format="dimension" />
+ <declare-styleable name="WorkspaceSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
</declare-styleable>
<declare-styleable name="FolderSpec">
@@ -278,6 +275,14 @@
<attr name="maxAvailableSize" />
</declare-styleable>
+ <declare-styleable name="SizeSpec">
+ <attr name="fixedSize" format="dimension" />
+ <attr name="ofAvailableSpace" format="float" />
+ <attr name="ofRemainderSpace" format="float" />
+ <attr name="matchWorkspace" format="boolean" />
+ <attr name="maxSize" format="dimension" />
+ </declare-styleable>
+
<declare-styleable name="ProfileDisplayOption">
<attr name="name" />
<attr name="minWidthDps" format="float" />
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index f3b5155..88dcfcd 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -56,15 +56,15 @@
import com.android.launcher3.responsive.AllAppsSpecs;
import com.android.launcher3.responsive.CalculatedAllAppsSpec;
import com.android.launcher3.responsive.CalculatedFolderSpec;
+import com.android.launcher3.responsive.CalculatedWorkspaceSpec;
import com.android.launcher3.responsive.FolderSpecs;
+import com.android.launcher3.responsive.WorkspaceSpecs;
import com.android.launcher3.uioverrides.ApiWrapper;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
import com.android.launcher3.util.IconSizeSteps;
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;
@@ -115,13 +115,10 @@
// Responsive grid
private final boolean mIsResponsiveGrid;
- private WorkspaceSpecs mWorkspaceSpecs;
private CalculatedWorkspaceSpec mResponsiveWidthSpec;
private CalculatedWorkspaceSpec mResponsiveHeightSpec;
- private AllAppsSpecs mAllAppsSpecs;
private CalculatedAllAppsSpec mAllAppsResponsiveWidthSpec;
private CalculatedAllAppsSpec mAllAppsResponsiveHeightSpec;
- private FolderSpecs mFolderSpecs;
private CalculatedFolderSpec mResponsiveFolderWidthSpec;
private CalculatedFolderSpec mResponsiveFolderHeightSpec;
@@ -545,29 +542,31 @@
// Needs to be calculated after hotseatBarSizePx is correct,
// for the available height to be correct
if (mIsResponsiveGrid) {
- mWorkspaceSpecs = new WorkspaceSpecs(new ResourceHelper(context, inv.workspaceSpecsId));
+ WorkspaceSpecs workspaceSpecs = WorkspaceSpecs.create(
+ new ResourceHelper(context, inv.workspaceSpecsId));
int availableResponsiveWidth =
availableWidthPx - (isVerticalBarLayout() ? hotseatBarSizePx : 0);
// don't use availableHeightPx because it subtracts bottom padding,
// but the workspace go behind it
int availableResponsiveHeight =
heightPx - mInsets.top - (isVerticalBarLayout() ? 0 : hotseatBarSizePx);
- mResponsiveWidthSpec = mWorkspaceSpecs.getCalculatedWidthSpec(inv.numColumns,
+ mResponsiveWidthSpec = workspaceSpecs.getCalculatedWidthSpec(inv.numColumns,
availableResponsiveWidth);
- mResponsiveHeightSpec = mWorkspaceSpecs.getCalculatedHeightSpec(inv.numRows,
+ mResponsiveHeightSpec = workspaceSpecs.getCalculatedHeightSpec(inv.numRows,
availableResponsiveHeight);
- mAllAppsSpecs = new AllAppsSpecs(new ResourceHelper(context, inv.allAppsSpecsId));
- mAllAppsResponsiveWidthSpec = mAllAppsSpecs.getCalculatedWidthSpec(inv.numColumns,
+ AllAppsSpecs allAppsSpecs = AllAppsSpecs.create(
+ new ResourceHelper(context, inv.allAppsSpecsId));
+ mAllAppsResponsiveWidthSpec = allAppsSpecs.getCalculatedWidthSpec(inv.numColumns,
mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
- mAllAppsResponsiveHeightSpec = mAllAppsSpecs.getCalculatedHeightSpec(inv.numRows,
- mResponsiveHeightSpec.getAvailableSpace(),
- mResponsiveHeightSpec);
+ mAllAppsResponsiveHeightSpec = allAppsSpecs.getCalculatedHeightSpec(inv.numRows,
+ mResponsiveHeightSpec.getAvailableSpace(), mResponsiveHeightSpec);
- mFolderSpecs = new FolderSpecs(new ResourceHelper(context, inv.folderSpecsId));
- mResponsiveFolderWidthSpec = mFolderSpecs.getWidthSpec(inv.numFolderColumns,
+ FolderSpecs folderSpecs = FolderSpecs.create(
+ new ResourceHelper(context, inv.folderSpecsId));
+ mResponsiveFolderWidthSpec = folderSpecs.getCalculatedWidthSpec(inv.numFolderColumns,
mResponsiveWidthSpec.getAvailableSpace(), mResponsiveWidthSpec);
- mResponsiveFolderHeightSpec = mFolderSpecs.getHeightSpec(inv.numFolderRows,
+ mResponsiveFolderHeightSpec = folderSpecs.getCalculatedHeightSpec(inv.numFolderRows,
mResponsiveHeightSpec.getAvailableSpace(), mResponsiveHeightSpec);
}
diff --git a/src/com/android/launcher3/responsive/AllAppsSpecs.kt b/src/com/android/launcher3/responsive/AllAppsSpecs.kt
index 85e383e..8ed3ffc 100644
--- a/src/com/android/launcher3/responsive/AllAppsSpecs.kt
+++ b/src/com/android/launcher3/responsive/AllAppsSpecs.kt
@@ -16,277 +16,89 @@
package com.android.launcher3.responsive
-import android.content.res.XmlResourceParser
-import android.util.AttributeSet
-import android.util.Log
-import android.util.Xml
+import android.content.res.TypedArray
import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.SpecType
import com.android.launcher3.util.ResourceHelper
-import com.android.launcher3.workspace.CalculatedWorkspaceSpec
-import java.io.IOException
-import kotlin.math.roundToInt
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-private const val LOG_TAG = "AllAppsSpecs"
+class AllAppsSpecs(widthSpecs: List<AllAppsSpec>, heightSpecs: List<AllAppsSpec>) :
+ ResponsiveSpecs<AllAppsSpec>(widthSpecs, heightSpecs) {
-class AllAppsSpecs(resourceHelper: ResourceHelper) {
- object XmlTags {
- const val ALL_APPS_SPECS = "allAppsSpecs"
-
- const val ALL_APPS_SPEC = "allAppsSpec"
- const val START_PADDING = "startPadding"
- const val END_PADDING = "endPadding"
- const val GUTTER = "gutter"
- const val CELL_SIZE = "cellSize"
- }
-
- val allAppsHeightSpecList = mutableListOf<AllAppsSpec>()
- val allAppsWidthSpecList = mutableListOf<AllAppsSpec>()
-
- // TODO(b/286538013) Remove this init after a more generic or reusable parser is created
- init {
- var parser: XmlResourceParser? = null
- try {
- parser = resourceHelper.getXml()
- val depth = parser.depth
- var type: Int
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
- ) {
- if (type == XmlPullParser.START_TAG && XmlTags.ALL_APPS_SPECS == parser.name) {
- val displayDepth = parser.depth
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > displayDepth) && type != XmlPullParser.END_DOCUMENT
- ) {
- if (
- type == XmlPullParser.START_TAG && XmlTags.ALL_APPS_SPEC == parser.name
- ) {
- val attrs =
- resourceHelper.obtainStyledAttributes(
- Xml.asAttributeSet(parser),
- R.styleable.AllAppsSpec
- )
- val maxAvailableSize =
- attrs.getDimensionPixelSize(
- R.styleable.AllAppsSpec_maxAvailableSize,
- 0
- )
- val specType =
- AllAppsSpec.SpecType.values()[
- attrs.getInt(
- R.styleable.AllAppsSpec_specType,
- AllAppsSpec.SpecType.HEIGHT.ordinal
- )]
- attrs.recycle()
-
- var startPadding: SizeSpec? = null
- var endPadding: SizeSpec? = null
- var gutter: SizeSpec? = null
- var cellSize: SizeSpec? = null
-
- val limitDepth = parser.depth
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > limitDepth) && type != XmlPullParser.END_DOCUMENT
- ) {
- val attr: AttributeSet = Xml.asAttributeSet(parser)
- if (type == XmlPullParser.START_TAG) {
- when (parser.name) {
- XmlTags.START_PADDING -> {
- startPadding = SizeSpec.create(resourceHelper, attr)
- }
- XmlTags.END_PADDING -> {
- endPadding = SizeSpec.create(resourceHelper, attr)
- }
- XmlTags.GUTTER -> {
- gutter = SizeSpec.create(resourceHelper, attr)
- }
- XmlTags.CELL_SIZE -> {
- cellSize = SizeSpec.create(resourceHelper, attr)
- }
- }
- }
- }
-
- if (
- startPadding == null ||
- endPadding == null ||
- gutter == null ||
- cellSize == null
- ) {
- throw IllegalStateException(
- "All attributes in AllAppsSpec must be defined"
- )
- }
-
- val allAppsSpec =
- AllAppsSpec(
- maxAvailableSize,
- specType,
- startPadding,
- endPadding,
- gutter,
- cellSize
- )
- if (allAppsSpec.isValid()) {
- if (allAppsSpec.specType == AllAppsSpec.SpecType.HEIGHT)
- allAppsHeightSpecList.add(allAppsSpec)
- else allAppsWidthSpecList.add(allAppsSpec)
- } else {
- throw IllegalStateException("Invalid AllAppsSpec found.")
- }
- }
- }
-
- if (allAppsWidthSpecList.isEmpty() || allAppsHeightSpecList.isEmpty()) {
- throw IllegalStateException(
- "AllAppsSpecs is incomplete - " +
- "height list size = ${allAppsHeightSpecList.size}; " +
- "width list size = ${allAppsWidthSpecList.size}."
- )
- }
- }
- }
- } catch (e: Exception) {
- when (e) {
- is IOException,
- is XmlPullParserException -> {
- throw RuntimeException("Failure parsing all apps specs file.", e)
- }
- else -> throw e
- }
- } finally {
- parser?.close()
- }
- }
-
- /**
- * Returns the CalculatedAllAppsSpec for width, based on the available width, the AllAppsSpecs
- * and the CalculatedWorkspaceSpec.
- */
fun getCalculatedWidthSpec(
columns: Int,
availableWidth: Int,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
): CalculatedAllAppsSpec {
- val widthSpec = allAppsWidthSpecList.first { availableWidth <= it.maxAvailableSize }
+ check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
+ "Invalid specType for CalculatedWorkspaceSpec. " +
+ "Expected: ${SpecType.WIDTH} - " +
+ "Found: ${calculatedWorkspaceSpec.spec.specType}}"
+ }
- return CalculatedAllAppsSpec(availableWidth, columns, widthSpec, calculatedWorkspaceSpec)
+ val spec = getWidthSpec(availableWidth)
+ return CalculatedAllAppsSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
}
- /**
- * Returns the CalculatedAllAppsSpec for height, based on the available height, the AllAppsSpecs
- * and the CalculatedWorkspaceSpec.
- */
fun getCalculatedHeightSpec(
rows: Int,
availableHeight: Int,
calculatedWorkspaceSpec: CalculatedWorkspaceSpec
): CalculatedAllAppsSpec {
- val heightSpec = allAppsHeightSpecList.first { availableHeight <= it.maxAvailableSize }
+ check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
+ "Invalid specType for CalculatedWorkspaceSpec. " +
+ "Expected: ${SpecType.HEIGHT} - " +
+ "Found: ${calculatedWorkspaceSpec.spec.specType}}"
+ }
- return CalculatedAllAppsSpec(availableHeight, rows, heightSpec, calculatedWorkspaceSpec)
- }
-}
-
-class CalculatedAllAppsSpec(
- val availableSpace: Int,
- val cells: Int,
- private val allAppsSpec: AllAppsSpec,
- calculatedWorkspaceSpec: CalculatedWorkspaceSpec
-) {
- 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 {
- // Copy values from workspace
- if (allAppsSpec.startPadding.matchWorkspace)
- startPaddingPx = calculatedWorkspaceSpec.startPaddingPx
- if (allAppsSpec.endPadding.matchWorkspace)
- endPaddingPx = calculatedWorkspaceSpec.endPaddingPx
- if (allAppsSpec.gutter.matchWorkspace) gutterPx = calculatedWorkspaceSpec.gutterPx
- if (allAppsSpec.cellSize.matchWorkspace) cellSizePx = calculatedWorkspaceSpec.cellSizePx
-
- // Calculate all fixed size first
- if (allAppsSpec.startPadding.fixedSize > 0)
- startPaddingPx = allAppsSpec.startPadding.fixedSize.roundToInt()
- if (allAppsSpec.endPadding.fixedSize > 0)
- endPaddingPx = allAppsSpec.endPadding.fixedSize.roundToInt()
- if (allAppsSpec.gutter.fixedSize > 0) gutterPx = allAppsSpec.gutter.fixedSize.roundToInt()
- if (allAppsSpec.cellSize.fixedSize > 0)
- cellSizePx = allAppsSpec.cellSize.fixedSize.roundToInt()
-
- // Calculate all available space next
- if (allAppsSpec.startPadding.ofAvailableSpace > 0)
- startPaddingPx =
- (allAppsSpec.startPadding.ofAvailableSpace * availableSpace).roundToInt()
- if (allAppsSpec.endPadding.ofAvailableSpace > 0)
- endPaddingPx = (allAppsSpec.endPadding.ofAvailableSpace * availableSpace).roundToInt()
- if (allAppsSpec.gutter.ofAvailableSpace > 0)
- gutterPx = (allAppsSpec.gutter.ofAvailableSpace * availableSpace).roundToInt()
- if (allAppsSpec.cellSize.ofAvailableSpace > 0)
- cellSizePx = (allAppsSpec.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 (allAppsSpec.startPadding.ofRemainderSpace > 0)
- startPaddingPx =
- (allAppsSpec.startPadding.ofRemainderSpace * remainderSpace).roundToInt()
- if (allAppsSpec.endPadding.ofRemainderSpace > 0)
- endPaddingPx = (allAppsSpec.endPadding.ofRemainderSpace * remainderSpace).roundToInt()
- if (allAppsSpec.gutter.ofRemainderSpace > 0)
- gutterPx = (allAppsSpec.gutter.ofRemainderSpace * remainderSpace).roundToInt()
- if (allAppsSpec.cellSize.ofRemainderSpace > 0)
- cellSizePx = (allAppsSpec.cellSize.ofRemainderSpace * remainderSpace).roundToInt()
+ val spec = getHeightSpec(availableHeight)
+ return CalculatedAllAppsSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
}
- override fun toString(): String {
- return "CalculatedAllAppsSpec(availableSpace=$availableSpace, " +
- "cells=$cells, startPaddingPx=$startPaddingPx, endPaddingPx=$endPaddingPx, " +
- "gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
- "AllAppsSpec.maxAvailableSize=${allAppsSpec.maxAvailableSize})"
+ companion object {
+ private const val XML_ALL_APPS_SPEC = "allAppsSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): AllAppsSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_ALL_APPS_SPEC, ::AllAppsSpec)
+ val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
+ return AllAppsSpecs(widthSpecs, heightSpecs)
+ }
}
}
data class AllAppsSpec(
- val maxAvailableSize: Int,
- val specType: SpecType,
- val startPadding: SizeSpec,
- val endPadding: SizeSpec,
- val gutter: SizeSpec,
- val cellSize: SizeSpec
-) {
+ override val maxAvailableSize: Int,
+ override val specType: SpecType,
+ override val startPadding: SizeSpec,
+ override val endPadding: SizeSpec,
+ override val gutter: SizeSpec,
+ override val cellSize: SizeSpec
+) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
- enum class SpecType {
- HEIGHT,
- WIDTH
+ init {
+ check(isValid()) { "Invalid AllAppsSpec found." }
}
- fun isValid(): Boolean {
- if (maxAvailableSize <= 0) {
- Log.e(LOG_TAG, "AllAppsSpec#isValid - maxAvailableSize <= 0")
- return false
- }
-
- // All specs need to be individually valid
- if (!allSpecsAreValid()) {
- Log.e(LOG_TAG, "AllAppsSpec#isValid - !allSpecsAreValid()")
- return false
- }
-
- return true
- }
-
- private fun allSpecsAreValid(): Boolean =
- startPadding.isValid() && endPadding.isValid() && gutter.isValid() && cellSize.isValid()
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ SpecType.values()[
+ attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
+ startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
+ endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
+ gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
+ cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
+ )
}
+
+class CalculatedAllAppsSpec(
+ availableSpace: Int,
+ cells: Int,
+ spec: AllAppsSpec,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
diff --git a/src/com/android/launcher3/responsive/FolderSpecs.kt b/src/com/android/launcher3/responsive/FolderSpecs.kt
index f4446bc..bc2db28 100644
--- a/src/com/android/launcher3/responsive/FolderSpecs.kt
+++ b/src/com/android/launcher3/responsive/FolderSpecs.kt
@@ -1,280 +1,105 @@
+/*
+ * 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.res.XmlResourceParser
-import android.util.AttributeSet
-import android.util.Log
-import android.util.Xml
+import android.content.res.TypedArray
import com.android.launcher3.R
-import com.android.launcher3.responsive.FolderSpec.*
+import com.android.launcher3.responsive.ResponsiveSpec.SpecType
import com.android.launcher3.util.ResourceHelper
-import com.android.launcher3.workspace.CalculatedWorkspaceSpec
-import com.android.launcher3.workspace.WorkspaceSpec
-import java.io.IOException
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-private const val LOG_TAG = "FolderSpecs"
+class FolderSpecs(widthSpecs: List<FolderSpec>, heightSpecs: List<FolderSpec>) :
+ ResponsiveSpecs<FolderSpec>(widthSpecs, heightSpecs) {
-class FolderSpecs(resourceHelper: ResourceHelper) {
-
- object XmlTags {
- const val FOLDER_SPECS = "folderSpecs"
-
- const val FOLDER_SPEC = "folderSpec"
- const val START_PADDING = "startPadding"
- const val END_PADDING = "endPadding"
- const val GUTTER = "gutter"
- const val CELL_SIZE = "cellSize"
- }
-
- private val _heightSpecs = mutableListOf<FolderSpec>()
- val heightSpecs: List<FolderSpec>
- get() = _heightSpecs
-
- private val _widthSpecs = mutableListOf<FolderSpec>()
- val widthSpecs: List<FolderSpec>
- get() = _widthSpecs
-
- // TODO(b/286538013) Remove this init after a more generic or reusable parser is created
- init {
- var parser: XmlResourceParser? = null
- try {
- parser = resourceHelper.getXml()
- val depth = parser.depth
- var type: Int
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
- ) {
- if (type == XmlPullParser.START_TAG && XmlTags.FOLDER_SPECS == parser.name) {
- val displayDepth = parser.depth
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > displayDepth) && type != XmlPullParser.END_DOCUMENT
- ) {
- if (type == XmlPullParser.START_TAG && XmlTags.FOLDER_SPEC == parser.name) {
- val attrs =
- resourceHelper.obtainStyledAttributes(
- Xml.asAttributeSet(parser),
- R.styleable.FolderSpec
- )
- val maxAvailableSize =
- attrs.getDimensionPixelSize(
- R.styleable.FolderSpec_maxAvailableSize,
- 0
- )
- val specType =
- SpecType.values()[
- attrs.getInt(
- R.styleable.FolderSpec_specType,
- SpecType.HEIGHT.ordinal
- )]
- attrs.recycle()
-
- var startPadding: SizeSpec? = null
- var endPadding: SizeSpec? = null
- var gutter: SizeSpec? = null
- var cellSize: SizeSpec? = null
-
- val limitDepth = parser.depth
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > limitDepth) && type != XmlPullParser.END_DOCUMENT
- ) {
- val attr: AttributeSet = Xml.asAttributeSet(parser)
- if (type == XmlPullParser.START_TAG) {
- val sizeSpec = SizeSpec.create(resourceHelper, attr)
- when (parser.name) {
- XmlTags.START_PADDING -> startPadding = sizeSpec
- XmlTags.END_PADDING -> endPadding = sizeSpec
- XmlTags.GUTTER -> gutter = sizeSpec
- XmlTags.CELL_SIZE -> cellSize = sizeSpec
- }
- }
- }
-
- checkNotNull(startPadding) {
- "Attr 'startPadding' in FolderSpec must be defined."
- }
- checkNotNull(endPadding) {
- "Attr 'endPadding' in FolderSpec must be defined."
- }
- checkNotNull(gutter) { "Attr 'gutter' in FolderSpec must be defined." }
- checkNotNull(cellSize) {
- "Attr 'cellSize' in FolderSpec must be defined."
- }
-
- val folderSpec =
- FolderSpec(
- maxAvailableSize,
- specType,
- startPadding,
- endPadding,
- gutter,
- cellSize
- )
-
- check(folderSpec.isValid()) { "Invalid FolderSpec found." }
-
- if (folderSpec.specType == SpecType.HEIGHT) {
- _heightSpecs += folderSpec
- } else {
- _widthSpecs += folderSpec
- }
- }
- }
-
- check(_widthSpecs.isNotEmpty() && _heightSpecs.isNotEmpty()) {
- "FolderSpecs is incomplete - " +
- "height list size = ${_heightSpecs.size}; " +
- "width list size = ${_widthSpecs.size}."
- }
- }
- }
- } catch (e: Exception) {
- when (e) {
- is IOException,
- is XmlPullParserException -> {
- throw RuntimeException("Failure parsing folder specs file.", e)
- }
- else -> throw e
- }
- } finally {
- parser?.close()
- }
- }
-
- /**
- * Returns the [CalculatedFolderSpec] for width, based on the available width, FolderSpecs and
- * WorkspaceSpecs.
- */
- fun getWidthSpec(
+ fun getCalculatedWidthSpec(
columns: Int,
availableWidth: Int,
- workspaceSpec: CalculatedWorkspaceSpec
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
): CalculatedFolderSpec {
- check(workspaceSpec.workspaceSpec.specType == WorkspaceSpec.SpecType.WIDTH) {
+ check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
"Invalid specType for CalculatedWorkspaceSpec. " +
- "Expected: ${WorkspaceSpec.SpecType.WIDTH} - " +
- "Found: ${workspaceSpec.workspaceSpec.specType}}"
+ "Expected: ${SpecType.WIDTH} - " +
+ "Found: ${calculatedWorkspaceSpec.spec.specType}}"
}
- val widthSpec = _widthSpecs.firstOrNull { availableWidth <= it.maxAvailableSize }
- check(widthSpec != null) { "No FolderSpec for width spec found with $availableWidth." }
-
- return convertToCalculatedFolderSpec(widthSpec, availableWidth, columns, workspaceSpec)
+ val spec = getWidthSpec(availableWidth)
+ return CalculatedFolderSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
}
- /**
- * Returns the [CalculatedFolderSpec] for height, based on the available height, FolderSpecs and
- * WorkspaceSpecs.
- */
- fun getHeightSpec(
+ fun getCalculatedHeightSpec(
rows: Int,
availableHeight: Int,
- workspaceSpec: CalculatedWorkspaceSpec
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
): CalculatedFolderSpec {
- check(workspaceSpec.workspaceSpec.specType == WorkspaceSpec.SpecType.HEIGHT) {
+ check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
"Invalid specType for CalculatedWorkspaceSpec. " +
- "Expected: ${WorkspaceSpec.SpecType.HEIGHT} - " +
- "Found: ${workspaceSpec.workspaceSpec.specType}}"
+ "Expected: ${SpecType.HEIGHT} - " +
+ "Found: ${calculatedWorkspaceSpec.spec.specType}}"
}
- val heightSpec = _heightSpecs.firstOrNull { availableHeight <= it.maxAvailableSize }
- check(heightSpec != null) { "No FolderSpec for height spec found with $availableHeight." }
+ val spec = getHeightSpec(availableHeight)
+ return CalculatedFolderSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
+ }
- return convertToCalculatedFolderSpec(heightSpec, availableHeight, rows, workspaceSpec)
+ companion object {
+
+ private const val XML_FOLDER_SPEC = "folderSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): FolderSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_FOLDER_SPEC, ::FolderSpec)
+ val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
+ return FolderSpecs(widthSpecs, heightSpecs)
+ }
}
}
-data class CalculatedFolderSpec(
- val availableSpace: Int,
- val cells: Int,
- val startPaddingPx: Int,
- val endPaddingPx: Int,
- val gutterPx: Int,
- val cellSizePx: Int
-)
-
-/**
- * Responsive folder specs to be used to calculate the paddings, gutter and cell size for folders in
- * the workspace.
- *
- * @param maxAvailableSize indicates the breakpoint to use this specification.
- * @param specType indicates whether the paddings and gutters will be applied vertically or
- * horizontally.
- * @param startPadding padding used at the top or left (right in RTL) in the workspace folder.
- * @param endPadding padding used at the bottom or right (left in RTL) in the workspace folder.
- * @param gutter the space between the cells vertically or horizontally depending on the [specType].
- * @param cellSize height or width of the cell depending on the [specType].
- */
data class FolderSpec(
- val maxAvailableSize: Int,
- val specType: SpecType,
- val startPadding: SizeSpec,
- val endPadding: SizeSpec,
- val gutter: SizeSpec,
- val cellSize: SizeSpec
-) {
+ override val maxAvailableSize: Int,
+ override val specType: SpecType,
+ override val startPadding: SizeSpec,
+ override val endPadding: SizeSpec,
+ override val gutter: SizeSpec,
+ override val cellSize: SizeSpec
+) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
- enum class SpecType {
- HEIGHT,
- WIDTH
+ init {
+ check(isValid()) { "Invalid FolderSpec found." }
}
- fun isValid(): Boolean {
- if (maxAvailableSize <= 0) {
- Log.e(LOG_TAG, "FolderSpec#isValid - maxAvailableSize <= 0")
- return false
- }
-
- // All specs are valid
- if (
- !(startPadding.isValid() &&
- endPadding.isValid() &&
- gutter.isValid() &&
- cellSize.isValid())
- ) {
- Log.e(LOG_TAG, "FolderSpec#isValid - !allSpecsAreValid()")
- return false
- }
-
- return true
- }
-}
-
-/** Helper function to convert [FolderSpec] to [CalculatedFolderSpec] */
-private fun convertToCalculatedFolderSpec(
- folderSpec: FolderSpec,
- availableSpace: Int,
- cells: Int,
- workspaceSpec: CalculatedWorkspaceSpec
-): CalculatedFolderSpec {
- // Map if is fixedSize, ofAvailableSpace or matchWorkspace
- var startPaddingPx =
- folderSpec.startPadding.getCalculatedValue(availableSpace, workspaceSpec.startPaddingPx)
- var endPaddingPx =
- folderSpec.endPadding.getCalculatedValue(availableSpace, workspaceSpec.endPaddingPx)
- var gutterPx = folderSpec.gutter.getCalculatedValue(availableSpace, workspaceSpec.gutterPx)
- var cellSizePx =
- folderSpec.cellSize.getCalculatedValue(availableSpace, workspaceSpec.cellSizePx)
-
- // Remainder space
- val gutters = cells - 1
- val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
- val remainderSpace = availableSpace - usedSpace
-
- startPaddingPx = folderSpec.startPadding.getRemainderSpaceValue(remainderSpace, startPaddingPx)
- endPaddingPx = folderSpec.endPadding.getRemainderSpaceValue(remainderSpace, endPaddingPx)
- gutterPx = folderSpec.gutter.getRemainderSpaceValue(remainderSpace, gutterPx)
- cellSizePx = folderSpec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx)
-
- return CalculatedFolderSpec(
- availableSpace = availableSpace,
- cells = cells,
- startPaddingPx = startPaddingPx,
- endPaddingPx = endPaddingPx,
- gutterPx = gutterPx,
- cellSizePx = cellSizePx
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ SpecType.values()[
+ attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
+ startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
+ endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
+ gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
+ cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
)
}
+
+class CalculatedFolderSpec(
+ availableSpace: Int,
+ cells: Int,
+ spec: FolderSpec,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecs.kt b/src/com/android/launcher3/responsive/ResponsiveSpecs.kt
new file mode 100644
index 0000000..72a0ea4
--- /dev/null
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecs.kt
@@ -0,0 +1,222 @@
+/*
+ * 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.util.Log
+
+/**
+ * Base class for responsive specs that holds a list of width and height specs.
+ *
+ * @param widthSpecs List of width responsive specifications
+ * @param heightSpecs List of height responsive specifications
+ */
+abstract class ResponsiveSpecs<T : ResponsiveSpec>(
+ val widthSpecs: List<T>,
+ val heightSpecs: List<T>
+) {
+
+ init {
+ check(widthSpecs.isNotEmpty() && heightSpecs.isNotEmpty()) {
+ "${this::class.simpleName} is incomplete - " +
+ "width list size = ${widthSpecs.size}; " +
+ "height list size = ${heightSpecs.size}."
+ }
+ }
+
+ /**
+ * Get a [ResponsiveSpec] for width within the breakpoint.
+ *
+ * @param availableWidth The width breakpoint for the spec
+ * @return A [ResponsiveSpec] for width.
+ */
+ fun getWidthSpec(availableWidth: Int): T {
+ val spec = widthSpecs.firstOrNull { availableWidth <= it.maxAvailableSize }
+ check(spec != null) { "No available width spec found within $availableWidth." }
+ return spec
+ }
+
+ /**
+ * Get a [ResponsiveSpec] for height within the breakpoint.
+ *
+ * @param availableHeight The height breakpoint for the spec
+ * @return A [ResponsiveSpec] for height.
+ */
+ fun getHeightSpec(availableHeight: Int): T {
+ val spec = heightSpecs.firstOrNull { availableHeight <= it.maxAvailableSize }
+ check(spec != null) { "No available height spec found within $availableHeight." }
+ return spec
+ }
+}
+
+/**
+ * Base class for a responsive specification that is used to calculate the paddings, gutter and cell
+ * size.
+ *
+ * @param maxAvailableSize indicates the breakpoint to use this specification.
+ * @param specType indicates whether the paddings and gutters will be applied vertically or
+ * horizontally.
+ * @param startPadding padding used at the top or left (right in RTL) in the workspace folder.
+ * @param endPadding padding used at the bottom or right (left in RTL) in the workspace folder.
+ * @param gutter the space between the cells vertically or horizontally depending on the [specType].
+ * @param cellSize height or width of the cell depending on the [specType].
+ */
+abstract class ResponsiveSpec(
+ open val maxAvailableSize: Int,
+ open val specType: SpecType,
+ open val startPadding: SizeSpec,
+ open val endPadding: SizeSpec,
+ open val gutter: SizeSpec,
+ open val cellSize: SizeSpec
+) {
+ open fun isValid(): Boolean {
+ if (maxAvailableSize <= 0) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - maxAvailableSize <= 0")
+ return false
+ }
+
+ // All specs need to be individually valid
+ if (!allSpecsAreValid()) {
+ Log.e(LOG_TAG, "${this::class.simpleName}#isValid - !allSpecsAreValid()")
+ return false
+ }
+
+ return true
+ }
+
+ private fun allSpecsAreValid(): Boolean {
+ return startPadding.isValid() &&
+ endPadding.isValid() &&
+ gutter.isValid() &&
+ cellSize.isValid()
+ }
+
+ enum class SpecType {
+ HEIGHT,
+ WIDTH
+ }
+
+ companion object {
+ private const val LOG_TAG = "ResponsiveSpec"
+ }
+}
+
+/**
+ * Calculated responsive specs contains the final paddings, gutter and cell size in pixels after
+ * they are calculated from the available space, cells and workspace specs.
+ */
+sealed class CalculatedResponsiveSpec {
+ var availableSpace: Int = 0
+ private set
+
+ var cells: Int = 0
+ private set
+
+ var startPaddingPx: Int = 0
+ private set
+
+ var endPaddingPx: Int = 0
+ private set
+
+ var gutterPx: Int = 0
+ private set
+
+ var cellSizePx: Int = 0
+ private set
+
+ var spec: ResponsiveSpec
+ private set
+
+ constructor(
+ availableSpace: Int,
+ cells: Int,
+ spec: ResponsiveSpec,
+ calculatedWorkspaceSpec: CalculatedWorkspaceSpec
+ ) {
+ this.availableSpace = availableSpace
+ this.cells = cells
+ this.spec = spec
+
+ // Map if is fixedSize, ofAvailableSpace or matchWorkspace
+ startPaddingPx =
+ spec.startPadding.getCalculatedValue(
+ availableSpace,
+ calculatedWorkspaceSpec.startPaddingPx
+ )
+ endPaddingPx =
+ spec.endPadding.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.endPaddingPx)
+ gutterPx = spec.gutter.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.gutterPx)
+ cellSizePx =
+ spec.cellSize.getCalculatedValue(availableSpace, calculatedWorkspaceSpec.cellSizePx)
+
+ updateRemainderSpaces(availableSpace, cells, spec)
+ }
+
+ constructor(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
+ this.availableSpace = availableSpace
+ this.cells = cells
+ this.spec = spec
+
+ // Map if is fixedSize or ofAvailableSpace
+ startPaddingPx = spec.startPadding.getCalculatedValue(availableSpace)
+ endPaddingPx = spec.endPadding.getCalculatedValue(availableSpace)
+ gutterPx = spec.gutter.getCalculatedValue(availableSpace)
+ cellSizePx = spec.cellSize.getCalculatedValue(availableSpace)
+
+ updateRemainderSpaces(availableSpace, cells, spec)
+ }
+
+ private fun updateRemainderSpaces(availableSpace: Int, cells: Int, spec: ResponsiveSpec) {
+ val gutters = cells - 1
+ val usedSpace = startPaddingPx + endPaddingPx + (gutterPx * gutters) + (cellSizePx * cells)
+ val remainderSpace = availableSpace - usedSpace
+
+ startPaddingPx = spec.startPadding.getRemainderSpaceValue(remainderSpace, startPaddingPx)
+ endPaddingPx = spec.endPadding.getRemainderSpaceValue(remainderSpace, endPaddingPx)
+ gutterPx = spec.gutter.getRemainderSpaceValue(remainderSpace, gutterPx)
+ cellSizePx = spec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx)
+ }
+
+ override fun hashCode(): Int {
+ var result = availableSpace.hashCode()
+ result = 31 * result + cells.hashCode()
+ result = 31 * result + startPaddingPx.hashCode()
+ result = 31 * result + endPaddingPx.hashCode()
+ result = 31 * result + gutterPx.hashCode()
+ result = 31 * result + cellSizePx.hashCode()
+ result = 31 * result + spec.hashCode()
+ return result
+ }
+
+ override fun equals(other: Any?): Boolean {
+ return other is CalculatedResponsiveSpec &&
+ availableSpace == other.availableSpace &&
+ cells == other.cells &&
+ startPaddingPx == other.startPaddingPx &&
+ endPaddingPx == other.endPaddingPx &&
+ gutterPx == other.gutterPx &&
+ cellSizePx == other.cellSizePx &&
+ spec == other.spec
+ }
+
+ override fun toString(): String {
+ return "${this::class.simpleName}(" +
+ "availableSpace=$availableSpace, cells=$cells, startPaddingPx=$startPaddingPx, " +
+ "endPaddingPx=$endPaddingPx, gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
+ "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" +
+ ")"
+ }
+}
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt b/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt
new file mode 100644
index 0000000..a89b619
--- /dev/null
+++ b/src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.res.TypedArray
+import android.content.res.XmlResourceParser
+import android.util.Xml
+import com.android.launcher3.R
+import com.android.launcher3.util.ResourceHelper
+import java.io.IOException
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+class ResponsiveSpecsParser(private val resourceHelper: ResourceHelper) {
+
+ private fun parseSizeSpecs(parser: XmlResourceParser): Map<String, SizeSpec> {
+ val parentName = parser.name
+ parser.next()
+
+ val result = mutableMapOf<String, SizeSpec>()
+ while (parser.eventType != XmlPullParser.END_DOCUMENT && parser.name != parentName) {
+ if (parser.eventType == XmlResourceParser.START_TAG) {
+ result[parser.name] = SizeSpec.create(resourceHelper, Xml.asAttributeSet(parser))
+ }
+ parser.next()
+ }
+
+ return result
+ }
+
+ fun <T> parseXML(
+ tagName: String,
+ map: (attributes: TypedArray, sizeSpecs: Map<String, SizeSpec>) -> T
+ ): List<T> {
+ val parser: XmlResourceParser = resourceHelper.getXml()
+
+ try {
+ val list = mutableListOf<T>()
+
+ var eventType = parser.eventType
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlResourceParser.START_TAG && parser.name == tagName) {
+ val attrs =
+ resourceHelper.obtainStyledAttributes(
+ Xml.asAttributeSet(parser),
+ R.styleable.ResponsiveSpec
+ )
+
+ val sizeSpecs = parseSizeSpecs(parser)
+ list += map(attrs, sizeSpecs)
+ attrs.recycle()
+ }
+
+ eventType = parser.next()
+ }
+
+ parser.close()
+
+ return list
+ } catch (e: Exception) {
+ when (e) {
+ is NoSuchFieldException,
+ is IOException,
+ is XmlPullParserException ->
+ throw RuntimeException("Failure parsing specs file.", e)
+ else -> throw e
+ }
+ } finally {
+ parser.close()
+ }
+ }
+}
+
+fun Map<String, SizeSpec>.getOrError(key: String): SizeSpec {
+ return this.getOrElse(key) { error("Attr '$key' must be defined.") }
+}
diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt
index 3d618f9..d3868f0 100644
--- a/src/com/android/launcher3/responsive/SizeSpec.kt
+++ b/src/com/android/launcher3/responsive/SizeSpec.kt
@@ -1,3 +1,19 @@
+/*
+ * 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.res.TypedArray
@@ -26,7 +42,7 @@
) {
/** Retrieves the correct value for [SizeSpec]. */
- fun getCalculatedValue(availableSpace: Int, workspaceValue: Int): Int {
+ fun getCalculatedValue(availableSpace: Int, workspaceValue: Int = 0): Int {
val calculatedValue =
when {
fixedSize > 0 -> fixedSize.roundToInt()
@@ -91,6 +107,13 @@
return true
}
+ object XmlTags {
+ const val START_PADDING = "startPadding"
+ const val END_PADDING = "endPadding"
+ const val GUTTER = "gutter"
+ const val CELL_SIZE = "cellSize"
+ }
+
companion object {
private const val TAG = "SizeSpec"
diff --git a/src/com/android/launcher3/responsive/WorkspaceSpecs.kt b/src/com/android/launcher3/responsive/WorkspaceSpecs.kt
new file mode 100644
index 0000000..0da7026
--- /dev/null
+++ b/src/com/android/launcher3/responsive/WorkspaceSpecs.kt
@@ -0,0 +1,98 @@
+/*
+ * 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.res.TypedArray
+import android.util.Log
+import com.android.launcher3.R
+import com.android.launcher3.responsive.ResponsiveSpec.SpecType
+import com.android.launcher3.util.ResourceHelper
+
+private const val TAG = "WorkspaceSpecs"
+
+class WorkspaceSpecs(widthSpecs: List<WorkspaceSpec>, heightSpecs: List<WorkspaceSpec>) :
+ ResponsiveSpecs<WorkspaceSpec>(widthSpecs, heightSpecs) {
+
+ fun getCalculatedWidthSpec(columns: Int, availableWidth: Int): CalculatedWorkspaceSpec {
+ val spec = getWidthSpec(availableWidth)
+ return CalculatedWorkspaceSpec(availableWidth, columns, spec)
+ }
+
+ fun getCalculatedHeightSpec(rows: Int, availableHeight: Int): CalculatedWorkspaceSpec {
+ val spec = getHeightSpec(availableHeight)
+ return CalculatedWorkspaceSpec(availableHeight, rows, spec)
+ }
+
+ companion object {
+ private const val XML_WORKSPACE_SPEC = "workspaceSpec"
+
+ @JvmStatic
+ fun create(resourceHelper: ResourceHelper): WorkspaceSpecs {
+ val parser = ResponsiveSpecsParser(resourceHelper)
+ val specs = parser.parseXML(XML_WORKSPACE_SPEC, ::WorkspaceSpec)
+ val (widthSpecs, heightSpecs) = specs.partition { it.specType == SpecType.WIDTH }
+ return WorkspaceSpecs(widthSpecs, heightSpecs)
+ }
+ }
+}
+
+data class WorkspaceSpec(
+ override val maxAvailableSize: Int,
+ override val specType: SpecType,
+ override val startPadding: SizeSpec,
+ override val endPadding: SizeSpec,
+ override val gutter: SizeSpec,
+ override val cellSize: SizeSpec
+) : ResponsiveSpec(maxAvailableSize, specType, startPadding, endPadding, gutter, cellSize) {
+
+ init {
+ check(isValid()) { "Invalid WorkspaceSpec found." }
+ }
+
+ constructor(
+ attrs: TypedArray,
+ specs: Map<String, SizeSpec>
+ ) : this(
+ maxAvailableSize =
+ attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
+ specType =
+ SpecType.values()[
+ attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
+ startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
+ endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
+ gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
+ cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
+ )
+
+ override fun isValid(): Boolean {
+ // Workspace spec should not match workspace
+ if (
+ startPadding.matchWorkspace ||
+ endPadding.matchWorkspace ||
+ gutter.matchWorkspace ||
+ cellSize.matchWorkspace
+ ) {
+ Log.e(TAG, "WorkspaceSpec#isValid - workspace shouldn't contain matchWorkspace!")
+ return false
+ }
+
+ return super.isValid()
+ }
+}
+
+class CalculatedWorkspaceSpec(availableSpace: Int, cells: Int, spec: WorkspaceSpec) :
+ CalculatedResponsiveSpec(availableSpace, cells, spec)
diff --git a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt b/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
deleted file mode 100644
index 8cc0c59..0000000
--- a/src/com/android/launcher3/workspace/WorkspaceSpecs.kt
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * 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.res.XmlResourceParser
-import android.util.AttributeSet
-import android.util.Log
-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
-import org.xmlpull.v1.XmlPullParser
-import org.xmlpull.v1.XmlPullParserException
-
-private const val TAG = "WorkspaceSpecs"
-
-class WorkspaceSpecs(resourceHelper: ResourceHelper) {
- object XmlTags {
- const val WORKSPACE_SPECS = "workspaceSpecs"
-
- const val WORKSPACE_SPEC = "workspaceSpec"
- const val START_PADDING = "startPadding"
- const val END_PADDING = "endPadding"
- const val GUTTER = "gutter"
- const val CELL_SIZE = "cellSize"
- }
-
- val workspaceHeightSpecList = mutableListOf<WorkspaceSpec>()
- val workspaceWidthSpecList = mutableListOf<WorkspaceSpec>()
-
- // TODO(b/286538013) Remove this init after a more generic or reusable parser is created
- init {
- try {
- val parser: XmlResourceParser = resourceHelper.getXml()
- val depth = parser.depth
- var type: Int
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
- ) {
- if (type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPECS == parser.name) {
- val displayDepth = parser.depth
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > displayDepth) && type != XmlPullParser.END_DOCUMENT
- ) {
- if (
- type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPEC == parser.name
- ) {
- val attrs =
- resourceHelper.obtainStyledAttributes(
- Xml.asAttributeSet(parser),
- R.styleable.WorkspaceSpec
- )
- val maxAvailableSize =
- attrs.getDimensionPixelSize(
- R.styleable.WorkspaceSpec_maxAvailableSize,
- 0
- )
- val specType =
- WorkspaceSpec.SpecType.values()[
- attrs.getInt(
- R.styleable.WorkspaceSpec_specType,
- WorkspaceSpec.SpecType.HEIGHT.ordinal
- )]
- attrs.recycle()
-
- var startPadding: SizeSpec? = null
- var endPadding: SizeSpec? = null
- var gutter: SizeSpec? = null
- var cellSize: SizeSpec? = null
-
- val limitDepth = parser.depth
- while (
- (parser.next().also { type = it } != XmlPullParser.END_TAG ||
- parser.depth > limitDepth) && type != XmlPullParser.END_DOCUMENT
- ) {
- val attr: AttributeSet = Xml.asAttributeSet(parser)
- if (type == XmlPullParser.START_TAG) {
- when (parser.name) {
- XmlTags.START_PADDING -> {
- startPadding = SizeSpec.create(resourceHelper, attr)
- }
- XmlTags.END_PADDING -> {
- endPadding = SizeSpec.create(resourceHelper, attr)
- }
- XmlTags.GUTTER -> {
- gutter = SizeSpec.create(resourceHelper, attr)
- }
- XmlTags.CELL_SIZE -> {
- cellSize = SizeSpec.create(resourceHelper, attr)
- }
- }
- }
- }
-
- if (
- startPadding == null ||
- endPadding == null ||
- gutter == null ||
- cellSize == null
- ) {
- throw IllegalStateException(
- "All attributes in workspaceSpec must be defined"
- )
- }
-
- val workspaceSpec =
- WorkspaceSpec(
- maxAvailableSize,
- specType,
- startPadding,
- endPadding,
- gutter,
- cellSize
- )
- if (workspaceSpec.isValid()) {
- if (workspaceSpec.specType == WorkspaceSpec.SpecType.HEIGHT)
- workspaceHeightSpecList.add(workspaceSpec)
- else workspaceWidthSpecList.add(workspaceSpec)
- } else {
- throw IllegalStateException("Invalid workspaceSpec found.")
- }
- }
- }
-
- if (workspaceWidthSpecList.isEmpty() || workspaceHeightSpecList.isEmpty()) {
- throw IllegalStateException(
- "WorkspaceSpecs is incomplete - " +
- "height list size = ${workspaceHeightSpecList.size}; " +
- "width list size = ${workspaceWidthSpecList.size}."
- )
- }
- }
- }
- parser.close()
- } catch (e: Exception) {
- when (e) {
- is IOException,
- is XmlPullParserException -> {
- throw RuntimeException("Failure parsing workspaces specs file.", e)
- }
- else -> throw e
- }
- }
- }
-
- /**
- * 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()
- }
-
- override fun toString(): String {
- return "CalculatedWorkspaceSpec(availableSpace=$availableSpace, " +
- "cells=$cells, startPaddingPx=$startPaddingPx, endPaddingPx=$endPaddingPx, " +
- "gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
- "workspaceSpec.maxAvailableSize=${workspaceSpec.maxAvailableSize})"
- }
-}
-
-data class WorkspaceSpec(
- val maxAvailableSize: Int,
- val specType: SpecType,
- val startPadding: SizeSpec,
- val endPadding: SizeSpec,
- val gutter: SizeSpec,
- val cellSize: SizeSpec
-) {
-
- enum class SpecType {
- HEIGHT,
- WIDTH
- }
-
- fun isValid(): Boolean {
- if (maxAvailableSize <= 0) {
- Log.e(TAG, "WorkspaceSpec#isValid - maxAvailableSize <= 0")
- return false
- }
-
- // All specs need to be individually valid
- if (!allSpecsAreValid()) {
- Log.e(TAG, "WorkspaceSpec#isValid - !allSpecsAreValid()")
- return false
- }
-
- return true
- }
-
- private fun allSpecsAreValid(): Boolean =
- 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 0d586c2..e5ee064 100644
--- a/tests/res/values/attrs.xml
+++ b/tests/res/values/attrs.xml
@@ -18,7 +18,7 @@
<!-- Attributes have to be copied to test for correct parsing of files -->
<resources>
<!-- Responsive grids attributes -->
- <declare-styleable name="WorkspaceSpec">
+ <declare-styleable name="ResponsiveSpec">
<attr name="specType" format="integer">
<enum name="height" value="0" />
<enum name="width" value="1" />
@@ -26,12 +26,9 @@
<attr name="maxAvailableSize" format="dimension" />
</declare-styleable>
- <declare-styleable name="SizeSpec">
- <attr name="fixedSize" format="dimension" />
- <attr name="ofAvailableSpace" format="float" />
- <attr name="ofRemainderSpace" format="float" />
- <attr name="matchWorkspace" format="boolean" />
- <attr name="maxSize" format="dimension" />
+ <declare-styleable name="WorkspaceSpec">
+ <attr name="specType" />
+ <attr name="maxAvailableSize" />
</declare-styleable>
<declare-styleable name="FolderSpec">
@@ -43,4 +40,12 @@
<attr name="specType" />
<attr name="maxAvailableSize" />
</declare-styleable>
+
+ <declare-styleable name="SizeSpec">
+ <attr name="fixedSize" format="dimension" />
+ <attr name="ofAvailableSpace" format="float" />
+ <attr name="ofRemainderSpace" format="float" />
+ <attr name="matchWorkspace" format="boolean" />
+ <attr name="maxSize" format="dimension" />
+ </declare-styleable>
</resources>
diff --git a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt b/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
index 77ea5ba..cd95e99 100644
--- a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt
@@ -41,9 +41,9 @@
@Test
fun parseValidFile() {
val allAppsSpecs =
- AllAppsSpecs(TestResourceHelper(context!!, TestR.xml.valid_all_apps_file))
- assertThat(allAppsSpecs.allAppsHeightSpecList.size).isEqualTo(1)
- assertThat(allAppsSpecs.allAppsHeightSpecList[0].toString())
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_all_apps_file))
+ assertThat(allAppsSpecs.heightSpecs.size).isEqualTo(1)
+ assertThat(allAppsSpecs.heightSpecs[0].toString())
.isEqualTo(
"AllAppsSpec(" +
"maxAvailableSize=26247, " +
@@ -71,8 +71,8 @@
")"
)
- assertThat(allAppsSpecs.allAppsWidthSpecList.size).isEqualTo(1)
- assertThat(allAppsSpecs.allAppsWidthSpecList[0].toString())
+ assertThat(allAppsSpecs.widthSpecs.size).isEqualTo(1)
+ assertThat(allAppsSpecs.widthSpecs[0].toString())
.isEqualTo(
"AllAppsSpec(" +
"maxAvailableSize=26247, " +
@@ -103,16 +103,16 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingTag_throwsError() {
- AllAppsSpecs(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_1))
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_1))
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
- AllAppsSpecs(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_2))
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_2))
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_valueBiggerThan1_throwsError() {
- AllAppsSpecs(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_3))
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.invalid_all_apps_file_case_3))
}
}
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
index 9f981fa..0f12e58 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt
@@ -23,7 +23,6 @@
import com.android.launcher3.AbstractDeviceProfileTest
import com.android.launcher3.tests.R as TestR
import com.android.launcher3.util.TestResourceHelper
-import com.android.launcher3.workspace.WorkspaceSpecs
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,12 +48,12 @@
val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 495
val workspaceSpecs =
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
val allAppsSpecs =
- AllAppsSpecs(TestResourceHelper(context!!, TestR.xml.valid_all_apps_file))
+ AllAppsSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_all_apps_file))
with(allAppsSpecs.getCalculatedWidthSpec(4, availableWidth, widthSpec)) {
assertThat(availableSpace).isEqualTo(availableWidth)
diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
index c14722c..863cf76 100644
--- a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt
@@ -24,7 +24,6 @@
import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.tests.R
import com.android.launcher3.util.TestResourceHelper
-import com.android.launcher3.workspace.WorkspaceSpecs
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -48,11 +47,11 @@
// Loading workspace specs
val resourceHelperWorkspace = TestResourceHelper(context!!, R.xml.valid_workspace_file)
- val workspaceSpecs = WorkspaceSpecs(resourceHelperWorkspace)
+ val workspaceSpecs = WorkspaceSpecs.create(resourceHelperWorkspace)
// Loading folders specs
val resourceHelperFolder = TestResourceHelper(context!!, R.xml.valid_folders_specs)
- val folderSpecs = FolderSpecs(resourceHelperFolder)
+ val folderSpecs = FolderSpecs.create(resourceHelperFolder)
assertThat(folderSpecs.widthSpecs.size).isEqualTo(2)
assertThat(folderSpecs.widthSpecs[0].cellSize.matchWorkspace).isEqualTo(true)
@@ -62,7 +61,7 @@
var availableWidth = deviceSpec.naturalSize.first
var calculatedWorkspace = workspaceSpecs.getCalculatedWidthSpec(columns, availableWidth)
var calculatedWidthFolderSpec =
- folderSpecs.getWidthSpec(columns, availableWidth, calculatedWorkspace)
+ folderSpecs.getCalculatedWidthSpec(columns, availableWidth, calculatedWorkspace)
with(calculatedWidthFolderSpec) {
assertThat(availableSpace).isEqualTo(availableWidth)
assertThat(cells).isEqualTo(columns)
@@ -76,7 +75,7 @@
availableWidth = 2000.dpToPx()
calculatedWorkspace = workspaceSpecs.getCalculatedWidthSpec(columns, availableWidth)
calculatedWidthFolderSpec =
- folderSpecs.getWidthSpec(columns, availableWidth, calculatedWorkspace)
+ folderSpecs.getCalculatedWidthSpec(columns, availableWidth, calculatedWorkspace)
with(calculatedWidthFolderSpec) {
assertThat(availableSpace).isEqualTo(availableWidth)
assertThat(cells).isEqualTo(columns)
@@ -97,11 +96,11 @@
// Loading workspace specs
val resourceHelperWorkspace = TestResourceHelper(context!!, R.xml.valid_workspace_file)
- val workspaceSpecs = WorkspaceSpecs(resourceHelperWorkspace)
+ val workspaceSpecs = WorkspaceSpecs.create(resourceHelperWorkspace)
// Loading folders specs
val resourceHelperFolder = TestResourceHelper(context!!, R.xml.valid_folders_specs)
- val folderSpecs = FolderSpecs(resourceHelperFolder)
+ val folderSpecs = FolderSpecs.create(resourceHelperFolder)
assertThat(folderSpecs.heightSpecs.size).isEqualTo(1)
assertThat(folderSpecs.heightSpecs[0].cellSize.matchWorkspace).isEqualTo(true)
@@ -109,7 +108,7 @@
// Validate height spec
val calculatedWorkspace = workspaceSpecs.getCalculatedHeightSpec(rows, availableHeight)
val calculatedFolderSpec =
- folderSpecs.getHeightSpec(rows, availableHeight, calculatedWorkspace)
+ folderSpecs.getCalculatedHeightSpec(rows, availableHeight, calculatedWorkspace)
with(calculatedFolderSpec) {
assertThat(availableSpace).isEqualTo(availableHeight)
assertThat(cells).isEqualTo(rows)
diff --git a/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
similarity index 94%
rename from tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt
rename to tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
index 7f03ba2..0af694e 100644
--- a/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt
+++ b/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.workspace
+package com.android.launcher3.responsive
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -49,7 +49,7 @@
val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 495
val workspaceSpecs =
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
@@ -86,7 +86,7 @@
val availableHeight = deviceSpec.naturalSize.second - deviceSpec.statusBarNaturalPx - 640
val workspaceSpecs =
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
val widthSpec = workspaceSpecs.getCalculatedWidthSpec(4, availableWidth)
val heightSpec = workspaceSpecs.getCalculatedHeightSpec(5, availableHeight)
diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
index 796bf9a..e21af57 100644
--- a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt
@@ -21,11 +21,10 @@
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.AbstractDeviceProfileTest
+import com.android.launcher3.responsive.ResponsiveSpec.SpecType
import com.android.launcher3.testing.shared.ResourceUtils
import com.android.launcher3.tests.R
import com.android.launcher3.util.TestResourceHelper
-import com.android.launcher3.workspace.CalculatedWorkspaceSpec
-import com.android.launcher3.workspace.WorkspaceSpec
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -44,14 +43,14 @@
@Test
fun parseValidFile() {
val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
- val folderSpecs = FolderSpecs(resourceHelper)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
val sizeSpec16 = SizeSpec(16f.dpToPx())
val widthSpecsExpected =
listOf(
FolderSpec(
maxAvailableSize = 800.dpToPx(),
- specType = FolderSpec.SpecType.WIDTH,
+ specType = SpecType.WIDTH,
startPadding = sizeSpec16,
endPadding = sizeSpec16,
gutter = sizeSpec16,
@@ -59,7 +58,7 @@
),
FolderSpec(
maxAvailableSize = 9999.dpToPx(),
- specType = FolderSpec.SpecType.WIDTH,
+ specType = SpecType.WIDTH,
startPadding = sizeSpec16,
endPadding = sizeSpec16,
gutter = sizeSpec16,
@@ -70,7 +69,7 @@
val heightSpecsExpected =
FolderSpec(
maxAvailableSize = 9999.dpToPx(),
- specType = FolderSpec.SpecType.HEIGHT,
+ specType = SpecType.HEIGHT,
startPadding = SizeSpec(24f.dpToPx()),
endPadding = SizeSpec(64f.dpToPx()),
gutter = sizeSpec16,
@@ -88,25 +87,25 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingTag_throwsError() {
val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_1)
- FolderSpecs(resourceHelper)
+ FolderSpecs.create(resourceHelper)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_2)
- FolderSpecs(resourceHelper)
+ FolderSpecs.create(resourceHelper)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_valueBiggerThan1_throwsError() {
val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_3)
- FolderSpecs(resourceHelper)
+ FolderSpecs.create(resourceHelper)
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingSpecs_throwsError() {
val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_4)
- FolderSpecs(resourceHelper)
+ FolderSpecs.create(resourceHelper)
}
@Test(expected = IllegalStateException::class)
@@ -117,7 +116,7 @@
val workspaceSpec =
WorkspaceSpec(
maxAvailableSize = availableSpace,
- specType = WorkspaceSpec.SpecType.WIDTH,
+ specType = SpecType.WIDTH,
startPadding = SizeSpec(fixedSize = 10f),
endPadding = SizeSpec(fixedSize = 10f),
gutter = SizeSpec(fixedSize = 10f),
@@ -126,8 +125,8 @@
val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_5)
- val folderSpecs = FolderSpecs(resourceHelper)
- folderSpecs.getWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
}
@Test(expected = IllegalStateException::class)
@@ -138,7 +137,7 @@
val workspaceSpec =
WorkspaceSpec(
maxAvailableSize = availableSpace,
- specType = WorkspaceSpec.SpecType.HEIGHT,
+ specType = SpecType.HEIGHT,
startPadding = SizeSpec(fixedSize = 10f),
endPadding = SizeSpec(fixedSize = 10f),
gutter = SizeSpec(fixedSize = 10f),
@@ -147,8 +146,8 @@
val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
val resourceHelper = TestResourceHelper(context!!, R.xml.invalid_folders_specs_5)
- val folderSpecs = FolderSpecs(resourceHelper)
- folderSpecs.getHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
}
@Test
@@ -159,7 +158,7 @@
val workspaceSpec =
WorkspaceSpec(
maxAvailableSize = availableSpace,
- specType = WorkspaceSpec.SpecType.WIDTH,
+ specType = SpecType.WIDTH,
startPadding = SizeSpec(fixedSize = 10f),
endPadding = SizeSpec(fixedSize = 10f),
gutter = SizeSpec(fixedSize = 10f),
@@ -167,21 +166,17 @@
)
val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
- val expectedResult =
- CalculatedFolderSpec(
- startPaddingPx = 16.dpToPx(),
- endPaddingPx = 16.dpToPx(),
- gutterPx = 16.dpToPx(),
- cellSizePx = calculatedWorkspaceSpec.cellSizePx,
- availableSpace = availableSpace,
- cells = cells
- )
-
val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
- val folderSpecs = FolderSpecs(resourceHelper)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
val calculatedWidthSpec =
- folderSpecs.getWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
- assertThat(calculatedWidthSpec).isEqualTo(expectedResult)
+ folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
+
+ assertThat(calculatedWidthSpec.cells).isEqualTo(cells)
+ assertThat(calculatedWidthSpec.availableSpace).isEqualTo(availableSpace)
+ assertThat(calculatedWidthSpec.startPaddingPx).isEqualTo(16.dpToPx())
+ assertThat(calculatedWidthSpec.endPaddingPx).isEqualTo(16.dpToPx())
+ assertThat(calculatedWidthSpec.gutterPx).isEqualTo(16.dpToPx())
+ assertThat(calculatedWidthSpec.cellSizePx).isEqualTo(calculatedWorkspaceSpec.cellSizePx)
}
@Test(expected = IllegalStateException::class)
@@ -192,7 +187,7 @@
val workspaceSpec =
WorkspaceSpec(
maxAvailableSize = availableSpace,
- specType = WorkspaceSpec.SpecType.HEIGHT,
+ specType = SpecType.HEIGHT,
startPadding = SizeSpec(fixedSize = 10f),
endPadding = SizeSpec(fixedSize = 10f),
gutter = SizeSpec(fixedSize = 10f),
@@ -201,8 +196,8 @@
val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
- val folderSpecs = FolderSpecs(resourceHelper)
- folderSpecs.getWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ folderSpecs.getCalculatedWidthSpec(cells, availableSpace, calculatedWorkspaceSpec)
}
@Test
@@ -213,7 +208,7 @@
val workspaceSpec =
WorkspaceSpec(
maxAvailableSize = availableSpace,
- specType = WorkspaceSpec.SpecType.HEIGHT,
+ specType = SpecType.HEIGHT,
startPadding = SizeSpec(fixedSize = 10f),
endPadding = SizeSpec(fixedSize = 10f),
gutter = SizeSpec(fixedSize = 10f),
@@ -221,21 +216,17 @@
)
val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
- val expectedResult =
- CalculatedFolderSpec(
- startPaddingPx = 24.dpToPx(),
- endPaddingPx = 64.dpToPx(),
- gutterPx = 16.dpToPx(),
- cellSizePx = calculatedWorkspaceSpec.cellSizePx,
- availableSpace = availableSpace,
- cells = cells
- )
-
val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
- val folderSpecs = FolderSpecs(resourceHelper)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
val calculatedHeightSpec =
- folderSpecs.getHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
- assertThat(calculatedHeightSpec).isEqualTo(expectedResult)
+ folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
+
+ assertThat(calculatedHeightSpec.cells).isEqualTo(cells)
+ assertThat(calculatedHeightSpec.availableSpace).isEqualTo(availableSpace)
+ assertThat(calculatedHeightSpec.startPaddingPx).isEqualTo(24.dpToPx())
+ assertThat(calculatedHeightSpec.endPaddingPx).isEqualTo(64.dpToPx())
+ assertThat(calculatedHeightSpec.gutterPx).isEqualTo(16.dpToPx())
+ assertThat(calculatedHeightSpec.cellSizePx).isEqualTo(calculatedWorkspaceSpec.cellSizePx)
}
@Test(expected = IllegalStateException::class)
@@ -246,7 +237,7 @@
val workspaceSpec =
WorkspaceSpec(
maxAvailableSize = availableSpace,
- specType = WorkspaceSpec.SpecType.WIDTH,
+ specType = SpecType.WIDTH,
startPadding = SizeSpec(fixedSize = 10f),
endPadding = SizeSpec(fixedSize = 10f),
gutter = SizeSpec(fixedSize = 10f),
@@ -255,8 +246,8 @@
val calculatedWorkspaceSpec = CalculatedWorkspaceSpec(availableSpace, cells, workspaceSpec)
val resourceHelper = TestResourceHelper(context!!, R.xml.valid_folders_specs)
- val folderSpecs = FolderSpecs(resourceHelper)
- folderSpecs.getHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
+ val folderSpecs = FolderSpecs.create(resourceHelper)
+ folderSpecs.getCalculatedHeightSpec(cells, availableSpace, calculatedWorkspaceSpec)
}
private fun Float.dpToPx(): Float {
diff --git a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
similarity index 86%
rename from tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
rename to tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
index 8b99a3a..0364069 100644
--- a/tests/src/com/android/launcher3/workspace/WorkspaceSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.launcher3.workspace
+package com.android.launcher3.responsive
import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -41,9 +41,9 @@
@Test
fun parseValidFile() {
val workspaceSpecs =
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
- assertThat(workspaceSpecs.workspaceHeightSpecList.size).isEqualTo(3)
- assertThat(workspaceSpecs.workspaceHeightSpecList[0].toString())
+ WorkspaceSpecs.create(TestResourceHelper(context!!, TestR.xml.valid_workspace_file))
+ assertThat(workspaceSpecs.heightSpecs.size).isEqualTo(3)
+ assertThat(workspaceSpecs.heightSpecs[0].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=1533, " +
@@ -70,7 +70,7 @@
"maxSize=2147483647)" +
")"
)
- assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
+ assertThat(workspaceSpecs.heightSpecs[1].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=1607, " +
@@ -97,7 +97,7 @@
"maxSize=2147483647)" +
")"
)
- assertThat(workspaceSpecs.workspaceHeightSpecList[2].toString())
+ assertThat(workspaceSpecs.heightSpecs[2].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=26247, " +
@@ -124,8 +124,8 @@
"maxSize=2147483647)" +
")"
)
- assertThat(workspaceSpecs.workspaceWidthSpecList.size).isEqualTo(1)
- assertThat(workspaceSpecs.workspaceWidthSpecList[0].toString())
+ assertThat(workspaceSpecs.widthSpecs.size).isEqualTo(1)
+ assertThat(workspaceSpecs.widthSpecs[0].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=26247, " +
@@ -156,21 +156,29 @@
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_missingTag_throwsError() {
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_1))
+ WorkspaceSpecs.create(
+ TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_1)
+ )
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_moreThanOneValuePerTag_throwsError() {
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_2))
+ WorkspaceSpecs.create(
+ TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_2)
+ )
}
@Test(expected = IllegalStateException::class)
fun parseInvalidFile_valueBiggerThan1_throwsError() {
- WorkspaceSpecs(TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_3))
+ WorkspaceSpecs.create(
+ 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))
+ WorkspaceSpecs.create(
+ TestResourceHelper(context!!, TestR.xml.invalid_workspace_file_case_4)
+ )
}
}