• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.workspace
18 
19 import android.content.res.TypedArray
20 import android.content.res.XmlResourceParser
21 import android.util.AttributeSet
22 import android.util.Log
23 import android.util.TypedValue
24 import android.util.Xml
25 import com.android.launcher3.R
26 import com.android.launcher3.util.ResourceHelper
27 import java.io.IOException
28 import org.xmlpull.v1.XmlPullParser
29 import org.xmlpull.v1.XmlPullParserException
30 
31 private const val TAG = "WorkspaceSpecs"
32 
33 class WorkspaceSpecs(resourceHelper: ResourceHelper) {
34     object XmlTags {
35         const val WORKSPACE_SPECS = "workspaceSpecs"
36 
37         const val WORKSPACE_SPEC = "workspaceSpec"
38         const val START_PADDING = "startPadding"
39         const val END_PADDING = "endPadding"
40         const val GUTTER = "gutter"
41         const val CELL_SIZE = "cellSize"
42     }
43 
44     val workspaceHeightSpecList = mutableListOf<WorkspaceSpec>()
45     val workspaceWidthSpecList = mutableListOf<WorkspaceSpec>()
46 
47     init {
48         try {
49             val parser: XmlResourceParser = resourceHelper.getXml()
50             val depth = parser.depth
51             var type: Int
52             while (
<lambda>null53                 (parser.next().also { type = it } != XmlPullParser.END_TAG ||
54                     parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
55             ) {
56                 if (type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPECS == parser.name) {
57                     val displayDepth = parser.depth
58                     while (
<lambda>null59                         (parser.next().also { type = it } != XmlPullParser.END_TAG ||
60                             parser.depth > displayDepth) && type != XmlPullParser.END_DOCUMENT
61                     ) {
62                         if (
63                             type == XmlPullParser.START_TAG && XmlTags.WORKSPACE_SPEC == parser.name
64                         ) {
65                             val attrs =
66                                 resourceHelper.obtainStyledAttributes(
67                                     Xml.asAttributeSet(parser),
68                                     R.styleable.WorkspaceSpec
69                                 )
70                             val maxAvailableSize =
71                                 attrs.getDimensionPixelSize(
72                                     R.styleable.WorkspaceSpec_maxAvailableSize,
73                                     0
74                                 )
75                             val specType =
76                                 WorkspaceSpec.SpecType.values()[
77                                         attrs.getInt(
78                                             R.styleable.WorkspaceSpec_specType,
79                                             WorkspaceSpec.SpecType.HEIGHT.ordinal
80                                         )]
81                             attrs.recycle()
82 
83                             var startPadding: SizeSpec? = null
84                             var endPadding: SizeSpec? = null
85                             var gutter: SizeSpec? = null
86                             var cellSize: SizeSpec? = null
87 
88                             val limitDepth = parser.depth
89                             while (
<lambda>null90                                 (parser.next().also { type = it } != XmlPullParser.END_TAG ||
91                                     parser.depth > limitDepth) && type != XmlPullParser.END_DOCUMENT
92                             ) {
93                                 val attr: AttributeSet = Xml.asAttributeSet(parser)
94                                 if (type == XmlPullParser.START_TAG) {
95                                     when (parser.name) {
96                                         XmlTags.START_PADDING -> {
97                                             startPadding = SizeSpec(resourceHelper, attr)
98                                         }
99                                         XmlTags.END_PADDING -> {
100                                             endPadding = SizeSpec(resourceHelper, attr)
101                                         }
102                                         XmlTags.GUTTER -> {
103                                             gutter = SizeSpec(resourceHelper, attr)
104                                         }
105                                         XmlTags.CELL_SIZE -> {
106                                             cellSize = SizeSpec(resourceHelper, attr)
107                                         }
108                                     }
109                                 }
110                             }
111 
112                             if (
113                                 startPadding == null ||
114                                     endPadding == null ||
115                                     gutter == null ||
116                                     cellSize == null
117                             ) {
118                                 throw IllegalStateException(
119                                     "All attributes in workspaceSpec must be defined"
120                                 )
121                             }
122 
123                             val workspaceSpec =
124                                 WorkspaceSpec(
125                                     maxAvailableSize,
126                                     specType,
127                                     startPadding,
128                                     endPadding,
129                                     gutter,
130                                     cellSize
131                                 )
132                             if (workspaceSpec.isValid()) {
133                                 if (workspaceSpec.specType == WorkspaceSpec.SpecType.HEIGHT)
134                                     workspaceHeightSpecList.add(workspaceSpec)
135                                 else workspaceWidthSpecList.add(workspaceSpec)
136                             } else {
137                                 throw IllegalStateException("Invalid workspaceSpec found.")
138                             }
139                         }
140                     }
141 
142                     if (workspaceWidthSpecList.isEmpty() || workspaceHeightSpecList.isEmpty()) {
143                         throw IllegalStateException(
144                             "WorkspaceSpecs is incomplete - " +
145                                 "height list size = ${workspaceHeightSpecList.size}; " +
146                                 "width list size = ${workspaceWidthSpecList.size}."
147                         )
148                     }
149                 }
150             }
151             parser.close()
152         } catch (e: Exception) {
153             when (e) {
154                 is IOException,
155                 is XmlPullParserException -> {
156                     throw RuntimeException("Failure parsing workspaces specs file.", e)
157                 }
158                 else -> throw e
159             }
160         }
161     }
162 }
163 
164 data class WorkspaceSpec(
165     val maxAvailableSize: Int,
166     val specType: SpecType,
167     val startPadding: SizeSpec,
168     val endPadding: SizeSpec,
169     val gutter: SizeSpec,
170     val cellSize: SizeSpec
171 ) {
172 
173     enum class SpecType {
174         HEIGHT,
175         WIDTH
176     }
177 
isValidnull178     fun isValid(): Boolean {
179         if (maxAvailableSize <= 0) {
180             Log.e(TAG, "WorkspaceSpec#isValid - maxAvailableSize <= 0")
181             return false
182         }
183 
184         // All specs need to be individually valid
185         if (!allSpecsAreValid()) {
186             Log.e(TAG, "WorkspaceSpec#isValid - !allSpecsAreValid()")
187             return false
188         }
189 
190         return true
191     }
192 
allSpecsAreValidnull193     private fun allSpecsAreValid(): Boolean =
194         startPadding.isValid() && endPadding.isValid() && gutter.isValid() && cellSize.isValid()
195 }
196 
197 class SizeSpec(resourceHelper: ResourceHelper, attrs: AttributeSet) {
198     val fixedSize: Float
199     val ofAvailableSpace: Float
200     val ofRemainderSpace: Float
201 
202     init {
203         val styledAttrs = resourceHelper.obtainStyledAttributes(attrs, R.styleable.SpecSize)
204 
205         fixedSize = getValue(styledAttrs, R.styleable.SpecSize_fixedSize)
206         ofAvailableSpace = getValue(styledAttrs, R.styleable.SpecSize_ofAvailableSpace)
207         ofRemainderSpace = getValue(styledAttrs, R.styleable.SpecSize_ofRemainderSpace)
208 
209         styledAttrs.recycle()
210     }
211 
212     private fun getValue(a: TypedArray, index: Int): Float {
213         if (a.getType(index) == TypedValue.TYPE_DIMENSION) {
214             return a.getDimensionPixelSize(index, 0).toFloat()
215         } else if (a.getType(index) == TypedValue.TYPE_FLOAT) {
216             return a.getFloat(index, 0f)
217         }
218         return 0f
219     }
220 
221     fun isValid(): Boolean {
222         // All attributes are empty
223         if (fixedSize <= 0f && ofAvailableSpace <= 0f && ofRemainderSpace <= 0f) {
224             Log.e(TAG, "SizeSpec#isValid - all attributes are empty")
225             return false
226         }
227 
228         // More than one attribute is filled
229         val attrCount =
230             (if (fixedSize > 0) 1 else 0) +
231                 (if (ofAvailableSpace > 0) 1 else 0) +
232                 (if (ofRemainderSpace > 0) 1 else 0)
233         if (attrCount > 1) {
234             Log.e(TAG, "SizeSpec#isValid - more than one attribute is filled")
235             return false
236         }
237 
238         // Values should be between 0 and 1
239         if (ofAvailableSpace !in 0f..1f || ofRemainderSpace !in 0f..1f) {
240             Log.e(TAG, "SizeSpec#isValid - values should be between 0 and 1")
241             return false
242         }
243 
244         return true
245     }
246 
247     override fun toString(): String {
248         return "SizeSpec(fixedSize=$fixedSize, ofAvailableSpace=$ofAvailableSpace, " +
249             "ofRemainderSpace=$ofRemainderSpace)"
250     }
251 }
252