Home | History | Annotate | Download | only in launcher3
      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;
     18 
     19 import android.content.ComponentName;
     20 import android.content.ContentValues;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.pm.ActivityInfo;
     24 import android.content.pm.ApplicationInfo;
     25 import android.content.pm.LauncherActivityInfo;
     26 import android.content.pm.PackageInfo;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.PackageManager.NameNotFoundException;
     29 import android.content.res.Resources;
     30 import android.database.Cursor;
     31 import android.database.sqlite.SQLiteDatabase;
     32 import android.database.sqlite.SQLiteException;
     33 import android.graphics.Bitmap;
     34 import android.graphics.BitmapFactory;
     35 import android.graphics.drawable.Drawable;
     36 import android.os.Build;
     37 import android.os.Handler;
     38 import android.os.Process;
     39 import android.os.SystemClock;
     40 import android.os.UserHandle;
     41 import android.support.annotation.NonNull;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 import com.android.launcher3.compat.LauncherAppsCompat;
     45 import com.android.launcher3.compat.UserManagerCompat;
     46 import com.android.launcher3.config.FeatureFlags;
     47 import com.android.launcher3.graphics.LauncherIcons;
     48 import com.android.launcher3.model.PackageItemInfo;
     49 import com.android.launcher3.util.ComponentKey;
     50 import com.android.launcher3.util.InstantAppResolver;
     51 import com.android.launcher3.util.Preconditions;
     52 import com.android.launcher3.util.Provider;
     53 import com.android.launcher3.util.SQLiteCacheHelper;
     54 import com.android.launcher3.util.Thunk;
     55 import java.util.Collections;
     56 import java.util.HashMap;
     57 import java.util.HashSet;
     58 import java.util.List;
     59 import java.util.Set;
     60 import java.util.Stack;
     61 
     62 /**
     63  * Cache of application icons.  Icons can be made from any thread.
     64  */
     65 public class IconCache {
     66 
     67     private static final String TAG = "Launcher.IconCache";
     68 
     69     private static final int INITIAL_ICON_CACHE_CAPACITY = 50;
     70 
     71     // Empty class name is used for storing package default entry.
     72     public static final String EMPTY_CLASS_NAME = ".";
     73 
     74     private static final boolean DEBUG = false;
     75     private static final boolean DEBUG_IGNORE_CACHE = false;
     76 
     77     private static final int LOW_RES_SCALE_FACTOR = 5;
     78 
     79     @Thunk static final Object ICON_UPDATE_TOKEN = new Object();
     80 
     81     public static class CacheEntry {
     82         public Bitmap icon;
     83         public CharSequence title = "";
     84         public CharSequence contentDescription = "";
     85         public boolean isLowResIcon;
     86     }
     87 
     88     private final HashMap<UserHandle, Bitmap> mDefaultIcons = new HashMap<>();
     89     @Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor();
     90 
     91     private final Context mContext;
     92     private final PackageManager mPackageManager;
     93     private final IconProvider mIconProvider;
     94     @Thunk final UserManagerCompat mUserManager;
     95     private final LauncherAppsCompat mLauncherApps;
     96     private final HashMap<ComponentKey, CacheEntry> mCache =
     97             new HashMap<>(INITIAL_ICON_CACHE_CAPACITY);
     98     private final InstantAppResolver mInstantAppResolver;
     99     private final int mIconDpi;
    100     @Thunk final IconDB mIconDb;
    101 
    102     @Thunk final Handler mWorkerHandler;
    103 
    104     private final BitmapFactory.Options mLowResOptions;
    105 
    106     public IconCache(Context context, InvariantDeviceProfile inv) {
    107         mContext = context;
    108         mPackageManager = context.getPackageManager();
    109         mUserManager = UserManagerCompat.getInstance(mContext);
    110         mLauncherApps = LauncherAppsCompat.getInstance(mContext);
    111         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
    112         mIconDpi = inv.fillResIconDpi;
    113         mIconDb = new IconDB(context, inv.iconBitmapSize);
    114 
    115         mIconProvider = Utilities.getOverrideObject(
    116                 IconProvider.class, context, R.string.icon_provider_class);
    117         mWorkerHandler = new Handler(LauncherModel.getWorkerLooper());
    118 
    119         mLowResOptions = new BitmapFactory.Options();
    120         // Always prefer RGB_565 config for low res. If the bitmap has transparency, it will
    121         // automatically be loaded as ALPHA_8888.
    122         mLowResOptions.inPreferredConfig = Bitmap.Config.RGB_565;
    123     }
    124 
    125     private Drawable getFullResDefaultActivityIcon() {
    126         return getFullResIcon(Resources.getSystem(), Utilities.ATLEAST_OREO ?
    127                 android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon);
    128     }
    129 
    130     private Drawable getFullResIcon(Resources resources, int iconId) {
    131         Drawable d;
    132         try {
    133             d = resources.getDrawableForDensity(iconId, mIconDpi);
    134         } catch (Resources.NotFoundException e) {
    135             d = null;
    136         }
    137 
    138         return (d != null) ? d : getFullResDefaultActivityIcon();
    139     }
    140 
    141     public Drawable getFullResIcon(String packageName, int iconId) {
    142         Resources resources;
    143         try {
    144             resources = mPackageManager.getResourcesForApplication(packageName);
    145         } catch (PackageManager.NameNotFoundException e) {
    146             resources = null;
    147         }
    148         if (resources != null) {
    149             if (iconId != 0) {
    150                 return getFullResIcon(resources, iconId);
    151             }
    152         }
    153         return getFullResDefaultActivityIcon();
    154     }
    155 
    156     public Drawable getFullResIcon(ActivityInfo info) {
    157         Resources resources;
    158         try {
    159             resources = mPackageManager.getResourcesForApplication(
    160                     info.applicationInfo);
    161         } catch (PackageManager.NameNotFoundException e) {
    162             resources = null;
    163         }
    164         if (resources != null) {
    165             int iconId = info.getIconResource();
    166             if (iconId != 0) {
    167                 return getFullResIcon(resources, iconId);
    168             }
    169         }
    170 
    171         return getFullResDefaultActivityIcon();
    172     }
    173 
    174     public Drawable getFullResIcon(LauncherActivityInfo info) {
    175         return getFullResIcon(info, true);
    176     }
    177 
    178     public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
    179         return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
    180     }
    181 
    182     protected Bitmap makeDefaultIcon(UserHandle user) {
    183         Drawable unbadged = getFullResDefaultActivityIcon();
    184         return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, Build.VERSION_CODES.O);
    185     }
    186 
    187     /**
    188      * Remove any records for the supplied ComponentName.
    189      */
    190     public synchronized void remove(ComponentName componentName, UserHandle user) {
    191         mCache.remove(new ComponentKey(componentName, user));
    192     }
    193 
    194     /**
    195      * Remove any records for the supplied package name from memory.
    196      */
    197     private void removeFromMemCacheLocked(String packageName, UserHandle user) {
    198         HashSet<ComponentKey> forDeletion = new HashSet<>();
    199         for (ComponentKey key: mCache.keySet()) {
    200             if (key.componentName.getPackageName().equals(packageName)
    201                     && key.user.equals(user)) {
    202                 forDeletion.add(key);
    203             }
    204         }
    205         for (ComponentKey condemned: forDeletion) {
    206             mCache.remove(condemned);
    207         }
    208     }
    209 
    210     /**
    211      * Updates the entries related to the given package in memory and persistent DB.
    212      */
    213     public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
    214         removeIconsForPkg(packageName, user);
    215         try {
    216             PackageInfo info = mPackageManager.getPackageInfo(packageName,
    217                     PackageManager.GET_UNINSTALLED_PACKAGES);
    218             long userSerial = mUserManager.getSerialNumberForUser(user);
    219             for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
    220                 addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/);
    221             }
    222         } catch (NameNotFoundException e) {
    223             Log.d(TAG, "Package not found", e);
    224         }
    225     }
    226 
    227     /**
    228      * Removes the entries related to the given package in memory and persistent DB.
    229      */
    230     public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
    231         removeFromMemCacheLocked(packageName, user);
    232         long userSerial = mUserManager.getSerialNumberForUser(user);
    233         mIconDb.delete(
    234                 IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?",
    235                 new String[]{packageName + "/%", Long.toString(userSerial)});
    236     }
    237 
    238     public void updateDbIcons(Set<String> ignorePackagesForMainUser) {
    239         // Remove all active icon update tasks.
    240         mWorkerHandler.removeCallbacksAndMessages(ICON_UPDATE_TOKEN);
    241 
    242         mIconProvider.updateSystemStateString();
    243         for (UserHandle user : mUserManager.getUserProfiles()) {
    244             // Query for the set of apps
    245             final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
    246             // Fail if we don't have any apps
    247             // TODO: Fix this. Only fail for the current user.
    248             if (apps == null || apps.isEmpty()) {
    249                 return;
    250             }
    251 
    252             // Update icon cache. This happens in segments and {@link #onPackageIconsUpdated}
    253             // is called by the icon cache when the job is complete.
    254             updateDBIcons(user, apps, Process.myUserHandle().equals(user)
    255                     ? ignorePackagesForMainUser : Collections.<String>emptySet());
    256         }
    257     }
    258 
    259     /**
    260      * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in
    261      * the DB and are updated.
    262      * @return The set of packages for which icons have updated.
    263      */
    264     private void updateDBIcons(UserHandle user, List<LauncherActivityInfo> apps,
    265             Set<String> ignorePackages) {
    266         long userSerial = mUserManager.getSerialNumberForUser(user);
    267         PackageManager pm = mContext.getPackageManager();
    268         HashMap<String, PackageInfo> pkgInfoMap = new HashMap<>();
    269         for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) {
    270             pkgInfoMap.put(info.packageName, info);
    271         }
    272 
    273         HashMap<ComponentName, LauncherActivityInfo> componentMap = new HashMap<>();
    274         for (LauncherActivityInfo app : apps) {
    275             componentMap.put(app.getComponentName(), app);
    276         }
    277 
    278         HashSet<Integer> itemsToRemove = new HashSet<>();
    279         Stack<LauncherActivityInfo> appsToUpdate = new Stack<>();
    280 
    281         Cursor c = null;
    282         try {
    283             c = mIconDb.query(
    284                     new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT,
    285                             IconDB.COLUMN_LAST_UPDATED, IconDB.COLUMN_VERSION,
    286                             IconDB.COLUMN_SYSTEM_STATE},
    287                     IconDB.COLUMN_USER + " = ? ",
    288                     new String[]{Long.toString(userSerial)});
    289 
    290             final int indexComponent = c.getColumnIndex(IconDB.COLUMN_COMPONENT);
    291             final int indexLastUpdate = c.getColumnIndex(IconDB.COLUMN_LAST_UPDATED);
    292             final int indexVersion = c.getColumnIndex(IconDB.COLUMN_VERSION);
    293             final int rowIndex = c.getColumnIndex(IconDB.COLUMN_ROWID);
    294             final int systemStateIndex = c.getColumnIndex(IconDB.COLUMN_SYSTEM_STATE);
    295 
    296             while (c.moveToNext()) {
    297                 String cn = c.getString(indexComponent);
    298                 ComponentName component = ComponentName.unflattenFromString(cn);
    299                 PackageInfo info = pkgInfoMap.get(component.getPackageName());
    300                 if (info == null) {
    301                     if (!ignorePackages.contains(component.getPackageName())) {
    302                         remove(component, user);
    303                         itemsToRemove.add(c.getInt(rowIndex));
    304                     }
    305                     continue;
    306                 }
    307                 if ((info.applicationInfo.flags & ApplicationInfo.FLAG_IS_DATA_ONLY) != 0) {
    308                     // Application is not present
    309                     continue;
    310                 }
    311 
    312                 long updateTime = c.getLong(indexLastUpdate);
    313                 int version = c.getInt(indexVersion);
    314                 LauncherActivityInfo app = componentMap.remove(component);
    315                 if (version == info.versionCode && updateTime == info.lastUpdateTime &&
    316                         TextUtils.equals(c.getString(systemStateIndex),
    317                                 mIconProvider.getIconSystemState(info.packageName))) {
    318                     continue;
    319                 }
    320                 if (app == null) {
    321                     remove(component, user);
    322                     itemsToRemove.add(c.getInt(rowIndex));
    323                 } else {
    324                     appsToUpdate.add(app);
    325                 }
    326             }
    327         } catch (SQLiteException e) {
    328             Log.d(TAG, "Error reading icon cache", e);
    329             // Continue updating whatever we have read so far
    330         } finally {
    331             if (c != null) {
    332                 c.close();
    333             }
    334         }
    335         if (!itemsToRemove.isEmpty()) {
    336             mIconDb.delete(
    337                     Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null);
    338         }
    339 
    340         // Insert remaining apps.
    341         if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) {
    342             Stack<LauncherActivityInfo> appsToAdd = new Stack<>();
    343             appsToAdd.addAll(componentMap.values());
    344             new SerializedIconUpdateTask(userSerial, pkgInfoMap,
    345                     appsToAdd, appsToUpdate).scheduleNext();
    346         }
    347     }
    348 
    349     /**
    350      * Adds an entry into the DB and the in-memory cache.
    351      * @param replaceExisting if true, it will recreate the bitmap even if it already exists in
    352      *                        the memory. This is useful then the previous bitmap was created using
    353      *                        old data.
    354      */
    355     @Thunk synchronized void addIconToDBAndMemCache(LauncherActivityInfo app,
    356             PackageInfo info, long userSerial, boolean replaceExisting) {
    357         final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser());
    358         CacheEntry entry = null;
    359         if (!replaceExisting) {
    360             entry = mCache.get(key);
    361             // We can't reuse the entry if the high-res icon is not present.
    362             if (entry == null || entry.isLowResIcon || entry.icon == null) {
    363                 entry = null;
    364             }
    365         }
    366         if (entry == null) {
    367             entry = new CacheEntry();
    368             entry.icon = LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
    369                     mContext,  app.getApplicationInfo().targetSdkVersion);
    370         }
    371         entry.title = app.getLabel();
    372         entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
    373         mCache.put(key, entry);
    374 
    375         Bitmap lowResIcon = generateLowResIcon(entry.icon);
    376         ContentValues values = newContentValues(entry.icon, lowResIcon, entry.title.toString(),
    377                 app.getApplicationInfo().packageName);
    378         addIconToDB(values, app.getComponentName(), info, userSerial);
    379     }
    380 
    381     /**
    382      * Updates {@param values} to contain versioning information and adds it to the DB.
    383      * @param values {@link ContentValues} containing icon & title
    384      */
    385     private void addIconToDB(ContentValues values, ComponentName key,
    386             PackageInfo info, long userSerial) {
    387         values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
    388         values.put(IconDB.COLUMN_USER, userSerial);
    389         values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime);
    390         values.put(IconDB.COLUMN_VERSION, info.versionCode);
    391         mIconDb.insertOrReplace(values);
    392     }
    393 
    394     /**
    395      * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
    396      * @return a request ID that can be used to cancel the request.
    397      */
    398     public IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
    399             final ItemInfoWithIcon info) {
    400         Runnable request = new Runnable() {
    401 
    402             @Override
    403             public void run() {
    404                 if (info instanceof AppInfo || info instanceof ShortcutInfo) {
    405                     getTitleAndIcon(info, false);
    406                 } else if (info instanceof PackageItemInfo) {
    407                     getTitleAndIconForApp((PackageItemInfo) info, false);
    408                 }
    409                 mMainThreadExecutor.execute(new Runnable() {
    410 
    411                     @Override
    412                     public void run() {
    413                         caller.reapplyItemInfo(info);
    414                     }
    415                 });
    416             }
    417         };
    418         mWorkerHandler.post(request);
    419         return new IconLoadRequest(request, mWorkerHandler);
    420     }
    421 
    422     /**
    423      * Updates {@param application} only if a valid entry is found.
    424      */
    425     public synchronized void updateTitleAndIcon(AppInfo application) {
    426         CacheEntry entry = cacheLocked(application.componentName,
    427                 Provider.<LauncherActivityInfo>of(null),
    428                 application.user, false, application.usingLowResIcon);
    429         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
    430             applyCacheEntry(entry, application);
    431         }
    432     }
    433 
    434     /**
    435      * Fill in {@param info} with the icon and label for {@param activityInfo}
    436      */
    437     public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
    438             LauncherActivityInfo activityInfo, boolean useLowResIcon) {
    439         // If we already have activity info, no need to use package icon
    440         getTitleAndIcon(info, Provider.of(activityInfo), false, useLowResIcon);
    441     }
    442 
    443     /**
    444      * Fill in {@param info} with the icon and label. If the
    445      * corresponding activity is not found, it reverts to the package icon.
    446      */
    447     public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
    448         // null info means not installed, but if we have a component from the intent then
    449         // we should still look in the cache for restored app icons.
    450         if (info.getTargetComponent() == null) {
    451             info.iconBitmap = getDefaultIcon(info.user);
    452             info.title = "";
    453             info.contentDescription = "";
    454             info.usingLowResIcon = false;
    455         } else {
    456             getTitleAndIcon(info, new ActivityInfoProvider(info.getIntent(), info.user),
    457                     true, useLowResIcon);
    458         }
    459     }
    460 
    461     /**
    462      * Fill in {@param shortcutInfo} with the icon and label for {@param info}
    463      */
    464     private synchronized void getTitleAndIcon(
    465             @NonNull ItemInfoWithIcon infoInOut,
    466             @NonNull Provider<LauncherActivityInfo> activityInfoProvider,
    467             boolean usePkgIcon, boolean useLowResIcon) {
    468         CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider,
    469                 infoInOut.user, usePkgIcon, useLowResIcon);
    470         applyCacheEntry(entry, infoInOut);
    471     }
    472 
    473     /**
    474      * Fill in {@param infoInOut} with the corresponding icon and label.
    475      */
    476     public synchronized void getTitleAndIconForApp(
    477             PackageItemInfo infoInOut, boolean useLowResIcon) {
    478         CacheEntry entry = getEntryForPackageLocked(
    479                 infoInOut.packageName, infoInOut.user, useLowResIcon);
    480         applyCacheEntry(entry, infoInOut);
    481     }
    482 
    483     private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
    484         info.title = Utilities.trim(entry.title);
    485         info.contentDescription = entry.contentDescription;
    486         info.iconBitmap = entry.icon == null ? getDefaultIcon(info.user) : entry.icon;
    487         info.usingLowResIcon = entry.isLowResIcon;
    488     }
    489 
    490     public synchronized Bitmap getDefaultIcon(UserHandle user) {
    491         if (!mDefaultIcons.containsKey(user)) {
    492             mDefaultIcons.put(user, makeDefaultIcon(user));
    493         }
    494         return mDefaultIcons.get(user);
    495     }
    496 
    497     public boolean isDefaultIcon(Bitmap icon, UserHandle user) {
    498         return mDefaultIcons.get(user) == icon;
    499     }
    500 
    501     /**
    502      * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
    503      * This method is not thread safe, it must be called from a synchronized method.
    504      */
    505     protected CacheEntry cacheLocked(
    506             @NonNull ComponentName componentName,
    507             @NonNull Provider<LauncherActivityInfo> infoProvider,
    508             UserHandle user, boolean usePackageIcon, boolean useLowResIcon) {
    509         Preconditions.assertWorkerThread();
    510         ComponentKey cacheKey = new ComponentKey(componentName, user);
    511         CacheEntry entry = mCache.get(cacheKey);
    512         if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
    513             entry = new CacheEntry();
    514             mCache.put(cacheKey, entry);
    515 
    516             // Check the DB first.
    517             LauncherActivityInfo info = null;
    518             boolean providerFetchedOnce = false;
    519 
    520             if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) {
    521                 info = infoProvider.get();
    522                 providerFetchedOnce = true;
    523 
    524                 if (info != null) {
    525                     entry.icon = LauncherIcons.createBadgedIconBitmap(
    526                             getFullResIcon(info), info.getUser(), mContext,
    527                             infoProvider.get().getApplicationInfo().targetSdkVersion);
    528                 } else {
    529                     if (usePackageIcon) {
    530                         CacheEntry packageEntry = getEntryForPackageLocked(
    531                                 componentName.getPackageName(), user, false);
    532                         if (packageEntry != null) {
    533                             if (DEBUG) Log.d(TAG, "using package default icon for " +
    534                                     componentName.toShortString());
    535                             entry.icon = packageEntry.icon;
    536                             entry.title = packageEntry.title;
    537                             entry.contentDescription = packageEntry.contentDescription;
    538                         }
    539                     }
    540                     if (entry.icon == null) {
    541                         if (DEBUG) Log.d(TAG, "using default icon for " +
    542                                 componentName.toShortString());
    543                         entry.icon = getDefaultIcon(user);
    544                     }
    545                 }
    546             }
    547 
    548             if (TextUtils.isEmpty(entry.title)) {
    549                 if (info == null && !providerFetchedOnce) {
    550                     info = infoProvider.get();
    551                     providerFetchedOnce = true;
    552                 }
    553                 if (info != null) {
    554                     entry.title = info.getLabel();
    555                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
    556                 }
    557             }
    558         }
    559         return entry;
    560     }
    561 
    562     public synchronized void clear() {
    563         Preconditions.assertWorkerThread();
    564         mIconDb.clear();
    565     }
    566 
    567     /**
    568      * Adds a default package entry in the cache. This entry is not persisted and will be removed
    569      * when the cache is flushed.
    570      */
    571     public synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
    572             Bitmap icon, CharSequence title) {
    573         removeFromMemCacheLocked(packageName, user);
    574 
    575         ComponentKey cacheKey = getPackageKey(packageName, user);
    576         CacheEntry entry = mCache.get(cacheKey);
    577 
    578         // For icon caching, do not go through DB. Just update the in-memory entry.
    579         if (entry == null) {
    580             entry = new CacheEntry();
    581         }
    582         if (!TextUtils.isEmpty(title)) {
    583             entry.title = title;
    584         }
    585         if (icon != null) {
    586             entry.icon = LauncherIcons.createIconBitmap(icon, mContext);
    587         }
    588         if (!TextUtils.isEmpty(title) && entry.icon != null) {
    589             mCache.put(cacheKey, entry);
    590         }
    591     }
    592 
    593     private static ComponentKey getPackageKey(String packageName, UserHandle user) {
    594         ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
    595         return new ComponentKey(cn, user);
    596     }
    597 
    598     /**
    599      * Gets an entry for the package, which can be used as a fallback entry for various components.
    600      * This method is not thread safe, it must be called from a synchronized method.
    601      */
    602     private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
    603             boolean useLowResIcon) {
    604         Preconditions.assertWorkerThread();
    605         ComponentKey cacheKey = getPackageKey(packageName, user);
    606         CacheEntry entry = mCache.get(cacheKey);
    607 
    608         if (entry == null || (entry.isLowResIcon && !useLowResIcon)) {
    609             entry = new CacheEntry();
    610             boolean entryUpdated = true;
    611 
    612             // Check the DB first.
    613             if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) {
    614                 try {
    615                     int flags = Process.myUserHandle().equals(user) ? 0 :
    616                         PackageManager.GET_UNINSTALLED_PACKAGES;
    617                     PackageInfo info = mPackageManager.getPackageInfo(packageName, flags);
    618                     ApplicationInfo appInfo = info.applicationInfo;
    619                     if (appInfo == null) {
    620                         throw new NameNotFoundException("ApplicationInfo is null");
    621                     }
    622 
    623                     // Load the full res icon for the application, but if useLowResIcon is set, then
    624                     // only keep the low resolution icon instead of the larger full-sized icon
    625                     Bitmap icon = LauncherIcons.createBadgedIconBitmap(
    626                             appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
    627                     if (mInstantAppResolver.isInstantApp(appInfo)) {
    628                         icon = LauncherIcons.badgeWithDrawable(icon,
    629                                 mContext.getDrawable(R.drawable.ic_instant_app_badge), mContext);
    630                     }
    631                     Bitmap lowResIcon =  generateLowResIcon(icon);
    632                     entry.title = appInfo.loadLabel(mPackageManager);
    633                     entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
    634                     entry.icon = useLowResIcon ? lowResIcon : icon;
    635                     entry.isLowResIcon = useLowResIcon;
    636 
    637                     // Add the icon in the DB here, since these do not get written during
    638                     // package updates.
    639                     ContentValues values =
    640                             newContentValues(icon, lowResIcon, entry.title.toString(), packageName);
    641                     addIconToDB(values, cacheKey.componentName, info,
    642                             mUserManager.getSerialNumberForUser(user));
    643 
    644                 } catch (NameNotFoundException e) {
    645                     if (DEBUG) Log.d(TAG, "Application not installed " + packageName);
    646                     entryUpdated = false;
    647                 }
    648             }
    649 
    650             // Only add a filled-out entry to the cache
    651             if (entryUpdated) {
    652                 mCache.put(cacheKey, entry);
    653             }
    654         }
    655         return entry;
    656     }
    657 
    658     private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
    659         Cursor c = null;
    660         try {
    661             c = mIconDb.query(
    662                 new String[]{lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON,
    663                         IconDB.COLUMN_LABEL},
    664                 IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?",
    665                 new String[]{cacheKey.componentName.flattenToString(),
    666                         Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))});
    667             if (c.moveToNext()) {
    668                 entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : null);
    669                 entry.isLowResIcon = lowRes;
    670                 entry.title = c.getString(1);
    671                 if (entry.title == null) {
    672                     entry.title = "";
    673                     entry.contentDescription = "";
    674                 } else {
    675                     entry.contentDescription = mUserManager.getBadgedLabelForUser(
    676                             entry.title, cacheKey.user);
    677                 }
    678                 return true;
    679             }
    680         } catch (SQLiteException e) {
    681             Log.d(TAG, "Error reading icon cache", e);
    682         } finally {
    683             if (c != null) {
    684                 c.close();
    685             }
    686         }
    687         return false;
    688     }
    689 
    690     public static class IconLoadRequest {
    691         private final Runnable mRunnable;
    692         private final Handler mHandler;
    693 
    694         IconLoadRequest(Runnable runnable, Handler handler) {
    695             mRunnable = runnable;
    696             mHandler = handler;
    697         }
    698 
    699         public void cancel() {
    700             mHandler.removeCallbacks(mRunnable);
    701         }
    702     }
    703 
    704     /**
    705      * A runnable that updates invalid icons and adds missing icons in the DB for the provided
    706      * LauncherActivityInfo list. Items are updated/added one at a time, so that the
    707      * worker thread doesn't get blocked.
    708      */
    709     @Thunk class SerializedIconUpdateTask implements Runnable {
    710         private final long mUserSerial;
    711         private final HashMap<String, PackageInfo> mPkgInfoMap;
    712         private final Stack<LauncherActivityInfo> mAppsToAdd;
    713         private final Stack<LauncherActivityInfo> mAppsToUpdate;
    714         private final HashSet<String> mUpdatedPackages = new HashSet<>();
    715 
    716         @Thunk SerializedIconUpdateTask(long userSerial, HashMap<String, PackageInfo> pkgInfoMap,
    717                 Stack<LauncherActivityInfo> appsToAdd,
    718                 Stack<LauncherActivityInfo> appsToUpdate) {
    719             mUserSerial = userSerial;
    720             mPkgInfoMap = pkgInfoMap;
    721             mAppsToAdd = appsToAdd;
    722             mAppsToUpdate = appsToUpdate;
    723         }
    724 
    725         @Override
    726         public void run() {
    727             if (!mAppsToUpdate.isEmpty()) {
    728                 LauncherActivityInfo app = mAppsToUpdate.pop();
    729                 String pkg = app.getComponentName().getPackageName();
    730                 PackageInfo info = mPkgInfoMap.get(pkg);
    731                 addIconToDBAndMemCache(app, info, mUserSerial, true /*replace existing*/);
    732                 mUpdatedPackages.add(pkg);
    733 
    734                 if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) {
    735                     // No more app to update. Notify model.
    736                     LauncherAppState.getInstance(mContext).getModel().onPackageIconsUpdated(
    737                             mUpdatedPackages, mUserManager.getUserForSerialNumber(mUserSerial));
    738                 }
    739 
    740                 // Let it run one more time.
    741                 scheduleNext();
    742             } else if (!mAppsToAdd.isEmpty()) {
    743                 LauncherActivityInfo app = mAppsToAdd.pop();
    744                 PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName());
    745                 // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every
    746                 // app should have package info, this is not guaranteed by the api
    747                 if (info != null) {
    748                     addIconToDBAndMemCache(app, info, mUserSerial, false /*replace existing*/);
    749                 }
    750 
    751                 if (!mAppsToAdd.isEmpty()) {
    752                     scheduleNext();
    753                 }
    754             }
    755         }
    756 
    757         public void scheduleNext() {
    758             mWorkerHandler.postAtTime(this, ICON_UPDATE_TOKEN, SystemClock.uptimeMillis() + 1);
    759         }
    760     }
    761 
    762     private static final class IconDB extends SQLiteCacheHelper {
    763         private final static int DB_VERSION = 17;
    764 
    765         private final static int RELEASE_VERSION = DB_VERSION +
    766                 (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
    767 
    768         private final static String TABLE_NAME = "icons";
    769         private final static String COLUMN_ROWID = "rowid";
    770         private final static String COLUMN_COMPONENT = "componentName";
    771         private final static String COLUMN_USER = "profileId";
    772         private final static String COLUMN_LAST_UPDATED = "lastUpdated";
    773         private final static String COLUMN_VERSION = "version";
    774         private final static String COLUMN_ICON = "icon";
    775         private final static String COLUMN_ICON_LOW_RES = "icon_low_res";
    776         private final static String COLUMN_LABEL = "label";
    777         private final static String COLUMN_SYSTEM_STATE = "system_state";
    778 
    779         public IconDB(Context context, int iconPixelSize) {
    780             super(context, LauncherFiles.APP_ICONS_DB,
    781                     (RELEASE_VERSION << 16) + iconPixelSize,
    782                     TABLE_NAME);
    783         }
    784 
    785         @Override
    786         protected void onCreateTable(SQLiteDatabase db) {
    787             db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
    788                     COLUMN_COMPONENT + " TEXT NOT NULL, " +
    789                     COLUMN_USER + " INTEGER NOT NULL, " +
    790                     COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " +
    791                     COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " +
    792                     COLUMN_ICON + " BLOB, " +
    793                     COLUMN_ICON_LOW_RES + " BLOB, " +
    794                     COLUMN_LABEL + " TEXT, " +
    795                     COLUMN_SYSTEM_STATE + " TEXT, " +
    796                     "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " +
    797                     ");");
    798         }
    799     }
    800 
    801     private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, String label,
    802             String packageName) {
    803         ContentValues values = new ContentValues();
    804         values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon));
    805         values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(lowResIcon));
    806 
    807         values.put(IconDB.COLUMN_LABEL, label);
    808         values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName));
    809 
    810         return values;
    811     }
    812 
    813     /**
    814      * Generates a new low-res icon given a high-res icon.
    815      */
    816     private Bitmap generateLowResIcon(Bitmap icon) {
    817         return Bitmap.createScaledBitmap(icon,
    818                 icon.getWidth() / LOW_RES_SCALE_FACTOR,
    819                 icon.getHeight() / LOW_RES_SCALE_FACTOR, true);
    820     }
    821 
    822     private static Bitmap loadIconNoResize(Cursor c, int iconIndex, BitmapFactory.Options options) {
    823         byte[] data = c.getBlob(iconIndex);
    824         try {
    825             return BitmapFactory.decodeByteArray(data, 0, data.length, options);
    826         } catch (Exception e) {
    827             return null;
    828         }
    829     }
    830 
    831     private class ActivityInfoProvider extends Provider<LauncherActivityInfo> {
    832 
    833         private final Intent mIntent;
    834         private final UserHandle mUser;
    835 
    836         public ActivityInfoProvider(Intent intent, UserHandle user) {
    837             mIntent = intent;
    838             mUser = user;
    839         }
    840 
    841         @Override
    842         public LauncherActivityInfo get() {
    843             return mLauncherApps.resolveActivity(mIntent, mUser);
    844         }
    845     }
    846 
    847     /**
    848      * Interface for receiving itemInfo with high-res icon.
    849      */
    850     public interface ItemInfoUpdateReceiver {
    851 
    852         void reapplyItemInfo(ItemInfoWithIcon info);
    853     }
    854 }
    855