• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 package com.android.quickstep;
17 
18 import static com.android.launcher3.uioverrides.QuickstepLauncher.GO_LOW_RAM_RECENTS_ENABLED;
19 import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
20 
21 import android.app.ActivityManager.TaskDescription;
22 import android.content.Context;
23 import android.content.pm.ActivityInfo;
24 import android.content.pm.PackageManager;
25 import android.content.res.Resources;
26 import android.graphics.Bitmap;
27 import android.graphics.drawable.BitmapDrawable;
28 import android.graphics.drawable.Drawable;
29 import android.os.Build;
30 import android.os.UserHandle;
31 import android.text.TextUtils;
32 import android.util.SparseArray;
33 import android.view.accessibility.AccessibilityManager;
34 
35 import androidx.annotation.WorkerThread;
36 
37 import com.android.launcher3.R;
38 import com.android.launcher3.Utilities;
39 import com.android.launcher3.icons.BaseIconFactory;
40 import com.android.launcher3.icons.BitmapInfo;
41 import com.android.launcher3.icons.IconProvider;
42 import com.android.launcher3.util.DisplayController;
43 import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
44 import com.android.launcher3.util.DisplayController.Info;
45 import com.android.launcher3.util.Preconditions;
46 import com.android.quickstep.util.CancellableTask;
47 import com.android.quickstep.util.TaskKeyLruCache;
48 import com.android.systemui.shared.recents.model.Task;
49 import com.android.systemui.shared.recents.model.Task.TaskKey;
50 import com.android.systemui.shared.system.PackageManagerWrapper;
51 import com.android.systemui.shared.system.TaskDescriptionCompat;
52 
53 import java.util.concurrent.Executor;
54 import java.util.function.Consumer;
55 
56 /**
57  * Manages the caching of task icons and related data.
58  */
59 public class TaskIconCache implements DisplayInfoChangeListener {
60 
61     private final Executor mBgExecutor;
62     private final AccessibilityManager mAccessibilityManager;
63 
64     private final Context mContext;
65     private final TaskKeyLruCache<TaskCacheEntry> mIconCache;
66     private final SparseArray<BitmapInfo> mDefaultIcons = new SparseArray<>();
67     private final IconProvider mIconProvider;
68 
69     private BaseIconFactory mIconFactory;
70 
TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider)71     public TaskIconCache(Context context, Executor bgExecutor, IconProvider iconProvider) {
72         mContext = context;
73         mBgExecutor = bgExecutor;
74         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
75         mIconProvider = iconProvider;
76 
77         Resources res = context.getResources();
78         int cacheSize = res.getInteger(R.integer.recentsIconCacheSize);
79 
80         mIconCache = new TaskKeyLruCache<>(cacheSize);
81 
82         DisplayController.INSTANCE.get(mContext).addChangeListener(this);
83     }
84 
85     @Override
onDisplayInfoChanged(Context context, Info info, int flags)86     public void onDisplayInfoChanged(Context context, Info info, int flags) {
87         if ((flags & CHANGE_DENSITY) != 0) {
88             clearCache();
89         }
90     }
91 
92     /**
93      * Asynchronously fetches the icon and other task data.
94      *
95      * @param task The task to fetch the data for
96      * @param callback The callback to receive the task after its data has been populated.
97      * @return A cancelable handle to the request
98      */
updateIconInBackground(Task task, Consumer<Task> callback)99     public CancellableTask updateIconInBackground(Task task, Consumer<Task> callback) {
100         Preconditions.assertUIThread();
101         if (task.icon != null) {
102             // Nothing to load, the icon is already loaded
103             callback.accept(task);
104             return null;
105         }
106         CancellableTask<TaskCacheEntry> request = new CancellableTask<TaskCacheEntry>() {
107             @Override
108             public TaskCacheEntry getResultOnBg() {
109                 return getCacheEntry(task);
110             }
111 
112             @Override
113             public void handleResult(TaskCacheEntry result) {
114                 task.icon = result.icon;
115                 task.titleDescription = result.contentDescription;
116                 callback.accept(task);
117             }
118         };
119         mBgExecutor.execute(request);
120         return request;
121     }
122 
123     /**
124      * Clears the icon cache
125      */
clearCache()126     public void clearCache() {
127         mBgExecutor.execute(this::resetFactory);
128     }
129 
onTaskRemoved(TaskKey taskKey)130     void onTaskRemoved(TaskKey taskKey) {
131         mIconCache.remove(taskKey);
132     }
133 
invalidateCacheEntries(String pkg, UserHandle handle)134     void invalidateCacheEntries(String pkg, UserHandle handle) {
135         mBgExecutor.execute(() -> mIconCache.removeAll(key ->
136                 pkg.equals(key.getPackageName()) && handle.getIdentifier() == key.userId));
137     }
138 
139     @WorkerThread
getCacheEntry(Task task)140     private TaskCacheEntry getCacheEntry(Task task) {
141         TaskCacheEntry entry = mIconCache.getAndInvalidateIfModified(task.key);
142         if (entry != null) {
143             return entry;
144         }
145 
146         TaskDescription desc = task.taskDescription;
147         TaskKey key = task.key;
148         ActivityInfo activityInfo = null;
149 
150         // Create new cache entry
151         entry = new TaskCacheEntry();
152 
153         // Load icon
154         // TODO: Load icon resource (b/143363444)
155         Bitmap icon = TaskDescriptionCompat.getIcon(desc, key.userId);
156         if (icon != null) {
157             /* isInstantApp */
158             entry.icon = getBitmapInfo(
159                     new BitmapDrawable(mContext.getResources(), icon),
160                     key.userId,
161                     desc.getPrimaryColor(),
162                     false /* isInstantApp */).newIcon(mContext);
163         } else {
164             activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
165                     key.getComponent(), key.userId);
166             if (activityInfo != null) {
167                 BitmapInfo bitmapInfo = getBitmapInfo(
168                         mIconProvider.getIcon(activityInfo),
169                         key.userId,
170                         desc.getPrimaryColor(),
171                         activityInfo.applicationInfo.isInstantApp());
172                 entry.icon = bitmapInfo.newIcon(mContext);
173             } else {
174                 entry.icon = getDefaultIcon(key.userId);
175             }
176         }
177 
178         // Loading content descriptions if accessibility or low RAM recents is enabled.
179         if (GO_LOW_RAM_RECENTS_ENABLED || mAccessibilityManager.isEnabled()) {
180             // Skip loading the content description if the activity no longer exists
181             if (activityInfo == null) {
182                 activityInfo = PackageManagerWrapper.getInstance().getActivityInfo(
183                         key.getComponent(), key.userId);
184             }
185             if (activityInfo != null) {
186                 entry.contentDescription = getBadgedContentDescription(
187                         activityInfo, task.key.userId, task.taskDescription);
188             }
189         }
190 
191         mIconCache.put(task.key, entry);
192         return entry;
193     }
194 
getBadgedContentDescription(ActivityInfo info, int userId, TaskDescription td)195     private String getBadgedContentDescription(ActivityInfo info, int userId, TaskDescription td) {
196         PackageManager pm = mContext.getPackageManager();
197         String taskLabel = td == null ? null : Utilities.trim(td.getLabel());
198         if (TextUtils.isEmpty(taskLabel)) {
199             taskLabel = Utilities.trim(info.loadLabel(pm));
200         }
201 
202         String applicationLabel = Utilities.trim(info.applicationInfo.loadLabel(pm));
203         String badgedApplicationLabel = userId != UserHandle.myUserId()
204                 ? pm.getUserBadgedLabel(applicationLabel, UserHandle.of(userId)).toString()
205                 : applicationLabel;
206         return applicationLabel.equals(taskLabel)
207                 ? badgedApplicationLabel : badgedApplicationLabel + " " + taskLabel;
208     }
209 
210     @WorkerThread
getDefaultIcon(int userId)211     private Drawable getDefaultIcon(int userId) {
212         synchronized (mDefaultIcons) {
213             BitmapInfo info = mDefaultIcons.get(userId);
214             if (info == null) {
215                 try (BaseIconFactory bif = getIconFactory()) {
216                     info = bif.makeDefaultIcon(UserHandle.of(userId));
217                 }
218                 mDefaultIcons.put(userId, info);
219             }
220             return info.newIcon(mContext);
221         }
222     }
223 
224     @WorkerThread
getBitmapInfo(Drawable drawable, int userId, int primaryColor, boolean isInstantApp)225     private BitmapInfo getBitmapInfo(Drawable drawable, int userId,
226             int primaryColor, boolean isInstantApp) {
227         try (BaseIconFactory bif = getIconFactory()) {
228             bif.disableColorExtraction();
229             bif.setWrapperBackgroundColor(primaryColor);
230 
231             // User version code O, so that the icon is always wrapped in an adaptive icon container
232             return bif.createBadgedIconBitmap(drawable, UserHandle.of(userId),
233                     Build.VERSION_CODES.O, isInstantApp);
234         }
235     }
236 
237     @WorkerThread
getIconFactory()238     private BaseIconFactory getIconFactory() {
239         if (mIconFactory == null) {
240             mIconFactory = new BaseIconFactory(mContext,
241                     DisplayController.INSTANCE.get(mContext).getInfo().densityDpi,
242                     mContext.getResources().getDimensionPixelSize(R.dimen.taskbar_icon_size));
243         }
244         return mIconFactory;
245     }
246 
247     @WorkerThread
resetFactory()248     private void resetFactory() {
249         mIconFactory = null;
250         mIconCache.evictAll();
251     }
252 
253     private static class TaskCacheEntry {
254         public Drawable icon;
255         public String contentDescription = "";
256     }
257 }
258