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