• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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