1 /*
2  * Copyright (C) 2024 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.wallpaper.util
18 
19 import android.content.Context
20 import android.content.res.XmlResourceParser
21 import android.util.Log
22 import android.util.Xml
23 import com.android.wallpaper.model.LiveWallpaperInfo
24 import com.android.wallpaper.model.PartnerWallpaperInfo
25 import com.android.wallpaper.model.SystemStaticWallpaperInfo
26 import com.android.wallpaper.model.WallpaperCategory
27 import com.android.wallpaper.model.WallpaperInfo
28 import com.android.wallpaper.module.PartnerProvider
29 import dagger.hilt.android.qualifiers.ApplicationContext
30 import java.io.IOException
31 import javax.inject.Inject
32 import javax.inject.Singleton
33 import org.xmlpull.v1.XmlPullParser
34 import org.xmlpull.v1.XmlPullParserException
35 
36 /**
37  * Utility class for parsing an XML file containing information about a list of wallpapers. The
38  * logic in this class has been extracted into a separate class which uses dependency injection and
39  * was earlier present in a single method.
40  */
41 @Singleton
42 class WallpaperParserImpl
43 @Inject
44 constructor(
45     @ApplicationContext private val context: Context,
46     private val partnerProvider: PartnerProvider
47 ) : WallpaperParser {
48 
49     /** This method is responsible for generating list of system categories from the XML file. */
parseSystemCategoriesnull50     override fun parseSystemCategories(parser: XmlResourceParser): List<WallpaperCategory> {
51         val categories = mutableListOf<WallpaperCategory>()
52         try {
53             var priorityTracker = 0
54             val depth = parser.depth
55             var type: Int
56             while (
57                 (parser.next().also { type = it } != XmlPullParser.END_TAG ||
58                     parser.depth > depth) && type != XmlPullParser.END_DOCUMENT
59             ) {
60                 if (type == XmlPullParser.START_TAG && WallpaperCategory.TAG_NAME == parser.name) {
61                     val categoryBuilder =
62                         WallpaperCategory.Builder(
63                             partnerProvider.resources,
64                             Xml.asAttributeSet(parser)
65                         )
66                     categoryBuilder.setPriorityIfEmpty(PRIORITY_SYSTEM + priorityTracker++)
67                     categoryBuilder.addWallpapers(
68                         parseXmlForWallpapersForASingleCategory(parser, categoryBuilder.id)
69                     )
70                     val category = categoryBuilder.build()
71                     category?.let { categories.add(it) }
72                 }
73             }
74         } catch (e: Exception) {
75             when (e) {
76                 is IOException,
77                 is XmlPullParserException -> {
78                     Log.w(TAG, "Failed to parse the XML file of system wallpapers", e)
79                     return emptyList()
80                 }
81                 else -> throw e
82             }
83         }
84         return categories
85     }
86 
87     /**
88      * This method is responsible for parsing resources for PartnerWallpaperInfo wallpapers and
89      * returning a list of such wallpapers.
90      */
parsePartnerWallpaperInfoResourcesnull91     override fun parsePartnerWallpaperInfoResources(): List<WallpaperInfo> {
92         val wallpaperInfos: MutableList<WallpaperInfo> = ArrayList()
93 
94         val partnerRes = partnerProvider.getResources()
95         val packageName = partnerProvider.getPackageName()
96         if (partnerRes == null) {
97             return wallpaperInfos
98         }
99 
100         val resId =
101             partnerRes.getIdentifier(PartnerProvider.LEGACY_WALLPAPER_RES_ID, "array", packageName)
102         // Certain partner configurations don't have wallpapers provided, so need to check; return
103         // early if they are missing.
104         if (resId == 0) {
105             return wallpaperInfos
106         }
107 
108         val extras = partnerRes.getStringArray(resId)
109         for (extra in extras) {
110             val wpResId = partnerRes.getIdentifier(extra, "drawable", packageName)
111             if (wpResId != 0) {
112                 val thumbRes = partnerRes.getIdentifier(extra + "_small", "drawable", packageName)
113                 if (thumbRes != 0) {
114                     val wallpaperInfo: WallpaperInfo = PartnerWallpaperInfo(thumbRes, wpResId)
115                     wallpaperInfos.add(wallpaperInfo)
116                 }
117             } else {
118                 Log.e(TAG, "Couldn't find wallpaper $extra")
119             }
120         }
121 
122         return wallpaperInfos
123     }
124 
125     /**
126      * This method is responsible for parsing the XML for a single category and returning a list of
127      * WallpaperInfo objects.
128      */
parseXmlForWallpapersForASingleCategorynull129     private fun parseXmlForWallpapersForASingleCategory(
130         parser: XmlResourceParser,
131         categoryId: String
132     ): List<WallpaperInfo> {
133         val outputWallpaperInfo = mutableListOf<WallpaperInfo>()
134         val categoryDepth = parser.depth
135         var type: Int
136         while (
137             (parser.next().also { type = it } != XmlPullParser.END_TAG ||
138                 parser.depth > categoryDepth) && type != XmlPullParser.END_DOCUMENT
139         ) {
140             if (type == XmlPullParser.START_TAG) {
141                 var wallpaper: WallpaperInfo? = null
142                 if (SystemStaticWallpaperInfo.TAG_NAME == parser.name) {
143                     wallpaper =
144                         SystemStaticWallpaperInfo.fromAttributeSet(
145                             partnerProvider.packageName,
146                             categoryId,
147                             Xml.asAttributeSet(parser)
148                         )
149                 } else if (LiveWallpaperInfo.TAG_NAME == parser.name) {
150                     wallpaper =
151                         LiveWallpaperInfo.fromAttributeSet(
152                             context,
153                             categoryId,
154                             Xml.asAttributeSet(parser)
155                         )
156                 }
157                 if (wallpaper != null) {
158                     outputWallpaperInfo.add(wallpaper)
159                 }
160             }
161         }
162         return outputWallpaperInfo
163     }
164 
165     companion object {
166         const val PRIORITY_SYSTEM = 100
167         private const val TAG = "WallpaperXMLParser"
168     }
169 }
170