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

Commit 8bd7af2b authored by Jordan Silva's avatar Jordan Silva
Browse files

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
parent 80fddb6e
Loading
Loading
Loading
Loading
+12 −7
Original line number Diff line number Diff line
@@ -252,7 +252,7 @@
    </declare-styleable>

    <!--  Responsive grids attributes  -->
    <declare-styleable name="WorkspaceSpec">
    <declare-styleable name="ResponsiveSpec">
        <attr name="specType" format="integer">
            <enum name="height" value="0" />
            <enum name="width" value="1" />
@@ -260,12 +260,9 @@
        <attr name="maxAvailableSize" format="dimension" />
    </declare-styleable>

    <declare-styleable name="SizeSpec">
        <attr name="fixedSize" format="dimension" />
        <attr name="ofAvailableSpace" format="float" />
        <attr name="ofRemainderSpace" format="float" />
        <attr name="matchWorkspace" format="boolean" />
        <attr name="maxSize" format="dimension" />
    <declare-styleable name="WorkspaceSpec">
        <attr name="specType" />
        <attr name="maxAvailableSize" />
    </declare-styleable>

    <declare-styleable name="FolderSpec">
@@ -278,6 +275,14 @@
        <attr name="maxAvailableSize" />
    </declare-styleable>

    <declare-styleable name="SizeSpec">
        <attr name="fixedSize" format="dimension" />
        <attr name="ofAvailableSpace" format="float" />
        <attr name="ofRemainderSpace" format="float" />
        <attr name="matchWorkspace" format="boolean" />
        <attr name="maxSize" format="dimension" />
    </declare-styleable>

    <declare-styleable name="ProfileDisplayOption">
        <attr name="name" />
        <attr name="minWidthDps" format="float" />
+15 −16
Original line number Diff line number Diff line
@@ -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);
        }

+57 −245
Original line number Diff line number Diff line
@@ -16,277 +16,89 @@

package com.android.launcher3.responsive

import android.content.res.XmlResourceParser
import android.util.AttributeSet
import android.util.Log
import android.util.Xml
import android.content.res.TypedArray
import com.android.launcher3.R
import com.android.launcher3.responsive.ResponsiveSpec.SpecType
import com.android.launcher3.util.ResourceHelper
import com.android.launcher3.workspace.CalculatedWorkspaceSpec
import java.io.IOException
import kotlin.math.roundToInt
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException

