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