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