• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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