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.launcher3.widget 18 19 import android.content.Context 20 import com.android.launcher3.BuildConfig 21 import com.android.launcher3.Launcher 22 import com.android.launcher3.LauncherAppState 23 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError 24 import com.android.launcher3.logging.FileLog 25 import com.android.launcher3.model.data.LauncherAppWidgetInfo 26 import com.android.launcher3.qsb.QsbContainerView 27 28 /** Utility class for handling widget inflation taking into account all the restore state updates */ 29 class WidgetInflater(private val context: Context) { 30 31 private val widgetHelper = WidgetManagerHelper(context) 32 inflateAppWidgetnull33 fun inflateAppWidget( 34 item: LauncherAppWidgetInfo, 35 ): InflationResult { 36 if (item.hasOptionFlag(LauncherAppWidgetInfo.OPTION_SEARCH_WIDGET)) { 37 item.providerName = QsbContainerView.getSearchComponentName(context) 38 if (item.providerName == null) { 39 return InflationResult( 40 TYPE_DELETE, 41 reason = "search widget removed because search component cannot be found", 42 restoreErrorType = RestoreError.NO_SEARCH_WIDGET 43 ) 44 } 45 } 46 if (LauncherAppState.INSTANCE.get(context).isSafeModeEnabled) { 47 return InflationResult(TYPE_PENDING) 48 } 49 val appWidgetInfo: LauncherAppWidgetProviderInfo? 50 var removalReason = "" 51 @RestoreError var logReason = RestoreError.APP_NOT_INSTALLED 52 var update = false 53 54 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 55 // The widget id is not valid. Try to find the widget based on the provider info. 56 appWidgetInfo = widgetHelper.findProvider(item.providerName, item.user) 57 if (appWidgetInfo == null) { 58 if (!BuildConfig.WIDGETS_ENABLED) { 59 removalReason = "widgets are disabled on go device." 60 logReason = RestoreError.WIDGETS_DISABLED 61 } else { 62 removalReason = "WidgetManagerHelper cannot find a provider from provider info." 63 logReason = RestoreError.MISSING_WIDGET_PROVIDER 64 } 65 } else if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)) { 66 // since appWidgetInfo is not null anymore, update the provider status 67 item.restoreStatus = 68 item.restoreStatus and LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY.inv() 69 update = true 70 } 71 } else { 72 appWidgetInfo = 73 widgetHelper.getLauncherAppWidgetInfo(item.appWidgetId, item.targetComponent) 74 if (appWidgetInfo == null) { 75 if (item.appWidgetId <= LauncherAppWidgetInfo.CUSTOM_WIDGET_ID) { 76 removalReason = "CustomWidgetManager cannot find provider from that widget id." 77 logReason = RestoreError.MISSING_INFO 78 } else { 79 removalReason = 80 ("AppWidgetManager cannot find provider for that widget id." + 81 " It could be because AppWidgetService is not available, or the" + 82 " appWidgetId has not been bound to a the provider yet, or you" + 83 " don't have access to that appWidgetId.") 84 logReason = RestoreError.INVALID_WIDGET_ID 85 } 86 } 87 } 88 89 // If the provider is ready, but the widget is not yet restored, try to restore it. 90 if ( 91 !item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY) && 92 item.restoreStatus != LauncherAppWidgetInfo.RESTORE_COMPLETED 93 ) { 94 if (appWidgetInfo == null) { 95 return InflationResult( 96 type = TYPE_DELETE, 97 reason = 98 "Removing restored widget: id=${item.appWidgetId} belongs to component ${item.providerName} user ${item.user}, as the provider is null and $removalReason", 99 restoreErrorType = logReason 100 ) 101 } 102 103 // If we do not have a valid id, try to bind an id. 104 if (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID)) { 105 if (!item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_ID_ALLOCATED)) { 106 // Id has not been allocated yet. Allocate a new id. 107 LauncherWidgetHolder.newInstance(context).let { 108 item.appWidgetId = it.allocateAppWidgetId() 109 it.destroy() 110 } 111 item.restoreStatus = 112 item.restoreStatus or LauncherAppWidgetInfo.FLAG_ID_ALLOCATED 113 114 // Also try to bind the widget. If the bind fails, the user will be shown 115 // a click to setup UI, which will ask for the bind permission. 116 val pendingInfo = PendingAddWidgetInfo(appWidgetInfo, item.sourceContainer) 117 pendingInfo.spanX = item.spanX 118 pendingInfo.spanY = item.spanY 119 pendingInfo.minSpanX = item.minSpanX 120 pendingInfo.minSpanY = item.minSpanY 121 var options = pendingInfo.getDefaultSizeOptions(context) 122 val isDirectConfig = 123 item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG) 124 if (isDirectConfig && item.bindOptions != null) { 125 val newOptions = item.bindOptions.extras 126 if (options != null) { 127 newOptions!!.putAll(options) 128 } 129 options = newOptions 130 } 131 val success = 132 widgetHelper.bindAppWidgetIdIfAllowed( 133 item.appWidgetId, 134 appWidgetInfo, 135 options 136 ) 137 138 // We tried to bind once. If we were not able to bind, we would need to 139 // go through the permission dialog, which means we cannot skip the config 140 // activity. 141 item.bindOptions = null 142 item.restoreStatus = 143 item.restoreStatus and LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG.inv() 144 145 // Bind succeeded 146 if (success) { 147 // If the widget has a configure activity, it is still needs to set it 148 // up, otherwise the widget is ready to go. 149 item.restoreStatus = 150 if ((appWidgetInfo.configure == null) || isDirectConfig) 151 LauncherAppWidgetInfo.RESTORE_COMPLETED 152 else LauncherAppWidgetInfo.FLAG_UI_NOT_READY 153 } 154 update = true 155 } 156 } else if ( 157 (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) && 158 (appWidgetInfo.configure == null)) 159 ) { 160 // The widget was marked as UI not ready, but there is no configure activity to 161 // update the UI. 162 item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED 163 update = true 164 } else if ( 165 (item.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_UI_NOT_READY) && 166 appWidgetInfo.configure != null) 167 ) { 168 if (widgetHelper.isAppWidgetRestored(item.appWidgetId)) { 169 item.restoreStatus = LauncherAppWidgetInfo.RESTORE_COMPLETED 170 update = true 171 } 172 } 173 } 174 175 if (item.restoreStatus == LauncherAppWidgetInfo.RESTORE_COMPLETED) { 176 // Verify that we own the widget 177 if (appWidgetInfo == null) { 178 FileLog.e(Launcher.TAG, "Removing invalid widget: id=" + item.appWidgetId) 179 return InflationResult(TYPE_DELETE, reason = removalReason) 180 } 181 item.minSpanX = appWidgetInfo.minSpanX 182 item.minSpanY = appWidgetInfo.minSpanY 183 return InflationResult(TYPE_REAL, isUpdate = update, widgetInfo = appWidgetInfo) 184 } else { 185 return InflationResult(TYPE_PENDING, isUpdate = update, widgetInfo = appWidgetInfo) 186 } 187 } 188 189 data class InflationResult( 190 val type: Int, 191 val reason: String? = null, 192 @RestoreError val restoreErrorType: String = RestoreError.APP_NOT_INSTALLED, 193 val isUpdate: Boolean = false, 194 val widgetInfo: LauncherAppWidgetProviderInfo? = null 195 ) 196 197 companion object { 198 const val TYPE_DELETE = 0 199 200 const val TYPE_PENDING = 1 201 202 const val TYPE_REAL = 2 203 } 204 } 205