private const val LOG_TAG = "AllAppsSpecs"
class AllAppsSpecs(widthSpecs: List<AllAppsSpec>, heightSpecs: List<AllAppsSpec>) :
    ResponsiveSpecs<AllAppsSpec>(widthSpecs, heightSpecs) {

class AllAppsSpecs(resourceHelper: ResourceHelper) {
    object XmlTags {
        const val ALL_APPS_SPECS = "allAppsSpecs"

        const val ALL_APPS_SPEC = "allAppsSpec"
        const val START_PADDING = "startPadding"
        const val END_PADDING = "endPadding"
        const val GUTTER = "gutter"
        const val CELL_SIZE = "cellSize"
    }

    val allAppsHeightSpecList = mutableListOf<AllAppsSpec>()
    val allAppsWidthSpecList = mutableListOf<AllAppsSpec>()

    // TODO(b/286538013) Remove this init after a more generic or reusable parser is created
    init {
        var parser: XmlResourceParser? = null
        try {
            parser = resourceHelper.getXml()
            val depth = parser.depth
            var type: Int
            while (
                (parser.next().also { type = it } != XmlPullParser.END_TAG ||
                    parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
            ) {
                if (type == XmlPullParser.START_TAG && XmlTags.ALL_APPS_SPECS == parser.name) {
                    val displayDepth = parser.depth
                    while (
                        (parser.next().also { type = it } != XmlPullParser.END_TAG ||
                            parser.depth > displayDepth) && type != XmlPullParser.END_DOCUMENT
                    ) {
                        if (
                            type == XmlPullParser.START_TAG && XmlTags.ALL_APPS_SPEC == parser.name
                        ) {
                            val attrs =
                                resourceHelper.obtainStyledAttributes(
                                    Xml.asAttributeSet(parser),
                                    R.styleable.AllAppsSpec
                                )
                            val maxAvailableSize =
                                attrs.getDimensionPixelSize(
                                    R.styleable.AllAppsSpec_maxAvailableSize,
                                    0
                                )
                            val specType =
                                AllAppsSpec.SpecType.values()[
                                        attrs.getInt(
                                            R.styleable.AllAppsSpec_specType,
                                            AllAppsSpec.SpecType.HEIGHT.ordinal
                                        )]
                            attrs.recycle()

                            var startPadding: SizeSpec? = null
                            var endPadding: SizeSpec? = null
                            var gutter: SizeSpec? = null
                            var cellSize: SizeSpec? = null

                            val limitDepth = parser.depth
                            while (
                                (parser.next().also { type = it } != XmlPullParser.END_TAG ||
                                    parser.depth > limitDepth) && type != XmlPullParser.END_DOCUMENT
                            ) {
                                val attr: AttributeSet = Xml.asAttributeSet(parser)
                                if (type == XmlPullParser.START_TAG) {
                                    when (parser.name) {
                                        XmlTags.START_PADDING -> {
                                            startPadding = SizeSpec.create(resourceHelper, attr)
                                        }
                                        XmlTags.END_PADDING -> {
                                            endPadding = SizeSpec.create(resourceHelper, attr)
                                        }
                                        XmlTags.GUTTER -> {
                                            gutter = SizeSpec.create(resourceHelper, attr)
                                        }
                                        XmlTags.CELL_SIZE -> {
                                            cellSize = SizeSpec.create(resourceHelper, attr)
                                        }
                                    }
                                }
                            }

                            if (
                                startPadding == null ||
                                    endPadding == null ||
                                    gutter == null ||
                                    cellSize == null
                            ) {
                                throw IllegalStateException(
                                    "All attributes in AllAppsSpec must be defined"
                                )
                            }

                            val allAppsSpec =
                                AllAppsSpec(
                                    maxAvailableSize,
                                    specType,
                                    startPadding,
                                    endPadding,
                                    gutter,
                                    cellSize
                                )
                            if (allAppsSpec.isValid()) {
                                if (allAppsSpec.specType == AllAppsSpec.SpecType.HEIGHT)
                                    allAppsHeightSpecList.add(allAppsSpec)
                                else allAppsWidthSpecList.add(allAppsSpec)
                            } else {
                                throw IllegalStateException("Invalid AllAppsSpec found.")
                            }
                        }
                    }

                    if (allAppsWidthSpecList.isEmpty() || allAppsHeightSpecList.isEmpty()) {
                        throw IllegalStateException(
                            "AllAppsSpecs is incomplete - " +
                                "height list size = ${allAppsHeightSpecList.size}; " +
                                "width list size = ${allAppsWidthSpecList.size}."
                        )
                    }
                }
            }
        } catch (e: Exception) {
            when (e) {
                is IOException,
                is XmlPullParserException -> {
                    throw RuntimeException("Failure parsing all apps specs file.", e)
                }
                else -> throw e
            }
        } finally {
            parser?.close()
        }
    }

    /**
     * Returns the CalculatedAllAppsSpec for width, based on the available width, the AllAppsSpecs
     * and the CalculatedWorkspaceSpec.
     */
    fun getCalculatedWidthSpec(
        columns: Int,
        availableWidth: Int,
        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
    ): CalculatedAllAppsSpec {
        val widthSpec = allAppsWidthSpecList.first { availableWidth <= it.maxAvailableSize }
        check(calculatedWorkspaceSpec.spec.specType == SpecType.WIDTH) {
            "Invalid specType for CalculatedWorkspaceSpec. " +
                "Expected: ${SpecType.WIDTH} - " +
                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
        }

        return CalculatedAllAppsSpec(availableWidth, columns, widthSpec, calculatedWorkspaceSpec)
        val spec = getWidthSpec(availableWidth)
        return CalculatedAllAppsSpec(availableWidth, columns, spec, calculatedWorkspaceSpec)
    }

    /**
     * Returns the CalculatedAllAppsSpec for height, based on the available height, the AllAppsSpecs
     * and the CalculatedWorkspaceSpec.
     */
    fun getCalculatedHeightSpec(
        rows: Int,
        availableHeight: Int,
        calculatedWorkspaceSpec: CalculatedWorkspaceSpec
    ): CalculatedAllAppsSpec {
        val heightSpec = allAppsHeightSpecList.first { availableHeight <= it.maxAvailableSize }

        return CalculatedAllAppsSpec(availableHeight, rows, heightSpec, calculatedWorkspaceSpec)
    }
        check(calculatedWorkspaceSpec.spec.specType == SpecType.HEIGHT) {
            "Invalid specType for CalculatedWorkspaceSpec. " +
                "Expected: ${SpecType.HEIGHT} - " +
                "Found: ${calculatedWorkspaceSpec.spec.specType}}"
        }

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()
        val spec = getHeightSpec(availableHeight)
        return CalculatedAllAppsSpec(availableHeight, rows, spec, calculatedWorkspaceSpec)
    }

        // 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()
    companion object {
        private const val XML_ALL_APPS_SPEC = "allAppsSpec"

        // 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()
        @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)
        }

    override fun toString(): String {
        return "CalculatedAllAppsSpec(availableSpace=$availableSpace, " +
            "cells=$cells, startPaddingPx=$startPaddingPx, endPaddingPx=$endPaddingPx, " +
            "gutterPx=$gutterPx, cellSizePx=$cellSizePx, " +
            "AllAppsSpec.maxAvailableSize=${allAppsSpec.maxAvailableSize})"
    }
}

data class AllAppsSpec(
    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(LOG_TAG, "AllAppsSpec#isValid - maxAvailableSize <= 0")
            return false
        }
    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) {

        // All specs need to be individually valid
        if (!allSpecsAreValid()) {
            Log.e(LOG_TAG, "AllAppsSpec#isValid - !allSpecsAreValid()")
            return false
        }

        return true
    init {
        check(isValid()) { "Invalid AllAppsSpec found." }
    }

    constructor(
        attrs: TypedArray,
        specs: Map<String, SizeSpec>
    ) : this(
        maxAvailableSize =
            attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0),
        specType =
            SpecType.values()[
                    attrs.getInt(R.styleable.ResponsiveSpec_specType, SpecType.HEIGHT.ordinal)],
        startPadding = specs.getOrError(SizeSpec.XmlTags.START_PADDING),
        endPadding = specs.getOrError(SizeSpec.XmlTags.END_PADDING),
        gutter = specs.getOrError(SizeSpec.XmlTags.GUTTER),
        cellSize = specs.getOrError(SizeSpec.XmlTags.CELL_SIZE)
    )
}

    private fun allSpecsAreValid(): Boolean =
        startPadding.isValid() && endPadding.isValid() && gutter.isValid() && cellSize.isValid()
}
class CalculatedAllAppsSpec(
    availableSpace: Int,
    cells: Int,
    spec: AllAppsSpec,
    calculatedWorkspaceSpec: CalculatedWorkspaceSpec
) : CalculatedResponsiveSpec(availableSpace, cells, spec, calculatedWorkspaceSpec)
+73 −248

File changed.

Preview size limit exceeded, changes collapsed.

+222 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading