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.Context;
23 import android.content.Intent;
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.graphics.drawable.Drawable;
30 import android.os.Handler;
31 import android.os.Process;
32 import android.os.UserHandle;
33 import android.util.Log;
34 
35 import androidx.annotation.NonNull;
36 
37 import com.android.launcher3.AppInfo;
38 import com.android.launcher3.IconProvider;
39 import com.android.launcher3.InvariantDeviceProfile;
40 import com.android.launcher3.ItemInfoWithIcon;
41 import com.android.launcher3.LauncherFiles;
42 import com.android.launcher3.Utilities;
43 import com.android.launcher3.WorkspaceItemInfo;
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.icons.ComponentWithLabel.ComponentCachingLogic;
48 import com.android.launcher3.icons.cache.BaseIconCache;
49 import com.android.launcher3.icons.cache.CachingLogic;
50 import com.android.launcher3.icons.cache.HandlerRunnable;
51 import com.android.launcher3.model.PackageItemInfo;
52 import com.android.launcher3.util.InstantAppResolver;
53 import com.android.launcher3.util.Preconditions;
54 
55 import java.util.function.Supplier;
56 
57 /**
58  * Cache of application icons.  Icons can be made from any thread.
59  */
60 public class IconCache extends BaseIconCache {
61 
62     private static final String TAG = "Launcher.IconCache";
63 
64     private final CachingLogic<ComponentWithLabel> mComponentWithLabelCachingLogic;
65     private final CachingLogic<LauncherActivityInfo> mLauncherActivityInfoCachingLogic;
66 
67     private final LauncherAppsCompat mLauncherApps;
68     private final UserManagerCompat mUserManager;
69     private final InstantAppResolver mInstantAppResolver;
70     private final IconProvider mIconProvider;
71 
72     private int mPendingIconRequestCount = 0;
73 
IconCache(Context context, InvariantDeviceProfile inv)74     public IconCache(Context context, InvariantDeviceProfile inv) {
75         super(context, LauncherFiles.APP_ICONS_DB, MODEL_EXECUTOR.getLooper(),
76                 inv.fillResIconDpi, inv.iconBitmapSize, true /* inMemoryCache */);
77         mComponentWithLabelCachingLogic = new ComponentCachingLogic(context, false);
78         mLauncherActivityInfoCachingLogic = LauncherActivityCachingLogic.newInstance(context);
79         mLauncherApps = LauncherAppsCompat.getInstance(mContext);
80         mUserManager = UserManagerCompat.getInstance(mContext);
81         mInstantAppResolver = InstantAppResolver.newInstance(mContext);
82         mIconProvider = IconProvider.INSTANCE.get(context);
83     }
84 
85     @Override
getSerialNumberForUser(UserHandle user)86     protected long getSerialNumberForUser(UserHandle user) {
87         return mUserManager.getSerialNumberForUser(user);
88     }
89 
90     @Override
isInstantApp(ApplicationInfo info)91     protected boolean isInstantApp(ApplicationInfo info) {
92         return mInstantAppResolver.isInstantApp(info);
93     }
94 
95     @Override
getIconFactory()96     protected BaseIconFactory getIconFactory() {
97         return LauncherIcons.obtain(mContext);
98     }
99 
100     /**
101      * Updates the entries related to the given package in memory and persistent DB.
102      */
updateIconsForPkg(String packageName, UserHandle user)103     public synchronized void updateIconsForPkg(String packageName, UserHandle user) {
104         removeIconsForPkg(packageName, user);
105         try {
106             PackageInfo info = mPackageManager.getPackageInfo(packageName,
107                     PackageManager.GET_UNINSTALLED_PACKAGES);
108             long userSerial = mUserManager.getSerialNumberForUser(user);
109             for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) {
110                 addIconToDBAndMemCache(app, mLauncherActivityInfoCachingLogic, info, userSerial,
111                         false /*replace existing*/);
112             }
113         } catch (NameNotFoundException e) {
114             Log.d(TAG, "Package not found", e);
115         }
116     }
117 
118     /**
119      * Fetches high-res icon for the provided ItemInfo and updates the caller when done.
120      * @return a request ID that can be used to cancel the request.
121      */
updateIconInBackground(final ItemInfoUpdateReceiver caller, final ItemInfoWithIcon info)122     public IconLoadRequest updateIconInBackground(final ItemInfoUpdateReceiver caller,
123             final ItemInfoWithIcon info) {
124         Preconditions.assertUIThread();
125         if (mPendingIconRequestCount <= 0) {
126             MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
127         }
128         mPendingIconRequestCount ++;
129 
130         IconLoadRequest request = new IconLoadRequest(mWorkerHandler, this::onIconRequestEnd) {
131             @Override
132             public void run() {
133                 if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
134                     getTitleAndIcon(info, false);
135                 } else if (info instanceof PackageItemInfo) {
136                     getTitleAndIconForApp((PackageItemInfo) info, false);
137                 }
138                 MAIN_EXECUTOR.execute(() -> {
139                     caller.reapplyItemInfo(info);
140                     onEnd();
141                 });
142             }
143         };
144         Utilities.postAsyncCallback(mWorkerHandler, request);
145         return request;
146     }
147 
onIconRequestEnd()148     private void onIconRequestEnd() {
149         mPendingIconRequestCount --;
150         if (mPendingIconRequestCount <= 0) {
151             MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
152         }
153     }
154 
155     /**
156      * Updates {@param application} only if a valid entry is found.
157      */
updateTitleAndIcon(AppInfo application)158     public synchronized void updateTitleAndIcon(AppInfo application) {
159         CacheEntry entry = cacheLocked(application.componentName,
160                 application.user, () -> null, mLauncherActivityInfoCachingLogic,
161                 false, application.usingLowResIcon());
162         if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) {
163             applyCacheEntry(entry, application);
164         }
165     }
166 
167     /**
168      * Fill in {@param info} with the icon and label for {@param activityInfo}
169      */
getTitleAndIcon(ItemInfoWithIcon info, LauncherActivityInfo activityInfo, boolean useLowResIcon)170     public synchronized void getTitleAndIcon(ItemInfoWithIcon info,
171             LauncherActivityInfo activityInfo, boolean useLowResIcon) {
172         // If we already have activity info, no need to use package icon
173         getTitleAndIcon(info, () -> activityInfo, false, useLowResIcon);
174     }
175 
176     /**
177      * Fill in {@param info} with the icon and label. If the
178      * corresponding activity is not found, it reverts to the package icon.
179      */
getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon)180     public synchronized void getTitleAndIcon(ItemInfoWithIcon info, boolean useLowResIcon) {
181         // null info means not installed, but if we have a component from the intent then
182         // we should still look in the cache for restored app icons.
183         if (info.getTargetComponent() == null) {
184             info.applyFrom(getDefaultIcon(info.user));
185             info.title = "";
186             info.contentDescription = "";
187         } else {
188             Intent intent = info.getIntent();
189             getTitleAndIcon(info, () -> mLauncherApps.resolveActivity(intent, info.user),
190                     true, useLowResIcon);
191         }
192     }
193 
getTitleNoCache(ComponentWithLabel info)194     public synchronized String getTitleNoCache(ComponentWithLabel info) {
195         CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), () -> info,
196                 mComponentWithLabelCachingLogic, false /* usePackageIcon */,
197                 true /* useLowResIcon */);
198         return Utilities.trim(entry.title);
199     }
200 
201     /**
202      * Fill in {@param mWorkspaceItemInfo} with the icon and label for {@param info}
203      */
getTitleAndIcon( @onNull ItemInfoWithIcon infoInOut, @NonNull Supplier<LauncherActivityInfo> activityInfoProvider, boolean usePkgIcon, boolean useLowResIcon)204     private synchronized void getTitleAndIcon(
205             @NonNull ItemInfoWithIcon infoInOut,
206             @NonNull Supplier<LauncherActivityInfo> activityInfoProvider,
207             boolean usePkgIcon, boolean useLowResIcon) {
208         CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user,
209                 activityInfoProvider, mLauncherActivityInfoCachingLogic, usePkgIcon, useLowResIcon);
210         applyCacheEntry(entry, infoInOut);
211     }
212 
213 
214     /**
215      * Fill in {@param infoInOut} with the corresponding icon and label.
216      */
getTitleAndIconForApp( PackageItemInfo infoInOut, boolean useLowResIcon)217     public synchronized void getTitleAndIconForApp(
218             PackageItemInfo infoInOut, boolean useLowResIcon) {
219         CacheEntry entry = getEntryForPackageLocked(
220                 infoInOut.packageName, infoInOut.user, useLowResIcon);
221         applyCacheEntry(entry, infoInOut);
222     }
223 
applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info)224     protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) {
225         info.title = Utilities.trim(entry.title);
226         info.contentDescription = entry.contentDescription;
227         info.applyFrom((entry.icon == null) ? getDefaultIcon(info.user) : entry);
228     }
229 
getFullResIcon(LauncherActivityInfo info)230     public Drawable getFullResIcon(LauncherActivityInfo info) {
231         return getFullResIcon(info, true);
232     }
233 
getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable)234     public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) {
235         return mIconProvider.getIcon(info, mIconDpi, flattenDrawable);
236     }
237 
238     @Override
getIconSystemState(String packageName)239     protected String getIconSystemState(String packageName) {
240         return mIconProvider.getSystemStateForPackage(mSystemState, packageName)
241                 + ",flags_asi:" + FeatureFlags.APP_SEARCH_IMPROVEMENTS.get();
242     }
243 
244     public static abstract class IconLoadRequest extends HandlerRunnable {
IconLoadRequest(Handler handler, Runnable endRunnable)245         IconLoadRequest(Handler handler, Runnable endRunnable) {
246             super(handler, endRunnable);
247         }
248     }
249 
250     /**
251      * Interface for receiving itemInfo with high-res icon.
252      */
253     public interface ItemInfoUpdateReceiver {
254 
reapplyItemInfo(ItemInfoWithIcon info)255         void reapplyItemInfo(ItemInfoWithIcon info);
256     }
257 }
258