Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1a2d4bd6 authored by Thales Lima's avatar Thales Lima
Browse files

Create an XML parser for WorkspaceSpecs

Extract DeviceProfileTest to Launcher3 so it can be used in other tests as well, and change name of previous base test to be more descriptive.

Bug: 241386436
Test: WorkspaceSpecsTest
Change-Id: I64613bb5a23c374ed15fb6d936192236a541ab9b
parent fcb6a5fc
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@ import android.graphics.Rect
import android.graphics.RectF
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.DeviceProfileBaseTest
import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT
import com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT
import com.android.quickstep.views.TaskView.FullscreenDrawParams
@@ -36,7 +36,7 @@ import org.mockito.Mockito.mock
/** Test for FullscreenDrawParams class. */
@SmallTest
@RunWith(AndroidJUnit4::class)
class FullscreenDrawParamsTest : DeviceProfileBaseTest() {
class FullscreenDrawParamsTest : FakeInvariantDeviceProfileTest() {

    private val TASK_SCALE = 0.7f
    private var mThumbnailData: ThumbnailData = mock(ThumbnailData::class.java)
+2 −2
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@ package com.android.quickstep
import android.graphics.Rect
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.DeviceProfileBaseTest
import com.android.launcher3.FakeInvariantDeviceProfileTest
import com.android.launcher3.util.WindowBounds
import com.google.common.truth.Truth.assertThat
import org.junit.Test
@@ -26,7 +26,7 @@ import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class HotseatWidthCalculationTest : DeviceProfileBaseTest() {
class HotseatWidthCalculationTest : FakeInvariantDeviceProfileTest() {

    /**
     * This is a case when after setting the hotseat, the space needs to be recalculated but it
+15 −0
Original line number Diff line number Diff line
@@ -224,6 +224,21 @@
        <attr name="alignOnIcon" format="boolean" />
    </declare-styleable>

    <!--  Responsive grids attributes  -->
    <declare-styleable name="WorkspaceSpec">
        <attr name="specType" format="integer">
            <enum name="height" value="0" />
            <enum name="width" value="1" />
        </attr>
        <attr name="maxAvailableSize" format="dimension" />
    </declare-styleable>

    <declare-styleable name="SpecSize">
        <attr name="fixedSize" format="dimension" />
        <attr name="ofAvailableSpace" format="float" />
        <attr name="ofRemainderSpace" format="float" />
    </declare-styleable>

    <declare-styleable name="ProfileDisplayOption">
        <attr name="name" />
        <attr name="minWidthDps" format="float" />
+37 −0
Original line number Diff line number Diff line
/*
 * 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)
    }
}
+252 −0
Original line number Diff line number Diff line
/*
 * 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)"
    }
}
Loading