1 /* 2 * Copyright (C) 2008 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.icons; 18 19 import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT; 20 import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; 21 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 22 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 23 import static com.android.launcher3.util.LooperExecutor.CALLER_ICON_CACHE; 24 import static com.android.launcher3.widget.WidgetSections.NO_CATEGORY; 25 26 import static java.util.stream.Collectors.groupingBy; 27 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.pm.ApplicationInfo; 32 import android.content.pm.LauncherActivityInfo; 33 import android.content.pm.LauncherApps; 34 import android.content.pm.PackageInstaller; 35 import android.content.pm.ShortcutInfo; 36 import android.database.Cursor; 37 import android.database.sqlite.SQLiteException; 38 import android.os.Looper; 39 import android.os.Trace; 40 import android.os.UserHandle; 41 import android.text.TextUtils; 42 import android.util.Log; 43 import android.util.SparseArray; 44 45 import androidx.annotation.AnyThread; 46 import androidx.annotation.NonNull; 47 import androidx.annotation.Nullable; 48 import androidx.annotation.VisibleForTesting; 49 import androidx.core.util.Pair; 50 51 import com.android.launcher3.Flags; 52 import com.android.launcher3.InvariantDeviceProfile; 53 import com.android.launcher3.Utilities; 54 import com.android.launcher3.dagger.ApplicationContext; 55 import com.android.launcher3.dagger.LauncherAppSingleton; 56 import com.android.launcher3.icons.cache.BaseIconCache; 57 import com.android.launcher3.icons.cache.CacheLookupFlag; 58 import com.android.launcher3.icons.cache.CachedObject; 59 import com.android.launcher3.icons.cache.CachedObjectCachingLogic; 60 import com.android.launcher3.icons.cache.LauncherActivityCachingLogic; 61 import com.android.launcher3.logging.FileLog; 62 import com.android.launcher3.model.data.AppInfo; 63 import com.android.launcher3.model.data.IconRequestInfo; 64 import com.android.launcher3.model.data.ItemInfoWithIcon; 65 import com.android.launcher3.model.data.PackageItemInfo; 66 import com.android.launcher3.model.data.WorkspaceItemInfo; 67 import com.android.launcher3.pm.InstallSessionHelper; 68 import com.android.launcher3.pm.UserCache; 69 import com.android.launcher3.util.CancellableTask; 70 import com.android.launcher3.util.ComponentKey; 71 import com.android.launcher3.util.DaggerSingletonTracker; 72 import com.android.launcher3.util.InstantAppResolver; 73 import com.android.launcher3.util.PackageUserKey; 74 import com.android.launcher3.widget.WidgetSections; 75 import com.android.launcher3.widget.WidgetSections.WidgetSection; 76 77 import java.util.Collections; 78 import java.util.List; 79 import java.util.Map; 80 import java.util.Objects; 81 import java.util.function.Predicate; 82 import java.util.function.Supplier; 83 import java.util.stream.Stream; 84 85 import javax.inject.Inject; 86 import javax.inject.Named; 87 88 /** 89 * Cache of application icons. Icons can be made from any thread. 90 */ 91 @LauncherAppSingleton 92 public class IconCache extends BaseIconCache { 93 94 // Shortcut extra which can point to a packageName and can be used to indicate an alternate 95 // badge info. Launcher only reads this if the shortcut comes from a system app. 96 public static final String EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE = 97 "extra_shortcut_badge_override_package"; 98 99 private static final String TAG = "Launcher.IconCache"; 100 101 private final Predicate<ItemInfoWithIcon> mIsUsingFallbackOrNonDefaultIconCheck = w -> 102 w.bitmap != null && (w.bitmap.isNullOrLowRes() || !isDefaultIcon(w.bitmap, w.user)); 103 104 private final LauncherApps mLauncherApps; 105 private final UserCache mUserManager; 106 private final InstallSessionHelper mInstallSessionHelper; 107 private final InstantAppResolver mInstantAppResolver; 108 private final CancellableTask mCancelledTask; 109 private final LauncherIcons.IconPool mIconPool; 110 111 private final SparseArray<BitmapInfo> mWidgetCategoryBitmapInfos; 112 113 private int mPendingIconRequestCount = 0; 114 115 @Inject IconCache( @pplicationContext Context context, InvariantDeviceProfile idp, @Nullable @Named("ICONS_DB") String dbFileName, UserCache userCache, LauncherIconProvider iconProvider, InstallSessionHelper installSessionHelper, LauncherIcons.IconPool iconPool, DaggerSingletonTracker lifecycle)116 public IconCache( 117 @ApplicationContext Context context, 118 InvariantDeviceProfile idp, 119 @Nullable @Named("ICONS_DB") String dbFileName, 120 UserCache userCache, 121 LauncherIconProvider iconProvider, 122 InstallSessionHelper installSessionHelper, 123 LauncherIcons.IconPool iconPool, 124 DaggerSingletonTracker lifecycle) { 125 super(context, dbFileName, MODEL_EXECUTOR.getLooper(), 126 idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */, iconProvider); 127 mLauncherApps = context.getSystemService(LauncherApps.class); 128 mUserManager = userCache; 129 mInstallSessionHelper = installSessionHelper; 130 mIconPool = iconPool; 131 132 mInstantAppResolver = InstantAppResolver.newInstance(context); 133 mWidgetCategoryBitmapInfos = new SparseArray<>(); 134 135 mCancelledTask = new CancellableTask(() -> null, MAIN_EXECUTOR, c -> { }); 136 mCancelledTask.cancel(); 137 138 lifecycle.addCloseable(this::close); 139 } 140 141 @Override getSerialNumberForUser(@onNull UserHandle user)142 public long getSerialNumberForUser(@NonNull UserHandle user) { 143 return mUserManager.getSerialNumberForUser(user); 144 } 145 146 @Override isInstantApp(@onNull ApplicationInfo info)147 protected boolean isInstantApp(@NonNull ApplicationInfo info) { 148 return mInstantAppResolver.isInstantApp(info); 149 } 150 151 @NonNull 152 @Override getIconFactory()153 public BaseIconFactory getIconFactory() { 154 return mIconPool.obtain(); 155 } 156 157 /** 158 * Updates the entries related to the given package in memory and persistent DB. 159 */ updateIconsForPkg(@onNull final String packageName, @NonNull final UserHandle user)160 public synchronized void updateIconsForPkg(@NonNull final String packageName, 161 @NonNull final UserHandle user) { 162 List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(packageName, user); 163 if (Flags.restoreArchivedAppIconsFromDb() 164 && apps.stream().anyMatch(app -> app.getApplicationInfo().isArchived)) { 165 // When archiving app icon, don't delete old icon so it can be re-used. 166 return; 167 } 168 removeIconsForPkg(packageName, user); 169 long userSerial = mUserManager.getSerialNumberForUser(user); 170 for (LauncherActivityInfo app : apps) { 171 addIconToDBAndMemCache(app, LauncherActivityCachingLogic.INSTANCE, userSerial); 172 } 173 } 174 175 /** 176 * Closes the cache DB. This will clear any in-memory cache. 177 */ close()178 public void close() { 179 // This will clear all pending updates 180 getUpdateHandler(); 181 182 iconDb.close(); 183 } 184 185 /** 186 * Fetches high-res icon for the provided ItemInfo and updates the caller when done. 187 * 188 * @return a request ID that can be used to cancel the request. 189 */ 190 @AnyThread updateIconInBackground(final ItemInfoUpdateReceiver caller, final ItemInfoWithIcon info)191 public CancellableTask updateIconInBackground(final ItemInfoUpdateReceiver caller, 192 final ItemInfoWithIcon info) { 193 Supplier<ItemInfoWithIcon> task; 194 if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) { 195 task = () -> { 196 getTitleAndIcon(info, DEFAULT_LOOKUP_FLAG); 197 return info; 198 }; 199 } else if (info instanceof PackageItemInfo pii) { 200 task = () -> { 201 getTitleAndIconForApp(pii, DEFAULT_LOOKUP_FLAG); 202 return pii; 203 }; 204 } else { 205 Log.i(TAG, "Icon update not supported for " 206 + info == null ? "null" : info.getClass().getName()); 207 return mCancelledTask; 208 } 209 210 Runnable endRunnable; 211 if (Looper.myLooper() == Looper.getMainLooper()) { 212 if (mPendingIconRequestCount <= 0) { 213 MODEL_EXECUTOR.elevatePriority(CALLER_ICON_CACHE); 214 } 215 mPendingIconRequestCount++; 216 endRunnable = this::onIconRequestEnd; 217 } else { 218 endRunnable = () -> { }; 219 } 220 221 CancellableTask<ItemInfoWithIcon> request = new CancellableTask<>( 222 task, MAIN_EXECUTOR, caller::reapplyItemInfo, endRunnable); 223 Utilities.postAsyncCallback(workerHandler, request); 224 return request; 225 } 226 onIconRequestEnd()227 private void onIconRequestEnd() { 228 mPendingIconRequestCount--; 229 if (mPendingIconRequestCount <= 0) { 230 MODEL_EXECUTOR.restorePriority(CALLER_ICON_CACHE); 231 } 232 } 233 234 /** 235 * Updates {@param application} only if a valid entry is found. 236 */ updateTitleAndIcon(AppInfo application)237 public synchronized void updateTitleAndIcon(AppInfo application) { 238 CacheEntry entry = cacheLocked(application.componentName, 239 application.user, () -> null, LauncherActivityCachingLogic.INSTANCE, 240 application.getMatchingLookupFlag()); 241 if (entry.bitmap != null || !isDefaultIcon(entry.bitmap, application.user)) { 242 applyCacheEntry(entry, application); 243 } 244 } 245 246 /** 247 * Fill in {@code info} with the icon and label for {@code activityInfo} 248 */ 249 @SuppressWarnings("NewApi") getTitleAndIcon(ItemInfoWithIcon info, LauncherActivityInfo activityInfo, @NonNull CacheLookupFlag lookupFlag)250 public synchronized void getTitleAndIcon(ItemInfoWithIcon info, 251 LauncherActivityInfo activityInfo, @NonNull CacheLookupFlag lookupFlag) { 252 boolean isAppArchived = Flags.enableSupportForArchiving() && activityInfo != null 253 && activityInfo.getActivityInfo().isArchived; 254 // If we already have activity info, no need to use package icon 255 getTitleAndIcon(info, () -> activityInfo, lookupFlag.withUsePackageIcon(isAppArchived)); 256 } 257 258 /** 259 * Fill in {@code info} with the icon for {@code si} 260 */ getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si)261 public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) { 262 getShortcutIcon(info, new CacheableShortcutInfo(si, context)); 263 } 264 265 /** 266 * Fill in {@code info} with the icon for {@code si} 267 */ getShortcutIcon(ItemInfoWithIcon info, CacheableShortcutInfo si)268 public void getShortcutIcon(ItemInfoWithIcon info, CacheableShortcutInfo si) { 269 getShortcutIcon(info, si, mIsUsingFallbackOrNonDefaultIconCheck); 270 } 271 272 /** 273 * Fill in {@code info} with the icon and label for {@code si}. If the icon is not 274 * available, and fallback check returns true, it keeps the old icon. 275 * Shortcut entries are not kept in memory since they are not frequently used 276 */ getShortcutIcon(T info, CacheableShortcutInfo si, @NonNull Predicate<T> fallbackIconCheck)277 public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, CacheableShortcutInfo si, 278 @NonNull Predicate<T> fallbackIconCheck) { 279 UserHandle user = CacheableShortcutCachingLogic.INSTANCE.getUser(si); 280 BitmapInfo bitmapInfo = cacheLocked( 281 CacheableShortcutCachingLogic.INSTANCE.getComponent(si), 282 user, 283 () -> si, 284 CacheableShortcutCachingLogic.INSTANCE, 285 DEFAULT_LOOKUP_FLAG.withSkipAddToMemCache()).bitmap; 286 if (bitmapInfo.isNullOrLowRes()) { 287 bitmapInfo = getDefaultIcon(user); 288 } 289 290 if (isDefaultIcon(bitmapInfo, user) && fallbackIconCheck.test(info)) { 291 return; 292 } 293 info.bitmap = bitmapInfo.withBadgeInfo(getShortcutInfoBadge(si.getShortcutInfo())); 294 } 295 296 /** 297 * Returns the badging info for the shortcut 298 */ getShortcutInfoBadge(ShortcutInfo shortcutInfo)299 public BitmapInfo getShortcutInfoBadge(ShortcutInfo shortcutInfo) { 300 return getShortcutInfoBadgeItem(shortcutInfo).bitmap; 301 } 302 303 @VisibleForTesting getShortcutInfoBadgeItem(ShortcutInfo shortcutInfo)304 protected ItemInfoWithIcon getShortcutInfoBadgeItem(ShortcutInfo shortcutInfo) { 305 // Check for badge override first. 306 String pkg = shortcutInfo.getPackage(); 307 String override = shortcutInfo.getExtras() == null ? null 308 : shortcutInfo.getExtras().getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE); 309 if (!TextUtils.isEmpty(override) 310 && mInstallSessionHelper.isTrustedPackage(pkg, shortcutInfo.getUserHandle())) { 311 pkg = override; 312 } else { 313 // Try component based badge before trying the normal package badge 314 ComponentName cn = shortcutInfo.getActivity(); 315 if (cn != null) { 316 // Get the app info for the source activity. 317 AppInfo appInfo = new AppInfo(); 318 appInfo.user = shortcutInfo.getUserHandle(); 319 appInfo.componentName = cn; 320 appInfo.intent = new Intent(Intent.ACTION_MAIN) 321 .addCategory(Intent.CATEGORY_LAUNCHER) 322 .setComponent(cn); 323 getTitleAndIcon(appInfo, DEFAULT_LOOKUP_FLAG); 324 return appInfo; 325 } 326 } 327 PackageItemInfo pkgInfo = new PackageItemInfo(pkg, shortcutInfo.getUserHandle()); 328 getTitleAndIconForApp(pkgInfo, DEFAULT_LOOKUP_FLAG); 329 return pkgInfo; 330 } 331 332 /** 333 * Fill in {@param info} with the icon and label. If the 334 * corresponding activity is not found, it reverts to the package icon. 335 */ getTitleAndIcon( @onNull ItemInfoWithIcon info, @NonNull CacheLookupFlag lookupFlag)336 public synchronized void getTitleAndIcon( 337 @NonNull ItemInfoWithIcon info, 338 @NonNull CacheLookupFlag lookupFlag) { 339 // null info means not installed, but if we have a component from the intent then 340 // we should still look in the cache for restored app icons. 341 if (info.getTargetComponent() == null) { 342 info.bitmap = getDefaultIcon(info.user); 343 info.title = ""; 344 info.contentDescription = ""; 345 } else { 346 Intent intent = info.getIntent(); 347 getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user), 348 lookupFlag.withUsePackageIcon()); 349 } 350 } 351 352 /** 353 * Loads and returns the icon for the provided object without adding it to memCache 354 */ getTitleNoCache(CachedObject info)355 public synchronized String getTitleNoCache(CachedObject info) { 356 CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info, 357 CachedObjectCachingLogic.INSTANCE, 358 DEFAULT_LOOKUP_FLAG.withUseLowRes().withSkipAddToMemCache()); 359 return Utilities.trim(entry.title); 360 } 361 362 /** 363 * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info} 364 */ getTitleAndIcon( @onNull ItemInfoWithIcon infoInOut, @NonNull Supplier<LauncherActivityInfo> activityInfoProvider, @NonNull CacheLookupFlag lookupFlag)365 public synchronized void getTitleAndIcon( 366 @NonNull ItemInfoWithIcon infoInOut, 367 @NonNull Supplier<LauncherActivityInfo> activityInfoProvider, 368 @NonNull CacheLookupFlag lookupFlag) { 369 CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user, 370 activityInfoProvider, LauncherActivityCachingLogic.INSTANCE, lookupFlag); 371 applyCacheEntry(entry, infoInOut); 372 } 373 374 /** 375 * Creates an sql cursor for a query of a set of ItemInfoWithIcon icons and titles. 376 * 377 * @param iconRequestInfos List of IconRequestInfos representing titles and icons to query. 378 * @param user UserHandle all the given iconRequestInfos share 379 * @param lookupFlag what flags to use when loading the icon. 380 */ createBulkQueryCursor( List<IconRequestInfo<T>> iconRequestInfos, UserHandle user, CacheLookupFlag lookupFlag)381 private <T extends ItemInfoWithIcon> Cursor createBulkQueryCursor( 382 List<IconRequestInfo<T>> iconRequestInfos, UserHandle user, CacheLookupFlag lookupFlag) 383 throws SQLiteException { 384 String[] queryParams = Stream.concat( 385 iconRequestInfos.stream() 386 .map(r -> r.itemInfo.getTargetComponent()) 387 .filter(Objects::nonNull) 388 .distinct() 389 .map(ComponentName::flattenToString), 390 Stream.of(Long.toString(getSerialNumberForUser(user)))).toArray(String[]::new); 391 String componentNameQuery = TextUtils.join( 392 ",", Collections.nCopies(queryParams.length - 1, "?")); 393 394 return iconDb.query( 395 toLookupColumns(lookupFlag), 396 COLUMN_COMPONENT 397 + " IN ( " + componentNameQuery + " )" 398 + " AND " + COLUMN_USER + " = ?", 399 queryParams); 400 } 401 402 /** 403 * Load and fill icons requested in iconRequestInfos using a single bulk sql query. 404 */ getTitlesAndIconsInBulk( List<IconRequestInfo<T>> iconRequestInfos)405 public synchronized <T extends ItemInfoWithIcon> void getTitlesAndIconsInBulk( 406 List<IconRequestInfo<T>> iconRequestInfos) { 407 Map<Pair<UserHandle, Boolean>, List<IconRequestInfo<T>>> iconLoadSubsectionsMap = 408 iconRequestInfos.stream() 409 .filter(iconRequest -> { 410 if (iconRequest.itemInfo.getTargetComponent() == null) { 411 Log.i(TAG, 412 "Skipping Item info with null component name: " 413 + iconRequest.itemInfo); 414 iconRequest.itemInfo.bitmap = getDefaultIcon( 415 iconRequest.itemInfo.user); 416 return false; 417 } 418 return true; 419 }) 420 .collect(groupingBy(iconRequest -> 421 Pair.create(iconRequest.itemInfo.user, iconRequest.useLowResIcon))); 422 423 Trace.beginSection("loadIconsInBulk"); 424 iconLoadSubsectionsMap.forEach((sectionKey, filteredList) -> { 425 Map<ComponentName, List<IconRequestInfo<T>>> duplicateIconRequestsMap = 426 filteredList.stream() 427 .filter(iconRequest -> { 428 // Filter out icons that should not share the same bitmap and title 429 if (iconRequest.itemInfo.itemType == ITEM_TYPE_DEEP_SHORTCUT) { 430 Log.e(TAG, 431 "Skipping Item info for deep shortcut: " 432 + iconRequest.itemInfo, 433 new IllegalStateException()); 434 return false; 435 } 436 return true; 437 }) 438 .collect(groupingBy(iconRequest -> 439 iconRequest.itemInfo.getTargetComponent())); 440 441 Trace.beginSection("loadIconSubsectionInBulk"); 442 loadIconSubsection(sectionKey, filteredList, duplicateIconRequestsMap); 443 Trace.endSection(); 444 }); 445 Trace.endSection(); 446 } 447 loadIconSubsection( Pair<UserHandle, Boolean> sectionKey, List<IconRequestInfo<T>> filteredList, Map<ComponentName, List<IconRequestInfo<T>>> duplicateIconRequestsMap)448 private <T extends ItemInfoWithIcon> void loadIconSubsection( 449 Pair<UserHandle, Boolean> sectionKey, 450 List<IconRequestInfo<T>> filteredList, 451 Map<ComponentName, List<IconRequestInfo<T>>> duplicateIconRequestsMap) { 452 Trace.beginSection("loadIconSubsectionWithDatabase"); 453 CacheLookupFlag lookupFlag = DEFAULT_LOOKUP_FLAG.withUseLowRes(sectionKey.second); 454 try (Cursor c = createBulkQueryCursor( 455 filteredList, 456 /* user = */ sectionKey.first, 457 lookupFlag)) { 458 // Database title and icon loading 459 int componentNameColumnIndex = c.getColumnIndexOrThrow(COLUMN_COMPONENT); 460 while (c.moveToNext()) { 461 ComponentName cn = ComponentName.unflattenFromString( 462 c.getString(componentNameColumnIndex)); 463 List<IconRequestInfo<T>> duplicateIconRequests = 464 duplicateIconRequestsMap.get(cn); 465 466 if (cn != null) { 467 if (duplicateIconRequests != null) { 468 CacheEntry entry = cacheLocked( 469 cn, 470 /* user = */ sectionKey.first, 471 () -> duplicateIconRequests.get(0).launcherActivityInfo, 472 LauncherActivityCachingLogic.INSTANCE, 473 lookupFlag, 474 c); 475 476 for (IconRequestInfo<T> iconRequest : duplicateIconRequests) { 477 applyCacheEntry(entry, iconRequest.itemInfo); 478 } 479 } else { 480 Log.e(TAG, "Found entry in icon database but no main activity " 481 + "entry for cn: " + cn); 482 } 483 } 484 } 485 } catch (SQLiteException e) { 486 Log.d(TAG, "Error reading icon cache", e); 487 } finally { 488 Trace.endSection(); 489 } 490 491 Trace.beginSection("loadIconSubsectionWithFallback"); 492 // Fallback title and icon loading 493 for (ComponentName cn : duplicateIconRequestsMap.keySet()) { 494 IconRequestInfo<T> iconRequestInfo = duplicateIconRequestsMap.get(cn).get(0); 495 ItemInfoWithIcon itemInfo = iconRequestInfo.itemInfo; 496 BitmapInfo icon = itemInfo.bitmap; 497 boolean loadFallbackTitle = TextUtils.isEmpty(itemInfo.title); 498 boolean loadFallbackIcon = icon == null 499 || isDefaultIcon(icon, itemInfo.user) 500 || icon == BitmapInfo.LOW_RES_INFO; 501 502 if (loadFallbackTitle || loadFallbackIcon) { 503 Log.i(TAG, 504 "Database bulk icon loading failed, using fallback bulk icon loading " 505 + "for: " + cn); 506 CacheEntry entry = new CacheEntry(); 507 LauncherActivityInfo lai = iconRequestInfo.launcherActivityInfo; 508 509 // Fill fields that are not updated below so they are not subsequently 510 // deleted. 511 entry.title = itemInfo.title; 512 if (icon != null) { 513 entry.bitmap = icon; 514 } 515 entry.contentDescription = itemInfo.contentDescription; 516 517 if (loadFallbackIcon) { 518 loadFallbackIcon( 519 lai, 520 entry, 521 LauncherActivityCachingLogic.INSTANCE, 522 /* usePackageIcon= */ false, 523 /* usePackageTitle= */ loadFallbackTitle, 524 cn, 525 sectionKey.first); 526 } 527 if (loadFallbackTitle && TextUtils.isEmpty(entry.title) && lai != null) { 528 loadFallbackTitle( 529 lai, 530 entry, 531 LauncherActivityCachingLogic.INSTANCE, 532 sectionKey.first); 533 } 534 535 for (IconRequestInfo<T> iconRequest : duplicateIconRequestsMap.get(cn)) { 536 applyCacheEntry(entry, iconRequest.itemInfo); 537 } 538 } 539 } 540 Trace.endSection(); 541 } 542 543 /** 544 * Fill in {@param infoInOut} with the corresponding icon and label. 545 */ getTitleAndIconForApp( @onNull final PackageItemInfo infoInOut, @NonNull CacheLookupFlag lookupFlag)546 public synchronized void getTitleAndIconForApp( 547 @NonNull final PackageItemInfo infoInOut, 548 @NonNull CacheLookupFlag lookupFlag) { 549 CacheEntry entry = getEntryForPackageLocked( 550 infoInOut.packageName, infoInOut.user, lookupFlag); 551 applyCacheEntry(entry, infoInOut); 552 if (infoInOut.widgetCategory == NO_CATEGORY) { 553 return; 554 } 555 556 WidgetSection widgetSection = WidgetSections.getWidgetSections(context) 557 .get(infoInOut.widgetCategory); 558 infoInOut.title = context.getString(widgetSection.mSectionTitle); 559 infoInOut.contentDescription = getUserBadgedLabel(infoInOut.title, infoInOut.user); 560 final BitmapInfo cachedBitmap = mWidgetCategoryBitmapInfos.get(infoInOut.widgetCategory); 561 if (cachedBitmap != null) { 562 infoInOut.bitmap = getBadgedIcon(cachedBitmap, infoInOut.user); 563 return; 564 } 565 566 try (LauncherIcons li = mIconPool.obtain()) { 567 final BitmapInfo tempBitmap = li.createBadgedIconBitmap( 568 context.getDrawable(widgetSection.mSectionDrawable), 569 new BaseIconFactory.IconOptions()); 570 mWidgetCategoryBitmapInfos.put(infoInOut.widgetCategory, tempBitmap); 571 infoInOut.bitmap = getBadgedIcon(tempBitmap, infoInOut.user); 572 } catch (Exception e) { 573 Log.e(TAG, "Error initializing bitmap for icons with widget category", e); 574 } 575 576 } 577 getBadgedIcon(@ullable final BitmapInfo bitmap, @NonNull final UserHandle user)578 private synchronized BitmapInfo getBadgedIcon(@Nullable final BitmapInfo bitmap, 579 @NonNull final UserHandle user) { 580 if (bitmap == null) { 581 return getDefaultIcon(user); 582 } 583 return bitmap.withFlags(getUserFlagOpLocked(user)); 584 } 585 applyCacheEntry(@onNull final CacheEntry entry, @NonNull final ItemInfoWithIcon info)586 protected void applyCacheEntry(@NonNull final CacheEntry entry, 587 @NonNull final ItemInfoWithIcon info) { 588 info.title = Utilities.trim(entry.title); 589 info.contentDescription = entry.contentDescription; 590 info.bitmap = entry.bitmap; 591 // Clear any previously set appTitle, if the packageOverride is no longer valid 592 info.appTitle = null; 593 if (entry.bitmap == null) { 594 // TODO: entry.bitmap can never be null, so this should not happen at all. 595 Log.wtf(TAG, "Cannot find bitmap from the cache, default icon was loaded."); 596 info.bitmap = getDefaultIcon(info.user); 597 } 598 599 // apply package override 600 if (!Flags.enableSupportForArchiving() || !info.isArchived()) { 601 return; 602 } 603 String targetPackage = info.getTargetPackage(); 604 if (targetPackage == null) { 605 return; 606 } 607 CacheEntry packageEntry = getInMemoryPackageEntryLocked(targetPackage, info.user); 608 if (packageEntry == null || packageEntry.bitmap.isLowRes()) { 609 return; 610 } 611 info.appTitle = Utilities.trim(info.title); 612 info.title = Utilities.trim(packageEntry.title); 613 info.contentDescription = packageEntry.contentDescription; 614 info.bitmap = packageEntry.bitmap; 615 } 616 updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info)617 public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) { 618 cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), 619 info.getAppLabel()); 620 } 621 622 @VisibleForTesting isItemInDb(ComponentKey cacheKey)623 synchronized boolean isItemInDb(ComponentKey cacheKey) { 624 return getEntryFromDBLocked(cacheKey, new CacheEntry(), DEFAULT_LOOKUP_FLAG, 625 LauncherActivityCachingLogic.INSTANCE); 626 } 627 628 /** 629 * Interface for receiving itemInfo with high-res icon. 630 */ 631 public interface ItemInfoUpdateReceiver { 632 reapplyItemInfo(ItemInfoWithIcon info)633 void reapplyItemInfo(ItemInfoWithIcon info); 634 } 635 636 /** Log persistently to FileLog.d for debugging. */ 637 @Override logPersistently(@onNull String message, @Nullable Exception e)638 protected void logPersistently(@NonNull String message, @Nullable Exception e) { 639 FileLog.d(BaseIconCache.TAG, message, e); 640 } 641 } 642