From 8bd7af2b3f181d04460abcc39f65f5879dc42ea8 Mon Sep 17 00:00:00 2001 From: Jordan Silva Date: Wed, 21 Jun 2023 15:35:03 +0100 Subject: [PATCH] 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 --- res/values/attrs.xml | 19 +- src/com/android/launcher3/DeviceProfile.java | 31 +- .../launcher3/responsive/AllAppsSpecs.kt | 304 ++++------------ .../launcher3/responsive/FolderSpecs.kt | 325 ++++-------------- .../launcher3/responsive/ResponsiveSpecs.kt | 222 ++++++++++++ .../responsive/ResponsiveSpecsParser.kt | 90 +++++ .../android/launcher3/responsive/SizeSpec.kt | 25 +- .../launcher3/responsive/WorkspaceSpecs.kt | 98 ++++++ .../launcher3/workspace/WorkspaceSpecs.kt | 281 --------------- tests/res/values/attrs.xml | 19 +- .../launcher3/responsive/AllAppsSpecsTest.kt | 16 +- .../responsive/CalculatedAllAppsSpecTest.kt | 5 +- .../responsive/CalculatedFolderSpecsTest.kt | 15 +- .../CalculatedWorkspaceSpecTest.kt | 6 +- .../launcher3/responsive/FolderSpecsTest.kt | 91 +++-- .../WorkspaceSpecsTest.kt | 32 +- 16 files changed, 687 insertions(+), 892 deletions(-) create mode 100644 src/com/android/launcher3/responsive/ResponsiveSpecs.kt create mode 100644 src/com/android/launcher3/responsive/ResponsiveSpecsParser.kt create mode 100644 src/com/android/launcher3/responsive/WorkspaceSpecs.kt delete mode 100644 src/com/android/launcher3/workspace/WorkspaceSpecs.kt rename tests/src/com/android/launcher3/{workspace => responsive}/CalculatedWorkspaceSpecTest.kt (95%) rename tests/src/com/android/launcher3/{workspace => responsive}/WorkspaceSpecsTest.kt (86%) diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 733f527aff..9803779bf1 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -252,7 +252,7 @@ - + @@ -260,12 +260,9 @@ - - - - - - + + + @@ -278,6 +275,14 @@ + + + + + + + + diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index f3b5155047..88dcfcda28 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -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); } diff --git a/src/com/android/launcher3/responsive/AllAppsSpecs.kt b/src/com/android/launcher3/responsive/AllAppsSpecs.kt index 85e383e3d6..8ed3ffc1bf 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, heightSpecs: List) : + ResponsiveSpecs(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() - val allAppsWidthSpecList = mutableListOf() - - // 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 + ) : 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 f4446bc1d8..bc2db28ef1 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, heightSpecs: List) : + ResponsiveSpecs(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() - val heightSpecs: List - get() = _heightSpecs - - private val _widthSpecs = mutableListOf() - val widthSpecs: List - 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 + ) : 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 0000000000..72a0ea4f72 --- /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( + val widthSpecs: List, + val heightSpecs: List +) { + + 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 0000000000..a89b619c00 --- /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 { + val parentName = parser.name + parser.next() + + val result = mutableMapOf() + 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 parseXML( + tagName: String, + map: (attributes: TypedArray, sizeSpecs: Map) -> T + ): List { + val parser: XmlResourceParser = resourceHelper.getXml() + + try { + val list = mutableListOf() + + 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.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 3d618f989e..d3868f0c20 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 @@ 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" diff --git a/src/com/android/launcher3/responsive/WorkspaceSpecs.kt b/src/com/android/launcher3/responsive/WorkspaceSpecs.kt new file mode 100644 index 0000000000..0da7026bd6 --- /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, heightSpecs: List) : + ResponsiveSpecs(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 + ) : 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 8cc0c5936a..0000000000 --- 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() - val workspaceWidthSpecList = mutableListOf() - - // 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 0d586c2c12..e5ee06473c 100644 --- a/tests/res/values/attrs.xml +++ b/tests/res/values/attrs.xml @@ -18,7 +18,7 @@ - + @@ -26,12 +26,9 @@ - - - - - - + + + @@ -43,4 +40,12 @@ + + + + + + + + diff --git a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt b/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt index 77ea5bacaa..cd95e99b99 100644 --- a/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt +++ b/tests/src/com/android/launcher3/responsive/AllAppsSpecsTest.kt @@ -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)) } } diff --git a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt index 9f981fa3e7..0f12e58a33 100644 --- a/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt +++ b/tests/src/com/android/launcher3/responsive/CalculatedAllAppsSpecTest.kt @@ -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) diff --git a/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedFolderSpecsTest.kt index c14722c828..863cf76185 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.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) diff --git a/tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt b/tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt similarity index 95% rename from tests/src/com/android/launcher3/workspace/CalculatedWorkspaceSpecTest.kt rename to tests/src/com/android/launcher3/responsive/CalculatedWorkspaceSpecTest.kt index 7f03ba2f77..0af694e000 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 @@ 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) diff --git a/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt b/tests/src/com/android/launcher3/responsive/FolderSpecsTest.kt index 796bf9adce..e21af57cd5 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.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 { 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 8b99a3a72f..0364069535 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 @@ 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) + ) } }