1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.responsive 18 19 import android.content.res.TypedArray 20 import android.util.Log 21 import com.android.launcher3.R 22 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType 23 import com.android.launcher3.responsive.ResponsiveSpec.DimensionType 24 import com.android.launcher3.util.ResourceHelper 25 26 class HotseatSpecsProvider(groupOfSpecs: List<ResponsiveSpecGroup<HotseatSpec>>) { 27 28 private val groupOfSpecs: List<ResponsiveSpecGroup<HotseatSpec>> 29 30 init { <lambda>null31 this.groupOfSpecs = groupOfSpecs.sortedBy { it.aspectRatio } 32 } 33 getSpecsByAspectRationull34 fun getSpecsByAspectRatio(aspectRatio: Float): ResponsiveSpecGroup<HotseatSpec> { 35 check(aspectRatio > 0f) { "Invalid aspect ratio! The value should be bigger than 0." } 36 37 val specsGroup = groupOfSpecs.firstOrNull { aspectRatio <= it.aspectRatio } 38 check(specsGroup != null) { "No available spec with aspectRatio within $aspectRatio." } 39 40 return specsGroup 41 } 42 getSpecIgnoringDimensionTypenull43 private fun getSpecIgnoringDimensionType( 44 availableSize: Int, 45 specsGroup: ResponsiveSpecGroup<HotseatSpec> 46 ): HotseatSpec? { 47 val specWidth = specsGroup.widthSpecs.firstOrNull { availableSize <= it.maxAvailableSize } 48 val specHeight = specsGroup.heightSpecs.firstOrNull { availableSize <= it.maxAvailableSize } 49 return specWidth ?: specHeight 50 } 51 getCalculatedSpecnull52 fun getCalculatedSpec( 53 aspectRatio: Float, 54 dimensionType: DimensionType, 55 availableSpace: Int, 56 ): CalculatedHotseatSpec { 57 val specsGroup = getSpecsByAspectRatio(aspectRatio) 58 59 // TODO(b/315548992): Ignore the dimension type to prevent crash before launcher 60 // data migration is finished. The restore process allows the initialization of 61 // an invalid or disabled grid until the data is restored and migrated. 62 val spec = getSpecIgnoringDimensionType(availableSpace, specsGroup) 63 check(spec != null) { "No available spec found within $availableSpace. $specsGroup" } 64 // val spec = specsGroup.getSpec(dimensionType, availableSpace) 65 return CalculatedHotseatSpec(availableSpace, spec) 66 } 67 68 companion object { 69 @JvmStatic createnull70 fun create(resourceHelper: ResourceHelper): HotseatSpecsProvider { 71 val parser = ResponsiveSpecsParser(resourceHelper) 72 val specs = parser.parseXML(ResponsiveSpecType.Hotseat, ::HotseatSpec) 73 return HotseatSpecsProvider(specs) 74 } 75 } 76 } 77 78 data class HotseatSpec( 79 override val maxAvailableSize: Int, 80 override val dimensionType: DimensionType, 81 override val specType: ResponsiveSpecType, 82 val hotseatQsbSpace: SizeSpec, 83 val edgePadding: SizeSpec 84 ) : IResponsiveSpec { 85 init { <lambda>null86 check(isValid()) { "Invalid HotseatSpec found." } 87 } 88 89 constructor( 90 responsiveSpecType: ResponsiveSpecType, 91 attrs: TypedArray, 92 specs: Map<String, SizeSpec> 93 ) : this( 94 maxAvailableSize = 95 attrs.getDimensionPixelSize(R.styleable.ResponsiveSpec_maxAvailableSize, 0), 96 dimensionType = 97 DimensionType.entries[ 98 attrs.getInt( 99 R.styleable.ResponsiveSpec_dimensionType, 100 DimensionType.HEIGHT.ordinal 101 )], 102 specType = responsiveSpecType, 103 hotseatQsbSpace = specs.getOrError(SizeSpec.XmlTags.HOTSEAT_QSB_SPACE), 104 edgePadding = specs.getOrError(SizeSpec.XmlTags.EDGE_PADDING) 105 ) 106 isValidnull107 fun isValid(): Boolean { 108 if (maxAvailableSize <= 0) { 109 logError("The property maxAvailableSize must be higher than 0.") 110 return false 111 } 112 113 // All specs need to be individually valid 114 if (!allSpecsAreValid()) { 115 logError("One or more specs are invalid!") 116 return false 117 } 118 119 if (!isValidFixedSize()) { 120 logError("The total Fixed Size used must be lower or equal to $maxAvailableSize.") 121 return false 122 } 123 124 return true 125 } 126 allSpecsAreValidnull127 private fun allSpecsAreValid(): Boolean { 128 return hotseatQsbSpace.isValid() && 129 hotseatQsbSpace.onlyFixedSize() && 130 edgePadding.isValid() && 131 edgePadding.onlyFixedSize() 132 } 133 isValidFixedSizenull134 private fun isValidFixedSize() = 135 hotseatQsbSpace.fixedSize + edgePadding.fixedSize <= maxAvailableSize 136 137 private fun logError(message: String) { 138 Log.e(LOG_TAG, "$LOG_TAG #isValid - $message - $this") 139 } 140 141 companion object { 142 private const val LOG_TAG = "HotseatSpec" 143 } 144 } 145 146 class CalculatedHotseatSpec(val availableSpace: Int, val spec: HotseatSpec) { 147 148 var hotseatQsbSpace: Int = 0 149 private set 150 151 var edgePadding: Int = 0 152 private set 153 154 init { 155 hotseatQsbSpace = spec.hotseatQsbSpace.getCalculatedValue(availableSpace) 156 edgePadding = spec.edgePadding.getCalculatedValue(availableSpace) 157 } 158 hashCodenull159 override fun hashCode(): Int { 160 var result = availableSpace.hashCode() 161 result = 31 * result + hotseatQsbSpace.hashCode() 162 result = 31 * result + edgePadding.hashCode() 163 result = 31 * result + spec.hashCode() 164 return result 165 } 166 equalsnull167 override fun equals(other: Any?): Boolean { 168 return other is CalculatedHotseatSpec && 169 availableSpace == other.availableSpace && 170 hotseatQsbSpace == other.hotseatQsbSpace && 171 edgePadding == other.edgePadding && 172 spec == other.spec 173 } 174 toStringnull175 override fun toString(): String { 176 return "${this::class.simpleName}(" + 177 "availableSpace=$availableSpace, hotseatQsbSpace=$hotseatQsbSpace, " + 178 "edgePadding=$edgePadding, " + 179 "${spec::class.simpleName}.maxAvailableSize=${spec.maxAvailableSize}" + 180 ")" 181 } 182 } 183