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