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.content.res.XmlResourceParser 21 import android.util.Xml 22 import com.android.launcher3.R 23 import com.android.launcher3.responsive.ResponsiveSpec.Companion.ResponsiveSpecType 24 import com.android.launcher3.util.ResourceHelper 25 import java.io.IOException 26 import org.xmlpull.v1.XmlPullParser 27 import org.xmlpull.v1.XmlPullParserException 28 29 class ResponsiveSpecsParser(private val resourceHelper: ResourceHelper) { 30 parseXMLnull31 fun <T : IResponsiveSpec> parseXML( 32 responsiveSpecType: ResponsiveSpecType, 33 map: 34 ( 35 responsiveSpecType: ResponsiveSpecType, 36 attributes: TypedArray, 37 sizeSpecs: Map<String, SizeSpec> 38 ) -> T 39 ): List<ResponsiveSpecGroup<T>> { 40 val parser: XmlResourceParser = resourceHelper.getXml() 41 42 try { 43 val groups = mutableListOf<ResponsiveSpecGroup<T>>() 44 val specs = mutableListOf<T>() 45 var groupAttrs: TypedArray? = null 46 47 var eventType = parser.eventType 48 while (eventType != XmlPullParser.END_DOCUMENT) { 49 // Parsing Group 50 when { 51 parser starts ResponsiveSpecGroup.XML_GROUP_NAME -> { 52 groupAttrs = 53 resourceHelper.obtainStyledAttributes( 54 Xml.asAttributeSet(parser), 55 R.styleable.ResponsiveSpecGroup 56 ) 57 } 58 parser ends ResponsiveSpecGroup.XML_GROUP_NAME -> { 59 checkNotNull(groupAttrs) 60 groups += ResponsiveSpecGroup.create(groupAttrs, specs) 61 specs.clear() 62 groupAttrs.recycle() 63 groupAttrs = null 64 } 65 // Mapping Spec to WorkspaceSpec, AllAppsSpec, FolderSpecs, HotseatSpec 66 parser starts responsiveSpecType.xmlTag -> { 67 val attrs = 68 resourceHelper.obtainStyledAttributes( 69 Xml.asAttributeSet(parser), 70 R.styleable.ResponsiveSpec 71 ) 72 73 val sizeSpecs = parseSizeSpecs(parser) 74 specs += map(responsiveSpecType, attrs, sizeSpecs) 75 attrs.recycle() 76 } 77 } 78 79 eventType = parser.next() 80 } 81 82 parser.close() 83 84 // All the specs should have been linked to a group, otherwise the XML is invalid 85 check(specs.isEmpty()) { 86 throw InvalidResponsiveGridSpec( 87 "Invalid XML. ${specs.size} specs not linked to a group." 88 ) 89 } 90 91 return groups 92 } catch (e: Exception) { 93 when (e) { 94 is NoSuchFieldException, 95 is IOException, 96 is XmlPullParserException -> 97 throw RuntimeException("Failure parsing specs file.", e) 98 else -> throw e 99 } 100 } finally { 101 parser.close() 102 } 103 } 104 parseSizeSpecsnull105 private fun parseSizeSpecs(parser: XmlResourceParser): Map<String, SizeSpec> { 106 val parentName = parser.name 107 parser.next() 108 109 val result = mutableMapOf<String, SizeSpec>() 110 while (parser.eventType != XmlPullParser.END_DOCUMENT && parser.name != parentName) { 111 if (parser.eventType == XmlResourceParser.START_TAG) { 112 result[parser.name] = SizeSpec.create(resourceHelper, Xml.asAttributeSet(parser)) 113 } 114 parser.next() 115 } 116 117 return result 118 } 119 startsnull120 private infix fun XmlResourceParser.starts(tag: String): Boolean = 121 name == tag && eventType == XmlPullParser.START_TAG 122 123 private infix fun XmlResourceParser.ends(tag: String): Boolean = 124 name == tag && eventType == XmlPullParser.END_TAG 125 } 126 127 fun Map<String, SizeSpec>.getOrError(key: String): SizeSpec { 128 return this.getOrElse(key) { error("Attr '$key' must be defined.") } 129 } 130 131 class InvalidResponsiveGridSpec(message: String) : Exception(message) 132