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.util.Executors.MAIN_EXECUTOR;
20 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
21 
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.LauncherActivityInfo;
27 import android.content.pm.LauncherApps;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageInstaller;
30 import android.content.pm.PackageManager;
31 import android.content.pm.PackageManager.NameNotFoundException;
32 import android.content.pm.ShortcutInfo;
33 import android.graphics.drawable.Drawable;
34 import android.os.Handler;
35 import android.os.Process;
36 import android.os.UserHandle;
37 import android.util.Log;
38 
39 import androidx.annotation.NonNull;
40 
41 import com.android.launcher3.InvariantDeviceProfile;
42 import com.android.launcher3.LauncherFiles;
43 import com.android.launcher3.Utilities;
44 import com.android.launcher3.config.FeatureFlags;
45 import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
46 import com.android.launcher3.icons.cache.BaseIconCache;
47 import com.android.launcher3.icons.cache.CachingLogic;
48 import com.android.launcher3.icons.cache.HandlerRunnable;
49 import com.android.launcher3.model.data.AppInfo;
50 import com.android.launcher3.model.data.ItemInfoWithIcon;
51 import com.android.launcher3.model.data.PackageItemInfo;
52 import com.android.launcher3.model.data.WorkspaceItemInfo;
53 import com.android.launcher3.pm.UserCache;
54 import com.android.launcher3.shortcuts.ShortcutKey;
55 import com.android.launcher3.util.ComponentKey;
56 import com.android.launcher3.util.InstantAppResolver;
57 import com.android.launcher3.util.PackageUserKey;
58 import com.android.launcher3.util.Preconditions;
59 
60 import java.util.function.Predicate;
61 import java.util.function.Supplier;
62 
63 /**
64  * Cache of application icons.  Icons can be made from any thread.
65  */
66 public class IconCache extends BaseIconCache {
67 
68     private static final String TAG = "Launcher.IconCache";
69 
70     private final Predicate<ItemInfoWithIcon> mIsUsingFallbackIconCheck = w -> w.bitmap != null
71             && w.bitmap.isNullOrLowRes() && !isDefaultIcon(w.bitmap, w.user);
72 
73     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
74     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
75     private final CachingLogic<ShortcutInfo> mShortcutCachingLogic;
76 
77     private final LauncherApps mLauncherApps;
78     private final UserCache mUserManager;
79     private final InstantAppResolver mInstantAppResolver;
80     private final IconProvider mIconProvider;
81 
82     private int mPendingIconRequestCount = 0;
83 
IconCache(Context context, InvariantDeviceProfile idp)84     public IconCache(Context context, InvariantDeviceProfile idp) {
85         this(context, idp, LauncherFiles.APP_ICONS_DB);
86     }
87 
IconCache(Context context, InvariantDeviceProfile idp, String dbFileName)88     public IconCache(Context context, InvariantDeviceProfile idp, String dbFileName) {
89         super(context, dbFileName, MODEL_EXECUTOR.getLooper(),
90                 idp.fillResIconDpi, idp.iconBitmapSize, true /* inMemoryCache */);
91         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
92         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
93         mShortcutCachingLogic = new ShortcutCachingLogic();
94         mLauncherApps = mContext.getSystemService(LauncherApps.class);
95         mUserManager = UserCache.INSTANCE.get(mContext);
96         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
97         mIconProvider = new IconProvider(context);
98     }
99 
100     @Override
getSerialNumberForUser(UserHandle user)101     protected long getSerialNumberForUser(UserHandle user) {
102         return mUserManager.getSerialNumberForUser(user);
103     }
104 
105     @Override
isInstantApp(ApplicationInfo info)106     protected boolean isInstantApp(ApplicationInfo info) {
107         return mInstantAppResolver.isInstantApp(info);
108     }
109 
110     @Override
getIconFactory()111     protected BaseIconFactory getIconFactory() {
112         return LauncherIcons.obtain(mContext);
113     }
114 
115     /**
116      * Updates the entries related to the given package in memory and persistent DB.
117      */
updateIconsForPkg(String packageName, UserHandle user)118     public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
119         removeIconsForPkg(packageName, user);
120         try {
121             PackageInfo info = mPackageManager.getPackageInfo(packageName,
122                     PackageManager.GET_UNINSTALLED_PACKAGES);
123             long userSerial = mUserManager.getSerialNumberForUser(user);
124             for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
125                 addIconToDBAndMemCache(app, mLauncherActivityInfoCachingLogic, info, userSerial,
126                         false /*replace existing*/);
127             }
128         } catch (NameNotFoundException e) {
129             Log.d(TAG, "Package not found", e);
130         }
131     }
132 
133     /**
134      * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
135      * @return a request ID that can be used to cancel the request.
136      */
updateIconInBackground(final ItemInfoUpdateReceiver caller, final ItemInfoWithIcon info)137     public IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
138             final ItemInfoWithIcon info) {
139         Preconditions.assertUIThread();
140         if (mPendingIconRequestCount <= 0) {
141             MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
142         }
143         mPendingIconRequestCount ++;
144 
145         IconLoadRequest request = new IconLoadRequest(mWorkerHandler, this::onIconRequestEnd) {
146             @Override
147             public void run() {
148                 if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
149                     getTitleAndIcon(info, false);
150                 } else if (info instanceof PackageItemInfo) {
151                     getTitleAndIconForApp((PackageItemInfo) info, false);
152                 }
153                 MAIN_EXECUTOR.execute(() -> {
154                     caller.reapplyItemInfo(info);
155                     onEnd();
156                 });
157             }
158         };
159         Utilities.postAsyncCallback(mWorkerHandler, request);
160         return request;
161     }
162 
onIconRequestEnd()163     private void onIconRequestEnd() {
164         mPendingIconRequestCount --;
165         if (mPendingIconRequestCount <= 0) {
166             MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
167         }
168     }
169 
170     /**
171      * Updates {@param application} only if a valid entry is found.
172      */
updateTitleAndIcon(AppInfo application)173     public synchronized void updateTitleAndIcon(AppInfo application) {
174         CacheEntry entry = cacheLocked(application.componentName,
175                 application.user, () -> null, mLauncherActivityInfoCachingLogic,
176                 false, application.usingLowResIcon());
177         if (entry.bitmap != null && !isDefaultIcon(entry.bitmap, application.user)) {
178             applyCacheEntry(entry, application);
179         }
180     }
181 
182     /**
183      * Fill in {@param info} with the icon and label for {@param activityInfo}
184      */
getTitleAndIcon(ItemInfoWithIcon info, LauncherActivityInfo activityInfo, boolean useLowResIcon)185     public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
186             LauncherActivityInfo activityInfo, boolean useLowResIcon) {
187         // If we already have activity info, no need to use package icon
188         getTitleAndIcon(info, () -> activityInfo, false, useLowResIcon);
189     }
190 
191     /**
192      * Fill in {@param info} with the icon for {@param si}
193      */
getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si)194     public void getShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
195         getShortcutIcon(info, si, true, mIsUsingFallbackIconCheck);
196     }
197 
198     /**
199      * Fill in {@param info} with an unbadged icon for {@param si}
200      */
getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si)201     public void getUnbadgedShortcutIcon(ItemInfoWithIcon info, ShortcutInfo si) {
202         getShortcutIcon(info, si, false, mIsUsingFallbackIconCheck);
203     }
204 
205     /**
206      * Fill in {@param info} with the icon and label for {@param si}. If the icon is not
207      * available, and fallback check returns true, it keeps the old icon.
208      */
getShortcutIcon(T info, ShortcutInfo si, @NonNull Predicate<T> fallbackIconCheck)209     public <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
210             @NonNull Predicate<T> fallbackIconCheck) {
211         getShortcutIcon(info, si, true /* use badged */, fallbackIconCheck);
212     }
213 
getShortcutIcon(T info, ShortcutInfo si, boolean useBadged, @NonNull Predicate<T> fallbackIconCheck)214     private synchronized <T extends ItemInfoWithIcon> void getShortcutIcon(T info, ShortcutInfo si,
215             boolean useBadged, @NonNull Predicate<T> fallbackIconCheck) {
216         BitmapInfo bitmapInfo;
217         if (FeatureFlags.ENABLE_DEEP_SHORTCUT_ICON_CACHE.get()) {
218             bitmapInfo = cacheLocked(ShortcutKey.fromInfo(si).componentName, si.getUserHandle(),
219                     () -> si, mShortcutCachingLogic, false, false).bitmap;
220         } else {
221             // If caching is disabled, load the full icon
222             bitmapInfo = mShortcutCachingLogic.loadIcon(mContext, si);
223         }
224         if (bitmapInfo.isNullOrLowRes()) {
225             bitmapInfo = getDefaultIcon(si.getUserHandle());
226         }
227 
228         if (isDefaultIcon(bitmapInfo, si.getUserHandle()) && fallbackIconCheck.test(info)) {
229             return;
230         }
231         info.bitmap = bitmapInfo;
232         if (useBadged) {
233             BitmapInfo badgeInfo = getShortcutInfoBadge(si);
234             try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
235                 info.bitmap = li.badgeBitmap(info.bitmap.icon, badgeInfo);
236             }
237         }
238     }
239 
240     /**
241      * Returns the badging info for the shortcut
242      */
getShortcutInfoBadge(ShortcutInfo shortcutInfo)243     public BitmapInfo getShortcutInfoBadge(ShortcutInfo shortcutInfo) {
244         ComponentName cn = shortcutInfo.getActivity();
245         if (cn != null) {
246             // Get the app info for the source activity.
247             AppInfo appInfo = new AppInfo();
248             appInfo.user = shortcutInfo.getUserHandle();
249             appInfo.componentName = cn;
250             appInfo.intent = new Intent(Intent.ACTION_MAIN)
251                     .addCategory(Intent.CATEGORY_LAUNCHER)
252                     .setComponent(cn);
253             getTitleAndIcon(appInfo, false);
254             return appInfo.bitmap;
255         } else {
256             PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
257             getTitleAndIconForApp(pkgInfo, false);
258             return pkgInfo.bitmap;
259         }
260     }
261 
262     /**
263      * Fill in info with the icon and label for deep shortcut.
264      */
getDeepShortcutTitleAndIcon(ShortcutInfo info)265     public synchronized CacheEntry getDeepShortcutTitleAndIcon(ShortcutInfo info) {
266         return cacheLocked(ShortcutKey.fromInfo(info).componentName, info.getUserHandle(),
267                 () -> info, mShortcutCachingLogic, false, false);
268     }
269 
270     /**
271      * Fill in {@param info} with the icon and label. If the
272      * corresponding activity is not found, it reverts to the package icon.
273      */
getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon)274     public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
275         // null info means not installed, but if we have a component from the intent then
276         // we should still look in the cache for restored app icons.
277         if (info.getTargetComponent() == null) {
278             info.bitmap = getDefaultIcon(info.user);
279             info.title = "";
280             info.contentDescription = "";
281         } else {
282             Intent intent = info.getIntent();
283             getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
284                     true, useLowResIcon);
285         }
286     }
287 
getTitleNoCache(ComponentWithLabel info)288     public synchronized String getTitleNoCache(ComponentWithLabel info) {
289         CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
290                 mComponentWithLabelCachingLogic, false /* usePackageIcon */,
291                 true /* useLowResIcon */);
292         return Utilities.trim(entry.title);
293     }
294 
295     /**
296      * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info}
297      */
getTitleAndIcon( @onNull ItemInfoWithIcon infoInOut, @NonNull Supplier<LauncherActivityInfo> activityInfoProvider, boolean usePkgIcon, boolean useLowResIcon)298     private synchronized void getTitleAndIcon(
299             @NonNull ItemInfoWithIcon infoInOut,
300             @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
301             boolean usePkgIcon, boolean useLowResIcon) {
302         CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
303                 activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, useLowResIcon);
304         applyCacheEntry(entry, infoInOut);
305     }
306 
307 
308     /**
309      * Fill in {@param infoInOut} with the corresponding icon and label.
310      */
getTitleAndIconForApp( PackageItemInfo infoInOut, boolean useLowResIcon)311     public synchronized void getTitleAndIconForApp(
312             PackageItemInfo infoInOut, boolean useLowResIcon) {
313         CacheEntry entry = getEntryForPackageLocked(
314                 infoInOut.packageName, infoInOut.user, useLowResIcon);
315         applyCacheEntry(entry, infoInOut);
316     }
317 
applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info)318     protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
319         info.title = Utilities.trim(entry.title);
320         info.contentDescription = entry.contentDescription;
321         info.bitmap = (entry.bitmap == null) ? getDefaultIcon(info.user) : entry.bitmap;
322     }
323 
getFullResIcon(LauncherActivityInfo info)324     public Drawable getFullResIcon(LauncherActivityInfo info) {
325         return mIconProvider.getIcon(info, mIconDpi);
326     }
327 
updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info)328     public void updateSessionCache(PackageUserKey key, PackageInstaller.SessionInfo info) {
329         cachePackageInstallInfo(key.mPackageName, key.mUser, info.getAppIcon(), info.getAppLabel());
330     }
331 
332     @Override
getIconSystemState(String packageName)333     protected String getIconSystemState(String packageName) {
334         return mIconProvider.getSystemStateForPackage(mSystemState, packageName)
335                 + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get();
336     }
337 
338     @Override
getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes)339     protected boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
340         if (mIconProvider.isClockIcon(cacheKey)) {
341             // For clock icon, we always load the dynamic icon
342             return false;
343         }
344         return super.getEntryFromDB(cacheKey, entry, lowRes);
345     }
346 
347     public static abstract class IconLoadRequest extends HandlerRunnable {
IconLoadRequest(Handler handler, Runnable endRunnable)348         IconLoadRequest(Handler handler, Runnable endRunnable) {
349             super(handler, endRunnable);
350         }
351     }
352 
353     /**
354      * Interface for receiving itemInfo with high-res icon.
355      */
356     public interface ItemInfoUpdateReceiver {
357 
reapplyItemInfo(ItemInfoWithIcon info)358         void reapplyItemInfo(ItemInfoWithIcon info);
359     }
360 }
361