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