Merge "Create an XML parser for WorkspaceSpecs" into tm-qpr-dev
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.TypedArray
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.util.AttributeSet
|
||||
import kotlin.IntArray
|
||||
|
||||
/**
|
||||
* This class is a helper that can be subclassed in tests to provide a way to parse attributes
|
||||
* correctly.
|
||||
*/
|
||||
open class ResourceHelper(private val context: Context, private val specsFileId: Int) {
|
||||
open fun getXml(): XmlResourceParser {
|
||||
return context.resources.getXml(specsFileId)
|
||||
}
|
||||
|
||||
open fun obtainStyledAttributes(attrs: AttributeSet, styleId: IntArray): TypedArray {
|
||||
return context.obtainStyledAttributes(attrs, styleId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* 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.TypedArray
|
||||
import android.content.res.XmlResourceParser
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.util.TypedValue
|
||||
import android.util.Xml
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.util.ResourceHelper
|
||||
import java.io.IOException
|
||||
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>()
|
||||
|
||||
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(resourceHelper, attr)
|
||||
}
|
||||
XmlTags.END_PADDING -> {
|
||||
endPadding = SizeSpec(resourceHelper, attr)
|
||||
}
|
||||
XmlTags.GUTTER -> {
|
||||
gutter = SizeSpec(resourceHelper, attr)
|
||||
}
|
||||
XmlTags.CELL_SIZE -> {
|
||||
cellSize = SizeSpec(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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
class SizeSpec(resourceHelper: ResourceHelper, attrs: AttributeSet) {
|
||||
val fixedSize: Float
|
||||
val ofAvailableSpace: Float
|
||||
val ofRemainderSpace: Float
|
||||
|
||||
init {
|
||||
val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SpecSize)
|
||||
|
||||
fixedSize = getValue(styledAttrs, R.styleable.SpecSize_fixedSize)
|
||||
ofAvailableSpace = getValue(styledAttrs, R.styleable.SpecSize_ofAvailableSpace)
|
||||
ofRemainderSpace = getValue(styledAttrs, R.styleable.SpecSize_ofRemainderSpace)
|
||||
|
||||
styledAttrs.recycle()
|
||||
}
|
||||
|
||||
private fun getValue(a: TypedArray, index: Int): Float {
|
||||
if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
|
||||
return a.getDimensionPixelSize(index, 0).toFloat()
|
||||
} else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
|
||||
return a.getFloat(index, 0f)
|
||||
}
|
||||
return 0f
|
||||
}
|
||||
|
||||
fun isValid(): Boolean {
|
||||
// All attributes are empty
|
||||
if (fixedSize <= 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
|
||||
Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
|
||||
return false
|
||||
}
|
||||
|
||||
// More than one attribute is filled
|
||||
val attrCount =
|
||||
(if (fixedSize > 0) 1 else 0) +
|
||||
(if (ofAvailableSpace > 0) 1 else 0) +
|
||||
(if (ofRemainderSpace > 0) 1 else 0)
|
||||
if (attrCount > 1) {
|
||||
Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled")
|
||||
return false
|
||||
}
|
||||
|
||||
// Values should be between 0 and 1
|
||||
if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) {
|
||||
Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "SizeSpec(fixedSize=$fixedSize, ofAvailableSpace=$ofAvailableSpace, " +
|
||||
"ofRemainderSpace=$ofRemainderSpace)"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user