2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.recents.model;
19 import android.app.ActivityManager;
20 import android.content.ComponentCallbacks2;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.pm.ActivityInfo;
24 import android.content.res.Resources;
25 import android.graphics.Bitmap;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.graphics.drawable.Drawable;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.util.Log;
31 import android.util.LruCache;
33 import com.android.systemui.R;
34 import com.android.systemui.recents.Recents;
35 import com.android.systemui.recents.RecentsConfiguration;
36 import com.android.systemui.recents.RecentsDebugFlags;
37 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
38 import com.android.systemui.recents.misc.SystemServicesProxy;
41 import java.util.concurrent.ConcurrentLinkedQueue;
47 class TaskResourceLoadQueue {
48 ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
50 /** Adds a new task to the load queue */
51 void addTask(Task t) {
52 if (!mQueue.contains(t)) {
61 * Retrieves the next task from the load queue, as well as whether we want that task to be
68 /** Removes a task from the load queue */
69 void removeTask(Task t) {
73 /** Clears all the tasks from the load queue */
78 /** Returns whether the load queue is empty */
80 return mQueue.isEmpty();
85 * Task resource loader
87 class BackgroundTaskLoader implements Runnable {
88 static String TAG = "TaskResourceLoader";
89 static boolean DEBUG = false;
92 HandlerThread mLoadThread;
93 Handler mLoadThreadHandler;
94 Handler mMainThreadHandler;
96 TaskResourceLoadQueue mLoadQueue;
97 TaskKeyLruCache<Drawable> mIconCache;
98 TaskKeyLruCache<ThumbnailData> mThumbnailCache;
99 Bitmap mDefaultThumbnail;
100 BitmapDrawable mDefaultIcon;
103 boolean mWaitingOnLoadQueue;
105 /** Constructor, creates a new loading thread that loads task resources in the background */
106 public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
107 TaskKeyLruCache<Drawable> iconCache, TaskKeyLruCache<ThumbnailData> thumbnailCache,
108 Bitmap defaultThumbnail, BitmapDrawable defaultIcon) {
109 mLoadQueue = loadQueue;
110 mIconCache = iconCache;
111 mThumbnailCache = thumbnailCache;
112 mDefaultThumbnail = defaultThumbnail;
113 mDefaultIcon = defaultIcon;
114 mMainThreadHandler = new Handler();
115 mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
116 android.os.Process.THREAD_PRIORITY_BACKGROUND);
118 mLoadThreadHandler = new Handler(mLoadThread.getLooper());
119 mLoadThreadHandler.post(this);
122 /** Restarts the loader thread */
123 void start(Context context) {
126 // Notify the load thread to start loading
127 synchronized(mLoadThread) {
128 mLoadThread.notifyAll();
132 /** Requests the loader thread to stop after the current iteration */
134 // Mark as cancelled for the thread to pick up
136 // If we are waiting for the load queue for more tasks, then we can just reset the
137 // Context now, since nothing is using it
138 if (mWaitingOnLoadQueue) {
147 // We have to unset the context here, since the background thread may be using it
148 // when we call stop()
150 // If we are cancelled, then wait until we are started again
151 synchronized(mLoadThread) {
154 } catch (InterruptedException ie) {
155 ie.printStackTrace();
159 RecentsConfiguration config = Recents.getConfiguration();
160 SystemServicesProxy ssp = Recents.getSystemServices();
161 // If we've stopped the loader, then fall through to the above logic to wait on
164 // Load the next item from the queue
165 final Task t = mLoadQueue.nextTask();
167 Drawable cachedIcon = mIconCache.get(t.key);
168 ThumbnailData cachedThumbnailData = mThumbnailCache.get(t.key);
170 // Load the icon if it is stale or we haven't cached one yet
171 if (cachedIcon == null) {
172 cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription,
173 t.key.userId, mContext.getResources());
175 if (cachedIcon == null) {
176 ActivityInfo info = ssp.getActivityInfo(
177 t.key.getComponent(), t.key.userId);
179 if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
180 cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId);
184 if (cachedIcon == null) {
185 cachedIcon = mDefaultIcon;
188 // At this point, even if we can't load the icon, we will set the
190 mIconCache.put(t.key, cachedIcon);
192 // Load the thumbnail if it is stale or we haven't cached one yet
193 if (cachedThumbnailData == null) {
194 if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
195 if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
196 cachedThumbnailData = ssp.getTaskThumbnail(t.key.id);
199 if (cachedThumbnailData.thumbnail == null) {
200 cachedThumbnailData.thumbnail = mDefaultThumbnail;
203 // When svelte, we trim the memory to just the visible thumbnails when
204 // leaving, so don't thrash the cache as the user scrolls (just load
205 // them from scratch each time)
206 if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE) {
207 mThumbnailCache.put(t.key, cachedThumbnailData);
211 // Notify that the task data has changed
212 final Drawable newIcon = cachedIcon;
213 final ThumbnailData newThumbnailData = cachedThumbnailData;
214 mMainThreadHandler.post(new Runnable() {
217 t.notifyTaskDataLoaded(newThumbnailData.thumbnail, newIcon,
218 newThumbnailData.thumbnailInfo);
225 // If there are no other items in the list, then just wait until something is added
226 if (!mCancelled && mLoadQueue.isEmpty()) {
227 synchronized(mLoadQueue) {
229 mWaitingOnLoadQueue = true;
231 mWaitingOnLoadQueue = false;
232 } catch (InterruptedException ie) {
233 ie.printStackTrace();
243 * Recents task loader
245 public class RecentsTaskLoader {
247 private static final String TAG = "RecentsTaskLoader";
248 private static final boolean DEBUG = false;
250 // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
251 // for many tasks, which we use to get the activity labels and icons. Unlike the other caches
252 // below, this is per-package so we can't invalidate the items in the cache based on the last
253 // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
254 // package in the cache has been updated, so that we may remove it.
255 private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
256 private final TaskKeyLruCache<Drawable> mIconCache;
257 private final TaskKeyLruCache<ThumbnailData> mThumbnailCache;
258 private final TaskKeyLruCache<String> mActivityLabelCache;
259 private final TaskKeyLruCache<String> mContentDescriptionCache;
260 private final TaskResourceLoadQueue mLoadQueue;
261 private final BackgroundTaskLoader mLoader;
263 private final int mMaxThumbnailCacheSize;
264 private final int mMaxIconCacheSize;
265 private int mNumVisibleTasksLoaded;
266 private int mNumVisibleThumbnailsLoaded;
268 int mDefaultTaskBarBackgroundColor;
269 int mDefaultTaskViewBackgroundColor;
270 BitmapDrawable mDefaultIcon;
271 Bitmap mDefaultThumbnail;
273 private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
274 new TaskKeyLruCache.EvictionCallback() {
276 public void onEntryEvicted(Task.TaskKey key) {
277 mActivityInfoCache.remove(key.getComponent());
281 public RecentsTaskLoader(Context context) {
282 Resources res = context.getResources();
283 mDefaultTaskBarBackgroundColor =
284 context.getColor(R.color.recents_task_bar_default_background_color);
285 mDefaultTaskViewBackgroundColor =
286 context.getColor(R.color.recents_task_view_default_background_color);
287 mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
288 mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
289 int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
291 int thumbnailCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
292 mMaxThumbnailCacheSize;
294 // Create the default assets
295 Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
297 mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
298 mDefaultThumbnail.setHasAlpha(false);
299 mDefaultThumbnail.eraseColor(0xFFffffff);
300 mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
302 // Initialize the proxy, cache and loaders
303 int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
304 mLoadQueue = new TaskResourceLoadQueue();
305 mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
306 mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
307 mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
308 mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
309 mClearActivityInfoOnEviction);
310 mActivityInfoCache = new LruCache(numRecentTasks);
311 mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mThumbnailCache,
312 mDefaultThumbnail, mDefaultIcon);
315 /** Returns the size of the app icon cache. */
316 public int getIconCacheSize() {
317 return mMaxIconCacheSize;
320 /** Returns the size of the thumbnail cache. */
321 public int getThumbnailCacheSize() {
322 return mMaxThumbnailCacheSize;
325 /** Creates a new plan for loading the recent tasks. */
326 public RecentsTaskLoadPlan createLoadPlan(Context context) {
327 RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
331 /** Preloads recents tasks using the specified plan to store the output. */
332 public void preloadTasks(RecentsTaskLoadPlan plan, int topTaskId, boolean isTopTaskHome) {
333 plan.preloadPlan(this, topTaskId, isTopTaskHome);
336 /** Begins loading the heavy task data according to the specified options. */
337 public void loadTasks(Context context, RecentsTaskLoadPlan plan,
338 RecentsTaskLoadPlan.Options opts) {
340 throw new RuntimeException("Requires load options");
342 plan.executePlan(opts, this, mLoadQueue);
343 if (!opts.onlyLoadForCache) {
344 mNumVisibleTasksLoaded = opts.numVisibleTasks;
345 mNumVisibleThumbnailsLoaded = opts.numVisibleTaskThumbnails;
348 mLoader.start(context);
353 * Acquires the task resource data directly from the cache, loading if necessary.
355 * @param fetchAndInvalidateThumbnails If set, will try loading thumbnails, invalidating them
356 * in the cache and loading if necessary. Otherwise, do not
357 * load the thumbnail unless the icon also has to be loaded.
359 public void loadTaskData(Task t, boolean fetchAndInvalidateThumbnails) {
360 Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
361 Bitmap thumbnail = null;
362 ActivityManager.TaskThumbnailInfo thumbnailInfo = null;
363 if (fetchAndInvalidateThumbnails) {
364 ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(t.key);
365 if (thumbnailData != null) {
366 thumbnail = thumbnailData.thumbnail;
367 thumbnailInfo = thumbnailData.thumbnailInfo;
370 thumbnail = mDefaultThumbnail;
373 // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
374 // use the default assets in their place until they load
375 boolean requiresLoad = (icon == null) || (thumbnail == null);
376 icon = icon != null ? icon : mDefaultIcon;
378 mLoadQueue.addTask(t);
380 t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, icon,
384 /** Releases the task resource data back into the pool. */
385 public void unloadTaskData(Task t) {
386 mLoadQueue.removeTask(t);
387 t.notifyTaskDataUnloaded(null, mDefaultIcon);
390 /** Completely removes the resource data from the pool. */
391 public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
392 mLoadQueue.removeTask(t);
393 mThumbnailCache.remove(t.key);
394 mIconCache.remove(t.key);
395 mActivityLabelCache.remove(t.key);
396 mContentDescriptionCache.remove(t.key);
397 if (notifyTaskDataUnloaded) {
398 t.notifyTaskDataUnloaded(null, mDefaultIcon);
403 * Handles signals from the system, trimming memory when requested to prevent us from running
406 public void onTrimMemory(int level) {
407 RecentsConfiguration config = Recents.getConfiguration();
409 case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
410 // Stop the loader immediately when the UI is no longer visible
412 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
413 mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
414 mMaxThumbnailCacheSize / 2));
415 } else if (config.svelteLevel == RecentsConfiguration.SVELTE_LIMIT_CACHE) {
416 mThumbnailCache.trimToSize(mNumVisibleThumbnailsLoaded);
417 } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) {
418 mThumbnailCache.evictAll();
420 mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
421 mMaxIconCacheSize / 2));
423 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
424 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
425 // We are leaving recents, so trim the data a bit
426 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
427 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
428 mActivityInfoCache.trimToSize(Math.max(1,
429 ActivityManager.getMaxRecentTasksStatic() / 2));
431 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
432 case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
433 // We are going to be low on memory
434 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
435 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
436 mActivityInfoCache.trimToSize(Math.max(1,
437 ActivityManager.getMaxRecentTasksStatic() / 4));
439 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
440 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
441 // We are low on memory, so release everything
442 mThumbnailCache.evictAll();
443 mIconCache.evictAll();
444 mActivityInfoCache.evictAll();
445 // The cache is small, only clear the label cache when we are critical
446 mActivityLabelCache.evictAll();
447 mContentDescriptionCache.evictAll();
455 * Returns the cached task label if the task key is not expired, updating the cache if it is.
457 String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
458 SystemServicesProxy ssp = Recents.getSystemServices();
460 // Return the task description label if it exists
461 if (td != null && td.getLabel() != null) {
462 return td.getLabel();
464 // Return the cached activity label if it exists
465 String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
469 // All short paths failed, load the label from the activity info and cache it
470 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
471 if (activityInfo != null) {
472 label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
473 mActivityLabelCache.put(taskKey, label);
476 // If the activity info does not exist or fails to load, return an empty label for now,
477 // but do not cache it
482 * Returns the cached task content description if the task key is not expired, updating the
485 String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
486 SystemServicesProxy ssp = Recents.getSystemServices();
488 // Return the cached content description if it exists
489 String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
494 // All short paths failed, load the label from the activity info and cache it
495 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
496 if (activityInfo != null) {
497 label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, res);
498 mContentDescriptionCache.put(taskKey, label);
501 // If the content description does not exist, return an empty label for now, but do not
507 * Returns the cached task icon if the task key is not expired, updating the cache if it is.
509 Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
510 Resources res, boolean loadIfNotCached) {
511 SystemServicesProxy ssp = Recents.getSystemServices();
513 // Return the cached activity icon if it exists
514 Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
519 if (loadIfNotCached) {
520 // Return and cache the task description icon if it exists
521 icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res);
523 mIconCache.put(taskKey, icon);
527 // Load the icon from the activity info and cache it
528 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
529 if (activityInfo != null) {
530 icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId);
532 mIconCache.put(taskKey, icon);
537 // We couldn't load any icon
542 * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
544 Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
545 SystemServicesProxy ssp = Recents.getSystemServices();
547 // Return the cached thumbnail if it exists
548 ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(taskKey);
549 if (thumbnailData != null) {
550 return thumbnailData.thumbnail;
553 if (loadIfNotCached) {
554 RecentsConfiguration config = Recents.getConfiguration();
555 if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
556 // Load the thumbnail from the system
557 thumbnailData = ssp.getTaskThumbnail(taskKey.id);
558 if (thumbnailData.thumbnail != null) {
559 mThumbnailCache.put(taskKey, thumbnailData);
560 return thumbnailData.thumbnail;
564 // We couldn't load any thumbnail
569 * Returns the task's primary color if possible, defaulting to the default color if there is
570 * no specified primary color.
572 int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
573 if (td != null && td.getPrimaryColor() != 0) {
574 return td.getPrimaryColor();
576 return mDefaultTaskBarBackgroundColor;
580 * Returns the task's background color if possible.
582 int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
583 if (td != null && td.getBackgroundColor() != 0) {
584 return td.getBackgroundColor();
586 return mDefaultTaskViewBackgroundColor;
590 * Returns the activity info for the given task key, retrieving one from the system if the
591 * task key is expired.
593 ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
594 SystemServicesProxy ssp = Recents.getSystemServices();
595 ComponentName cn = taskKey.getComponent();
596 ActivityInfo activityInfo = mActivityInfoCache.get(cn);
597 if (activityInfo == null) {
598 activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
599 if (cn == null || activityInfo == null) {
600 Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
604 mActivityInfoCache.put(cn, activityInfo);
610 * Stops the task loader and clears all queued, pending task loads.
612 private void stopLoader() {
614 mLoadQueue.clearTasks();
617 /**** Event Bus Events ****/
619 public final void onBusEvent(PackagesChangedEvent event) {
620 // Remove all the cached activity infos for this package. The other caches do not need to
621 // be pruned at this time, as the TaskKey expiration checks will flush them next time their
622 // cached contents are requested
623 Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
624 for (ComponentName cn : activityInfoCache.keySet()) {
625 if (cn.getPackageName().equals(event.packageName)) {
627 Log.d(TAG, "Removing activity info from cache: " + cn);
629 mActivityInfoCache.remove(cn);