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