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