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