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
This commit is contained in:
Jordan Silva
2023-06-21 15:35:03 +01:00
parent 80fddb6e40
commit 8bd7af2b3f
16 changed files with 687 additions and 892 deletions
+12 -7
View File
@@ -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" />
+15 -16
View File
@@ -56,15 +56,15 @@ import com.android.launcher3.model.data.ItemInfo;
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 @@ public class DeviceProfile {
// 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 @@ public class DeviceProfile {
// 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);
}
@@ -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)
@@ -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)
@@ -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}" +
")"
}
}
@@ -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.") }
}
@@ -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 @@ data class SizeSpec(
) {
/** 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 @@ data class SizeSpec(
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"
@@ -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)
@@ -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
}
+12 -7
View File
@@ -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>
@@ -41,9 +41,9 @@ class AllAppsSpecsTest : AbstractDeviceProfileTest() {
@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 @@ class AllAppsSpecsTest : AbstractDeviceProfileTest() {
")"
)
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 @@ class AllAppsSpecsTest : AbstractDeviceProfileTest() {
@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))
}
}
@@ -23,7 +23,6 @@ import androidx.test.platform.app.InstrumentationRegistry
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 @@ class CalculatedAllAppsSpecTest : AbstractDeviceProfileTest() {
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)
@@ -24,7 +24,6 @@ import com.android.launcher3.AbstractDeviceProfileTest
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 @@ class CalculatedFolderSpecsTest : AbstractDeviceProfileTest() {
// 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 @@ class CalculatedFolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class CalculatedFolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class CalculatedFolderSpecsTest : AbstractDeviceProfileTest() {
// 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 @@ class CalculatedFolderSpecsTest : AbstractDeviceProfileTest() {
// 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)
@@ -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 @@ class CalculatedWorkspaceSpecTest : AbstractDeviceProfileTest() {
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 @@ class CalculatedWorkspaceSpecTest : AbstractDeviceProfileTest() {
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)
@@ -21,11 +21,10 @@ 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.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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
@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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
),
FolderSpec(
maxAvailableSize = 9999.dpToPx(),
specType = FolderSpec.SpecType.WIDTH,
specType = SpecType.WIDTH,
startPadding = sizeSpec16,
endPadding = sizeSpec16,
gutter = sizeSpec16,
@@ -70,7 +69,7 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
@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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
)
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
)
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 @@ class FolderSpecsTest : AbstractDeviceProfileTest() {
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 {
@@ -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 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() {
@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 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() {
"maxSize=2147483647)" +
")"
)
assertThat(workspaceSpecs.workspaceHeightSpecList[1].toString())
assertThat(workspaceSpecs.heightSpecs[1].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=1607, " +
@@ -97,7 +97,7 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() {
"maxSize=2147483647)" +
")"
)
assertThat(workspaceSpecs.workspaceHeightSpecList[2].toString())
assertThat(workspaceSpecs.heightSpecs[2].toString())
.isEqualTo(
"WorkspaceSpec(" +
"maxAvailableSize=26247, " +
@@ -124,8 +124,8 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() {
"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 @@ class WorkspaceSpecsTest : AbstractDeviceProfileTest() {
@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)
)
}
}