• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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.model
18 
19 import android.app.blob.BlobHandle
20 import android.app.blob.BlobStoreManager
21 import android.content.Context
22 import android.os.ParcelFileDescriptor.AutoCloseInputStream
23 import android.provider.Settings.Secure
24 import android.text.TextUtils
25 import android.util.Base64
26 import android.util.Log
27 import android.util.Xml
28 import com.android.launcher3.AutoInstallsLayout
29 import com.android.launcher3.AutoInstallsLayout.SourceResources
30 import com.android.launcher3.DefaultLayoutParser
31 import com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT
32 import com.android.launcher3.LauncherSettings.Settings
33 import com.android.launcher3.dagger.ApplicationContext
34 import com.android.launcher3.util.IOUtils
35 import com.android.launcher3.util.Partner
36 import com.android.launcher3.widget.LauncherWidgetHolder
37 import java.io.StringReader
38 import javax.inject.Inject
39 
40 private const val TAG = "LayoutParserFactory"
41 
42 /** Utility class for providing default layout parsers */
43 open class LayoutParserFactory
44 @Inject
45 constructor(@ApplicationContext private val context: Context) {
46 
createExternalLayoutParsernull47     open fun createExternalLayoutParser(
48         widgetHolder: LauncherWidgetHolder,
49         openHelper: DatabaseHelper,
50     ): AutoInstallsLayout? {
51 
52         createWorkspaceLoaderFromAppRestriction(widgetHolder, openHelper)?.let {
53             return it
54         }
55         AutoInstallsLayout.get(context, widgetHolder, openHelper)?.let {
56             return it
57         }
58 
59         val partner = Partner.get(context.packageManager)
60         if (partner != null) {
61             val workspaceResId = partner.getXmlResId(RES_PARTNER_DEFAULT_LAYOUT)
62             if (workspaceResId != 0) {
63                 return DefaultLayoutParser(
64                     context,
65                     widgetHolder,
66                     openHelper,
67                     partner.resources,
68                     workspaceResId,
69                 )
70             }
71         }
72         return null
73     }
74 
75     /**
76      * Creates workspace loader from an XML resource listed in the app restrictions.
77      *
78      * @return the loader if the restrictions are set and the resource exists; null otherwise.
79      */
createWorkspaceLoaderFromAppRestrictionnull80     private fun createWorkspaceLoaderFromAppRestriction(
81         widgetHolder: LauncherWidgetHolder,
82         openHelper: DatabaseHelper,
83     ): AutoInstallsLayout? {
84         val systemLayoutProvider =
85             Secure.getString(context.contentResolver, Settings.LAYOUT_PROVIDER_KEY)
86         if (TextUtils.isEmpty(systemLayoutProvider)) {
87             return null
88         }
89 
90         // Try the blob store first
91         val blobManager = context.getSystemService(BlobStoreManager::class.java)
92         if (systemLayoutProvider.startsWith(Settings.BLOB_KEY_PREFIX) && blobManager != null) {
93             val blobHandlerDigest = systemLayoutProvider.substring(Settings.BLOB_KEY_PREFIX.length)
94             try {
95                 AutoCloseInputStream(
96                         blobManager.openBlob(
97                             BlobHandle.createWithSha256(
98                                 Base64.decode(
99                                     blobHandlerDigest,
100                                     Base64.NO_WRAP or Base64.NO_PADDING,
101                                 ),
102                                 Settings.LAYOUT_DIGEST_LABEL,
103                                 0,
104                                 Settings.LAYOUT_DIGEST_TAG,
105                             )
106                         )
107                     )
108                     .use {
109                         return getAutoInstallsLayoutFromIS(
110                             widgetHolder,
111                             openHelper,
112                             String(IOUtils.toByteArray(it)),
113                         )
114                     }
115             } catch (e: Exception) {
116                 Log.e(TAG, "Error getting layout from blob handle", e)
117                 return null
118             }
119         }
120 
121         // Try contentProvider based provider
122         val pm = context.packageManager
123         val pi = pm.resolveContentProvider(systemLayoutProvider, 0)
124         if (pi == null) {
125             Log.e(TAG, "No provider found for authority $systemLayoutProvider")
126             return null
127         }
128         val uri = ModelDbController.getLayoutUri(systemLayoutProvider, context)
129         try {
130             context.contentResolver.openInputStream(uri)?.use {
131                 Log.d(TAG, "Loading layout from $systemLayoutProvider")
132                 val res = pm.getResourcesForApplication(pi.applicationInfo)
133                 return getAutoInstallsLayoutFromIS(
134                     widgetHolder,
135                     openHelper,
136                     String(IOUtils.toByteArray(it)),
137                     SourceResources.wrap(res),
138                 )
139             }
140         } catch (e: Exception) {
141             Log.e(TAG, "Error getting layout stream from: $systemLayoutProvider", e)
142         }
143         return null
144     }
145 
146     @Throws(Exception::class)
getAutoInstallsLayoutFromISnull147     protected fun getAutoInstallsLayoutFromIS(
148         widgetHolder: LauncherWidgetHolder,
149         openHelper: DatabaseHelper,
150         xml: String,
151         res: SourceResources = object : SourceResources {},
152     ): AutoInstallsLayout {
153         val parser = Xml.newPullParser()
154         parser.setInput(StringReader(xml))
155 
156         return AutoInstallsLayout(
157             context,
158             widgetHolder,
159             openHelper,
160             res,
<lambda>null161             { parser },
162             AutoInstallsLayout.TAG_WORKSPACE,
163         )
164     }
165 
166     /** Layout parser factory with fixed xml */
167     class XmlLayoutParserFactory(ctx: Context, private val xml: String) : LayoutParserFactory(ctx) {
168 
createExternalLayoutParsernull169         override fun createExternalLayoutParser(
170             widgetHolder: LauncherWidgetHolder,
171             openHelper: DatabaseHelper,
172         ): AutoInstallsLayout? {
173             try {
174                 return getAutoInstallsLayoutFromIS(widgetHolder, openHelper, xml)
175             } catch (e: Exception) {
176                 Log.e(TAG, "Error getting layout from provided xml", e)
177                 return super.createExternalLayoutParser(widgetHolder, openHelper)
178             }
179         }
180     }
181 }
182