• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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 package com.android.launcher3.model
17 
18 import android.annotation.SuppressLint
19 import android.appwidget.AppWidgetProviderInfo
20 import android.content.ComponentName
21 import android.content.Context
22 import android.content.Intent
23 import android.content.pm.LauncherApps
24 import android.content.pm.LauncherApps.ShortcutQuery
25 import android.content.pm.PackageInstaller
26 import android.content.pm.ShortcutInfo
27 import android.graphics.Point
28 import android.text.TextUtils
29 import android.util.Log
30 import android.util.LongSparseArray
31 import com.android.launcher3.Flags
32 import com.android.launcher3.InvariantDeviceProfile
33 import com.android.launcher3.LauncherSettings.Favorites
34 import com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RestoreError
35 import com.android.launcher3.icons.CacheableShortcutInfo
36 import com.android.launcher3.icons.IconCache
37 import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKUP_FLAG
38 import com.android.launcher3.logging.FileLog
39 import com.android.launcher3.model.data.AppInfo
40 import com.android.launcher3.model.data.AppPairInfo
41 import com.android.launcher3.model.data.FolderInfo
42 import com.android.launcher3.model.data.IconRequestInfo
43 import com.android.launcher3.model.data.ItemInfoWithIcon
44 import com.android.launcher3.model.data.LauncherAppWidgetInfo
45 import com.android.launcher3.model.data.WorkspaceItemInfo
46 import com.android.launcher3.pm.PackageInstallInfo
47 import com.android.launcher3.pm.UserCache
48 import com.android.launcher3.shortcuts.ShortcutKey
49 import com.android.launcher3.shortcuts.ShortcutRequest
50 import com.android.launcher3.util.ApiWrapper
51 import com.android.launcher3.util.ApplicationInfoWrapper
52 import com.android.launcher3.util.ComponentKey
53 import com.android.launcher3.util.PackageManagerHelper
54 import com.android.launcher3.util.PackageUserKey
55 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
56 import com.android.launcher3.widget.WidgetInflater
57 import com.android.launcher3.widget.util.WidgetSizes
58 
59 /**
60  * This items is used by LoaderTask to process items that have been loaded from the Launcher's DB.
61  * This data, stored in the Favorites table, needs to be processed in order to be shown on the Home
62  * Page.
63  *
64  * This class processes each of those items: App Shortcuts, Widgets, Folders, etc., one at a time.
65  */
66 class WorkspaceItemProcessor(
67     private val c: LoaderCursor,
68     private val memoryLogger: LoaderMemoryLogger?,
69     private val userCache: UserCache,
70     private val userManagerState: UserManagerState,
71     private val launcherApps: LauncherApps,
72     private val pendingPackages: MutableSet<PackageUserKey>,
73     private val shortcutKeyToPinnedShortcuts: Map<ShortcutKey, ShortcutInfo>,
74     private val context: Context,
75     private val idp: InvariantDeviceProfile,
76     private val iconCache: IconCache,
77     private val isSafeMode: Boolean,
78     private val bgDataModel: BgDataModel,
79     private val widgetProvidersMap: MutableMap<ComponentKey, AppWidgetProviderInfo?>,
80     private val installingPkgs: HashMap<PackageUserKey, PackageInstaller.SessionInfo>,
81     private val isSdCardReady: Boolean,
82     private val widgetInflater: WidgetInflater,
83     private val pmHelper: PackageManagerHelper,
84     private val iconRequestInfos: MutableList<IconRequestInfo<WorkspaceItemInfo>>,
85     private val unlockedUsers: LongSparseArray<Boolean>,
86     private val allDeepShortcuts: MutableList<CacheableShortcutInfo>,
87 ) {
88 
89     private val tempPackageKey = PackageUserKey(null, null)
90 
91     /**
92      * This is the entry point for processing 1 workspace item. This method is like the midfielder
93      * that delegates the actual processing to either processAppShortcut, processFolder, or
94      * processWidget depending on what type of item is being processed.
95      *
96      * All the parameters are expected to be shared between many repeated calls of this method, one
97      * for each workspace item.
98      */
processItemnull99     fun processItem() {
100         try {
101             if (c.user == null) {
102                 // User has been deleted, remove the item.
103                 c.markDeleted(
104                     "User has been deleted for item id=${c.id}",
105                     RestoreError.PROFILE_DELETED,
106                 )
107                 return
108             }
109             when (c.itemType) {
110                 Favorites.ITEM_TYPE_APPLICATION,
111                 Favorites.ITEM_TYPE_DEEP_SHORTCUT -> processAppOrDeepShortcut()
112                 Favorites.ITEM_TYPE_FOLDER,
113                 Favorites.ITEM_TYPE_APP_PAIR -> processFolderOrAppPair()
114                 Favorites.ITEM_TYPE_APPWIDGET,
115                 Favorites.ITEM_TYPE_CUSTOM_APPWIDGET -> processWidget()
116             }
117         } catch (e: Exception) {
118             Log.e(TAG, "Desktop items loading interrupted", e)
119         }
120     }
121 
122     /**
123      * This method verifies that an app shortcut should be shown on the home screen, updates the
124      * database accordingly, formats the data in such a way that it is ready to be added to the data
125      * model, and then adds it to the launcher’s data model.
126      *
127      * In this method, verification means that an an app shortcut database entry is required to:
128      * Have a Launch Intent. This is how the app component symbolized by the shortcut is launched.
129      * Have a Package Name. Not be in a funky “Restoring, but never actually restored” state. Not
130      * have null or missing ShortcutInfos or ItemInfos in other data models.
131      *
132      * If any of the above are found to be true, the database entry is deleted, and not shown on the
133      * user’s home screen. When an app is verified, it is marked as restored, meaning that the app
134      * is viable to show on the home screen.
135      *
136      * In order to accommodate different types and versions of App Shortcuts, different properties
137      * and flags are set on the ItemInfo objects that are added to the data model. For example,
138      * icons that are not a part of the workspace or hotseat are marked as using low resolution icon
139      * bitmaps. Currently suspended app icons are marked as such. Installing packages are also
140      * marked as such. Lastly, after applying common properties to the ItemInfo, it is added to the
141      * data model to be bound to the launcher’s data model.
142      */
143     @SuppressLint("NewApi")
processAppOrDeepShortcutnull144     private fun processAppOrDeepShortcut() {
145         var allowMissingTarget = false
146         var intent = c.parseIntent()
147         if (intent == null) {
148             c.markDeleted("Null intent from db for item id=${c.id}", RestoreError.APP_NO_DB_INTENT)
149             return
150         }
151         var disabledState =
152             if (userManagerState.isUserQuiet(c.serialNumber))
153                 WorkspaceItemInfo.FLAG_DISABLED_QUIET_USER
154             else 0
155         val cn = intent.component
156         val targetPkg = cn?.packageName ?: intent.getPackage()
157         if (targetPkg.isNullOrEmpty()) {
158             c.markDeleted(
159                 "No target package for item id=${c.id}",
160                 RestoreError.APP_NO_TARGET_PACKAGE,
161             )
162             return
163         }
164         val appInfoWrapper = ApplicationInfoWrapper(context, targetPkg, c.user)
165         var validTarget = launcherApps.isPackageEnabled(targetPkg, c.user)
166 
167         // If it's a deep shortcut, we'll use pinned shortcuts to restore it
168         if (cn != null && validTarget && (c.itemType != Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
169             // If the apk is present and the shortcut points to a specific component.
170 
171             // If the component is already present
172             if (launcherApps.isActivityEnabled(cn, c.user)) {
173                 // no special handling necessary for this item
174                 c.markRestored()
175             } else {
176                 // Gracefully try to find a fallback activity.
177                 FileLog.d(
178                     TAG,
179                     "Activity not enabled for id=${c.id}, component=$cn, user=${c.user}." +
180                         " Will attempt to find fallback Activity for targetPkg=$targetPkg.",
181                 )
182                 intent = pmHelper.getAppLaunchIntent(targetPkg, c.user)
183                 if (intent != null) {
184                     c.restoreFlag = 0
185                     c.updater().put(Favorites.INTENT, intent.toUri(0)).commit()
186                 } else {
187                     c.markDeleted(
188                         "No Activities found for id=${c.id}, targetPkg=$targetPkg, component=$cn." +
189                             " Unable to create launch Intent.",
190                         RestoreError.APP_NO_LAUNCH_INTENT,
191                     )
192                     return
193                 }
194             }
195         }
196         if (intent.`package` == null) {
197             intent.`package` = targetPkg
198         }
199 
200         val isPreArchivedShortcut =
201             Flags.restoreArchivedShortcuts() &&
202                 appInfoWrapper.isArchived() &&
203                 c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT &&
204                 c.restoreFlag != 0
205 
206         // else if cn == null => can't infer much, leave it
207         // else if !validPkg => could be restored icon or missing sd-card
208         when {
209             !TextUtils.isEmpty(targetPkg) && (!validTarget || isPreArchivedShortcut) -> {
210                 // Points to a valid app (superset of cn != null) but the apk
211                 // is not available.
212                 when {
213                     c.restoreFlag != 0 || isPreArchivedShortcut -> {
214                         // Package is not yet available but might be
215                         // installed later.
216                         FileLog.d(
217                             TAG,
218                             "package not yet restored: $targetPkg, itemType=${c.itemType}" +
219                                 ", isPreArchivedShortcut=$isPreArchivedShortcut" +
220                                 ", restoreFlag=${c.restoreFlag}",
221                         )
222                         tempPackageKey.update(targetPkg, c.user)
223                         when {
224                             c.hasRestoreFlag(WorkspaceItemInfo.FLAG_RESTORE_STARTED) -> {
225                                 // Restore has started once.
226                             }
227                             installingPkgs.containsKey(tempPackageKey) || isPreArchivedShortcut -> {
228                                 // App restore has started. Update the flag
229                                 c.restoreFlag =
230                                     c.restoreFlag or WorkspaceItemInfo.FLAG_RESTORE_STARTED
231                                 FileLog.d(
232                                     TAG,
233                                     "restore started for installing app: $targetPkg, itemType=${c.itemType}",
234                                 )
235                                 c.updater().put(Favorites.RESTORED, c.restoreFlag).commit()
236                             }
237                             else -> {
238                                 c.markDeleted(
239                                     "removing app that is not restored and not installing. package: $targetPkg",
240                                     RestoreError.APP_NOT_RESTORED_OR_INSTALLING,
241                                 )
242                                 return
243                             }
244                         }
245                     }
246                     appInfoWrapper.isOnSdCard() -> {
247                         // Package is present but not available.
248                         disabledState =
249                             disabledState or WorkspaceItemInfo.FLAG_DISABLED_NOT_AVAILABLE
250                         // Add the icon on the workspace anyway.
251                         allowMissingTarget = true
252                     }
253                     !isSdCardReady -> {
254                         // SdCard is not ready yet. Package might get available,
255                         // once it is ready.
256                         Log.d(TAG, "Missing package, will check later: $targetPkg")
257                         pendingPackages.add(PackageUserKey(targetPkg, c.user))
258                         // Add the icon on the workspace anyway.
259                         allowMissingTarget = true
260                     }
261                     else -> {
262                         // Do not wait for external media load anymore.
263                         c.markDeleted(
264                             "Invalid package removed: $targetPkg",
265                             RestoreError.APP_NOT_INSTALLED_EXTERNAL_MEDIA,
266                         )
267                         return
268                     }
269                 }
270             }
271         }
272         if (c.restoreFlag and WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0) {
273             FileLog.d(
274                 TAG,
275                 "restore flag set AND WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI != 0, setting valid target to false: $targetPkg, itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
276             )
277             validTarget = false
278         }
279         if (validTarget && !isPreArchivedShortcut) {
280             FileLog.d(
281                 TAG,
282                 "valid target true, marking restored: $targetPkg," +
283                     " itemType=${c.itemType}, restoreFlag=${c.restoreFlag}",
284             )
285             // The shortcut points to a valid target (either no target
286             // or something which is ready to be used)
287             c.markRestored()
288         }
289         val useLowResIcon = !c.isOnWorkspaceOrHotseat
290         val info: WorkspaceItemInfo?
291         when {
292             c.restoreFlag != 0 -> {
293                 // Already verified above that user is same as default user
294                 info = c.getRestoredItemInfo(intent, isPreArchivedShortcut)
295             }
296             c.itemType == Favorites.ITEM_TYPE_APPLICATION ->
297                 info = c.getAppShortcutInfo(intent, allowMissingTarget, useLowResIcon, false)
298             c.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT -> {
299                 val key = ShortcutKey.fromIntent(intent, c.user)
300                 if (unlockedUsers[c.serialNumber]) {
301                     val pinnedShortcut =
302                         shortcutKeyToPinnedShortcuts[key] ?: retryDeepShortcutById(key)
303                     if (pinnedShortcut == null) {
304                         // The shortcut is no longer valid.
305                         c.markDeleted(
306                             "Pinned shortcut not found from request. package=${key.packageName}, user=${c.user}",
307                             RestoreError.SHORTCUT_NOT_FOUND,
308                         )
309                         return
310                     }
311                     info = WorkspaceItemInfo(pinnedShortcut, context)
312                     // If the pinned deep shortcut is no longer published,
313                     // use the last saved icon instead of the default.
314                     val csi = CacheableShortcutInfo(pinnedShortcut, appInfoWrapper)
315                     iconCache.getShortcutIcon(info, csi, c::loadIconFromDb)
316                     if (appInfoWrapper.isSuspended()) {
317                         info.runtimeStatusFlags =
318                             info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
319                     }
320                     intent = info.getIntent()
321                     allDeepShortcuts.add(csi)
322                 } else {
323                     // Create a shortcut info in disabled mode for now.
324                     info = c.loadSimpleWorkspaceItem()
325                     info.runtimeStatusFlags =
326                         info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER
327                 }
328             }
329             else -> { // item type == ITEM_TYPE_SHORTCUT
330                 info = c.loadSimpleWorkspaceItem()
331 
332                 // Shortcuts are only available on the primary profile
333                 if (appInfoWrapper.isSuspended()) {
334                     disabledState = disabledState or ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED
335                 }
336                 info.options = c.options
337 
338                 // App shortcuts that used to be automatically added to Launcher
339                 // didn't always have the correct intent flags set, so do that here
340                 if (
341                     intent.action != null &&
342                         intent.categories != null &&
343                         intent.action == Intent.ACTION_MAIN &&
344                         intent.categories.contains(Intent.CATEGORY_LAUNCHER)
345                 ) {
346                     intent.addFlags(
347                         Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
348                     )
349                 }
350             }
351         }
352         if (info != null) {
353             if (info.itemType != Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
354                 // Skip deep shortcuts; their title and icons have already been
355                 // loaded above.
356                 iconRequestInfos.add(c.createIconRequestInfo(info, useLowResIcon))
357             }
358             c.applyCommonProperties(info)
359             info.intent = intent
360             info.rank = c.rank
361             info.spanX = 1
362             info.spanY = 1
363             info.runtimeStatusFlags = info.runtimeStatusFlags or disabledState
364             if (isSafeMode && !appInfoWrapper.isSystem()) {
365                 info.runtimeStatusFlags =
366                     info.runtimeStatusFlags or ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE
367             }
368             val activityInfo = c.launcherActivityInfo
369             if (activityInfo != null) {
370                 AppInfo.updateRuntimeFlagsForActivityTarget(
371                     info,
372                     activityInfo,
373                     userCache.getUserInfo(c.user),
374                     ApiWrapper.INSTANCE[context],
375                     pmHelper,
376                 )
377             }
378             if (
379                 (c.restoreFlag != 0 ||
380                     Flags.enableSupportForArchiving() &&
381                         activityInfo != null &&
382                         activityInfo.applicationInfo.isArchived) && !TextUtils.isEmpty(targetPkg)
383             ) {
384                 tempPackageKey.update(targetPkg, c.user)
385                 val si = installingPkgs[tempPackageKey]
386                 if (si == null) {
387                     info.runtimeStatusFlags =
388                         info.runtimeStatusFlags and
389                             ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE.inv()
390                 } else if (
391                     activityInfo == null ||
392                         (Flags.enableSupportForArchiving() &&
393                             activityInfo.applicationInfo.isArchived)
394                 ) {
395                     // For archived apps, include progress info in case there is
396                     // a pending install session post restart of device.
397                     val installProgress = (si.getProgress() * 100).toInt()
398                     info.setProgressLevel(installProgress, PackageInstallInfo.STATUS_INSTALLING)
399                 }
400             }
401             c.checkAndAddItem(info, bgDataModel, memoryLogger)
402         } else {
403             throw RuntimeException("Unexpected null WorkspaceItemInfo")
404         }
405     }
406 
407     /**
408      * It is possible that the data was cleared from ShortcutManager after it was restored. In that
409      * instance, the Launcher would have a valid Shortcut id, but ShortcutManager wouldn't recognize
410      * it as valid. Here we retry by querying ShortcutManager by package name and shortcut id.
411      */
retryDeepShortcutByIdnull412     private fun retryDeepShortcutById(key: ShortcutKey): ShortcutInfo? {
413         FileLog.d(TAG, "retryDeepShortcutById: package=${key.packageName}, shortcutId=${key.id}")
414         return launcherApps
415             .getShortcuts(
416                 ShortcutQuery().apply {
417                     setPackage(key.packageName)
418                     setShortcutIds(listOf(key.id))
419                     setQueryFlags(ShortcutRequest.ALL)
420                 },
421                 key.user,
422             )
423             ?.firstOrNull()
424     }
425 
426     /**
427      * Loads CollectionInfo information from the database and formats it. This function runs while
428      * LoaderTask is still active; some of the processing for folder content items is done after all
429      * the items in the workspace have been loaded. The loaded and formatted CollectionInfo is then
430      * stored in the BgDataModel.
431      */
processFolderOrAppPairnull432     private fun processFolderOrAppPair() {
433         var collection = c.findOrMakeFolder(c.id, bgDataModel)
434         // If we generated a placeholder Folder before this point, it may need to be replaced with
435         // an app pair.
436         if (c.itemType == Favorites.ITEM_TYPE_APP_PAIR && collection is FolderInfo) {
437             val newAppPair = AppPairInfo()
438             // Move the placeholder's contents over to the new app pair.
439             collection.getContents().forEach(newAppPair::add)
440             collection = newAppPair
441         }
442 
443         c.applyCommonProperties(collection)
444         // Do not trim the folder label, as is was set by the user.
445         collection.title = c.getString(c.mTitleIndex)
446         collection.spanX = 1
447         collection.spanY = 1
448         if (collection is FolderInfo) {
449             collection.options = c.options
450         } else {
451             // An app pair may be inside another folder, so it needs to preserve rank information.
452             collection.rank = c.rank
453         }
454 
455         c.markRestored()
456         c.checkAndAddItem(collection, bgDataModel, memoryLogger)
457     }
458 
459     /**
460      * This method, similar to processAppShortcut above, verifies that a widget should be shown on
461      * the home screen, updates the database accordingly, formats the data in such a way that it is
462      * ready to be added to the data model, and then adds it to the launcher’s data model.
463      *
464      * It verifies that: Widgets are not disabled due to the Launcher variety being of the `Go`
465      * type. Search Widgets have a package name. The app behind the widget is still installed on the
466      * device. The app behind the widget is not in a funky “Restoring, but never actually restored”
467      * state. The widget has a valid size. The widget is in the workspace or the hotseat. If any of
468      * the above are found to be true, the database entry is deleted, and the widget is not shown on
469      * the user’s home screen. When a widget is verified, it is marked as restored, meaning that the
470      * widget is viable to show on the home screen.
471      *
472      * Common properties are applied to the Widget’s Info object, and other information as well
473      * depending on the type of widget. Custom widgets are treated differently than non-custom
474      * widgets, installing / restoring widgets are treated differently, etc.
475      */
processWidgetnull476     private fun processWidget() {
477         val component = ComponentName.unflattenFromString(c.appWidgetProvider)!!
478         val appWidgetInfo = LauncherAppWidgetInfo(c.appWidgetId, component)
479         c.applyCommonProperties(appWidgetInfo)
480         appWidgetInfo.spanX = c.spanX
481         appWidgetInfo.spanY = c.spanY
482         appWidgetInfo.options = c.options
483         appWidgetInfo.user = c.user
484         appWidgetInfo.sourceContainer = c.appWidgetSource
485         appWidgetInfo.restoreStatus = c.restoreFlag
486         if (appWidgetInfo.spanX <= 0 || appWidgetInfo.spanY <= 0) {
487             c.markDeleted(
488                 "processWidget: Widget has invalid size: ${appWidgetInfo.spanX}x${appWidgetInfo.spanY}" +
489                     ", id=${c.id}," +
490                     ", appWidgetId=${c.appWidgetId}," +
491                     ", component=${component}",
492                 RestoreError.INVALID_WIDGET_SIZE,
493             )
494             return
495         }
496         if (!c.isOnWorkspaceOrHotseat) {
497             c.markDeleted(
498                 "processWidget: invalid Widget container != CONTAINER_DESKTOP nor CONTAINER_HOTSEAT." +
499                     " id=${c.id}," +
500                     ", appWidgetId=${c.appWidgetId}," +
501                     ", component=${component}," +
502                     ", container=${c.container}",
503                 RestoreError.INVALID_WIDGET_CONTAINER,
504             )
505             return
506         }
507         if (appWidgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_DIRECT_CONFIG)) {
508             appWidgetInfo.bindOptions = c.parseIntent()
509         }
510         val inflationResult = widgetInflater.inflateAppWidget(appWidgetInfo)
511         var shouldUpdate = inflationResult.isUpdate
512         val lapi = inflationResult.widgetInfo
513         FileLog.d(
514             TAG,
515             "processWidget: id=${c.id}" +
516                 ", appWidgetId=${c.appWidgetId}" +
517                 ", inflationResult=$inflationResult",
518         )
519         when (inflationResult.type) {
520             WidgetInflater.TYPE_DELETE -> {
521                 c.markDeleted(inflationResult.reason, inflationResult.restoreErrorType)
522                 return
523             }
524             WidgetInflater.TYPE_PENDING -> {
525                 tempPackageKey.update(component.packageName, c.user)
526                 val si = installingPkgs[tempPackageKey]
527                 val isArchived =
528                     ApplicationInfoWrapper(context, component.packageName, c.user).isArchived()
529                 if (
530                     !c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) &&
531                         !isSafeMode &&
532                         (si == null) &&
533                         (lapi == null) &&
534                         !isArchived
535                 ) {
536                     // Restore never started
537                     c.markDeleted(
538                         "processWidget: Unrestored Pending widget removed:" +
539                             " id=${c.id}" +
540                             ", appWidgetId=${c.appWidgetId}" +
541                             ", component=${component}" +
542                             ", restoreFlag:=${c.restoreFlag}",
543                         RestoreError.UNRESTORED_PENDING_WIDGET,
544                     )
545                     return
546                 } else if (
547                     !c.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_RESTORE_STARTED) && si != null
548                 ) {
549                     shouldUpdate = true
550                     appWidgetInfo.restoreStatus =
551                         appWidgetInfo.restoreStatus or LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
552                 }
553                 appWidgetInfo.installProgress =
554                     if (si == null) 0 else (si.getProgress() * 100).toInt()
555                 appWidgetInfo.pendingItemInfo =
556                     WidgetsModel.newPendingItemInfo(
557                         context,
558                         appWidgetInfo.providerName,
559                         appWidgetInfo.user,
560                     )
561                 val iconLookupFlag =
562                     if (isArchived && Flags.restoreArchivedAppIconsFromDb()) {
563                         DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()
564                     } else {
565                         DEFAULT_LOOKUP_FLAG
566                     }
567                 iconCache.getTitleAndIconForApp(appWidgetInfo.pendingItemInfo, iconLookupFlag)
568             }
569             WidgetInflater.TYPE_REAL ->
570                 WidgetSizes.updateWidgetSizeRangesAsync(
571                     appWidgetInfo.appWidgetId,
572                     lapi,
573                     context,
574                     appWidgetInfo.spanX,
575                     appWidgetInfo.spanY,
576                 )
577         }
578 
579         if (shouldUpdate) {
580             c.updater()
581                 .put(Favorites.APPWIDGET_PROVIDER, component.flattenToString())
582                 .put(Favorites.APPWIDGET_ID, appWidgetInfo.appWidgetId)
583                 .put(Favorites.RESTORED, appWidgetInfo.restoreStatus)
584                 .commit()
585         }
586         if (lapi != null) {
587             widgetProvidersMap[ComponentKey(lapi.provider, lapi.user)] = inflationResult.widgetInfo
588             if (appWidgetInfo.spanX < lapi.minSpanX || appWidgetInfo.spanY < lapi.minSpanY) {
589                 FileLog.d(
590                     TAG,
591                     " processWidget: Widget ${lapi.component} minSizes not met: span=${appWidgetInfo.spanX}x${appWidgetInfo.spanY} minSpan=${lapi.minSpanX}x${lapi.minSpanY}," +
592                         " id: ${c.id}," +
593                         " appWidgetId: ${c.appWidgetId}," +
594                         " component=${component}",
595                 )
596                 logWidgetInfo(idp, lapi)
597             }
598         }
599         c.checkAndAddItem(appWidgetInfo, bgDataModel, memoryLogger)
600     }
601 
602     companion object {
603         private const val TAG = "WorkspaceItemProcessor"
604 
logWidgetInfonull605         private fun logWidgetInfo(
606             idp: InvariantDeviceProfile,
607             widgetProviderInfo: LauncherAppWidgetProviderInfo,
608         ) {
609             val cellSize = Point()
610             for (deviceProfile in idp.supportedProfiles) {
611                 deviceProfile.getCellSize(cellSize)
612                 FileLog.d(
613                     TAG,
614                     "DeviceProfile available width: ${deviceProfile.availableWidthPx}," +
615                         " available height: ${deviceProfile.availableHeightPx}," +
616                         " cellLayoutBorderSpacePx Horizontal: ${deviceProfile.cellLayoutBorderSpacePx.x}," +
617                         " cellLayoutBorderSpacePx Vertical: ${deviceProfile.cellLayoutBorderSpacePx.y}," +
618                         " cellSize: $cellSize",
619                 )
620             }
621             val widgetDimension = StringBuilder()
622             widgetDimension
623                 .append("Widget dimensions:\n")
624                 .append("minResizeWidth: ")
625                 .append(widgetProviderInfo.minResizeWidth)
626                 .append("\n")
627                 .append("minResizeHeight: ")
628                 .append(widgetProviderInfo.minResizeHeight)
629                 .append("\n")
630                 .append("defaultWidth: ")
631                 .append(widgetProviderInfo.minWidth)
632                 .append("\n")
633                 .append("defaultHeight: ")
634                 .append(widgetProviderInfo.minHeight)
635                 .append("\n")
636             widgetDimension
637                 .append("targetCellWidth: ")
638                 .append(widgetProviderInfo.targetCellWidth)
639                 .append("\n")
640                 .append("targetCellHeight: ")
641                 .append(widgetProviderInfo.targetCellHeight)
642                 .append("\n")
643                 .append("maxResizeWidth: ")
644                 .append(widgetProviderInfo.maxResizeWidth)
645                 .append("\n")
646                 .append("maxResizeHeight: ")
647                 .append(widgetProviderInfo.maxResizeHeight)
648                 .append("\n")
649             FileLog.d(TAG, widgetDimension.toString())
650         }
651     }
652 }
653