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