/* * 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.Companion.ResponsiveSpecType import com.android.launcher3.responsive.ResponsiveSpec.DimensionType import com.android.launcher3.util.ResourceHelper class HotseatSpecsProvider(groupOfSpecs: List>) { private val groupOfSpecs: List> init { this.groupOfSpecs = groupOfSpecs.sortedBy { it.aspectRatio } } fun getSpecsByAspectRatio(aspectRatio: Float): ResponsiveSpecGroup { check(aspectRatio > 0f) { "Invalid aspect ratio! The value should be bigger than 0." } val specsGroup = groupOfSpecs.firstOrNull { aspectRatio <= it.aspectRatio } check(specsGroup != null) { "No available spec with aspectRatio within $aspectRatio." } return specsGroup } private fun getSpecIgnoringDimensionType( availableSize: Int, specsGroup: ResponsiveSpecGroup ): HotseatSpec? { val specWidth = specsGroup.widthSpecs.firstOrNull { availableSize <= it.maxAvailableSize } val specHeight = specsGroup.heightSpecs.firstOrNull { availableSize <= it.maxAvailableSize } return specWidth ?: specHeight } fun getCalculatedSpec( aspectRatio: Float, dimensionType: DimensionType, availableSpace: Int, ): CalculatedHotseatSpec { val specsGroup = getSpecsByAspectRatio(aspectRatio) // TODO(b/315548992): Ignore the dimension type to prevent crash before launcher // data migration is finished. The restore process allows the initialization of // an invalid or disabled grid until the data is restored and migrated. val spec = getSpecIgnoringDimensionType(availableSpace, specsGroup) check(spec != null) { "No available spec found within $availableSpace. $specsGroup" } // val spec = specsGroup.getSpec(dimensionType, availableSpace) return CalculatedHotseatSpec(availableSpace, spec) } companion object { @JvmStatic fun create(resourceHelper: ResourceHelper): HotseatSpecsProvider { val parser = ResponsiveSpecsParser(resourceHelper) val specs = parser.parseXML(ResponsiveSpecType.Hotseat, ::HotseatSpec) return HotseatSpecsProvider(specs) } } } data class HotseatSpec( override val maxAvailableSize: Int, override val dimensionType: DimensionType, override val specType: ResponsiveSpecType, val hotseatQsbSpace: SizeSpec, val edgePadding: SizeSpec ) : IResponsiveSpec { init { check(isValid()) { "Invalid HotseatSpec found." } } constructor( responsiveSpecType: ResponsiveSpecType, attrs: TypedArray, specs: Map ) : this( maxAvailableSize = attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0), dimensionType = DimensionType.entries[ attrs.getInt( R.styleable.ResponsiveSpec_dimensionType, DimensionType.HEIGHT.ordinal )], specType = responsiveSpecType, hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE), edgePadding = specs.getOrError(SizeSpec.XmlTags.EDGE_PADDING) ) fun isValid(): Boolean { if (maxAvailableSize <= 0) { logError("The property maxAvailableSize must be higher than 0.") return false } // All specs need to be individually valid if (!allSpecsAreValid()) { logError("One or more specs are invalid!") return false } if (!isValidFixedSize()) { logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.") return false } return true } private fun allSpecsAreValid(): Boolean { return hotseatQsbSpace.isValid() && hotseatQsbSpace.onlyFixedSize() && edgePadding.isValid() && edgePadding.onlyFixedSize() } private fun isValidFixedSize() = hotseatQsbSpace.fixedSize + edgePadding.fixedSize <= maxAvailableSize private fun logError(message: String) { Log.e(LOG_TAG, "${this::class.simpleName}#isValid - $message - $this") } companion object { private const val LOG_TAG = "HotseatSpec" } } class CalculatedHotseatSpec(val availableSpace: Int, val spec: HotseatSpec) { var hotseatQsbSpace: Int = 0 private set var edgePadding: Int = 0 private set init { hotseatQsbSpace = spec.hotseatQsbSpace.getCalculatedValue(availableSpace) edgePadding = spec.edgePadding.getCalculatedValue(availableSpace) } override fun hashCode(): Int { var result = availableSpace.hashCode() result = 31 * result + hotseatQsbSpace.hashCode() result = 31 * result + edgePadding.hashCode() result = 31 * result + spec.hashCode() return result } override fun equals(other: Any?): Boolean { return other is CalculatedHotseatSpec && availableSpace == other.availableSpace && hotseatQsbSpace == other.hotseatQsbSpace && edgePadding == other.edgePadding && spec == other.spec } override fun toString(): String { return "${this::class.simpleName}(" + "availableSpace=$availableSpace, hotseatQsbSpace=$hotseatQsbSpace, " + "edgePadding=$edgePadding, " + "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" + ")" } }