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<Bitmap> 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<Bitmap> 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 Bitmap cachedThumbnail = 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 (cachedThumbnail == null) {
194 if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
195 if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
196 cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
198 if (cachedThumbnail == null) {
199 cachedThumbnail = mDefaultThumbnail;
201 // When svelte, we trim the memory to just the visible thumbnails when
202 // leaving, so don't thrash the cache as the user scrolls (just load
203 // them from scratch each time)
204 if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE) {
205 mThumbnailCache.put(t.key, cachedThumbnail);
209 // Notify that the task data has changed
210 final Drawable newIcon = cachedIcon;
211 final Bitmap newThumbnail = cachedThumbnail == mDefaultThumbnail
212 ? null : cachedThumbnail;
213 mMainThreadHandler.post(new Runnable() {
216 t.notifyTaskDataLoaded(newThumbnail, newIcon);
223 // If there are no other items in the list, then just wait until something is added
224 if (!mCancelled && mLoadQueue.isEmpty()) {
225 synchronized(mLoadQueue) {
227 mWaitingOnLoadQueue = true;
229 mWaitingOnLoadQueue = false;
230 } catch (InterruptedException ie) {
231 ie.printStackTrace();
241 * Recents task loader
243 public class RecentsTaskLoader {
245 private static final String TAG = "RecentsTaskLoader";
246 private static final boolean DEBUG = false;
248 // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
249 // for many tasks, which we use to get the activity labels and icons. Unlike the other caches
250 // below, this is per-package so we can't invalidate the items in the cache based on the last
251 // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
252 // package in the cache has been updated, so that we may remove it.
253 private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
254 private final TaskKeyLruCache<Drawable> mIconCache;
255 private final TaskKeyLruCache<Bitmap> mThumbnailCache;
256 private final TaskKeyLruCache<String> mActivityLabelCache;
257 private final TaskKeyLruCache<String> mContentDescriptionCache;
258 private final TaskResourceLoadQueue mLoadQueue;
259 private final BackgroundTaskLoader mLoader;
261 private final int mMaxThumbnailCacheSize;
262 private final int mMaxIconCacheSize;
263 private int mNumVisibleTasksLoaded;
264 private int mNumVisibleThumbnailsLoaded;
266 int mDefaultTaskBarBackgroundColor;
267 int mDefaultTaskViewBackgroundColor;
268 BitmapDrawable mDefaultIcon;
269 Bitmap mDefaultThumbnail;
271 private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
272 new TaskKeyLruCache.EvictionCallback() {
274 public void onEntryEvicted(Task.TaskKey key) {
275 mActivityInfoCache.remove(key.getComponent());
279 public RecentsTaskLoader(Context context) {
280 Resources res = context.getResources();
281 mDefaultTaskBarBackgroundColor =
282 context.getColor(R.color.recents_task_bar_default_background_color);
283 mDefaultTaskViewBackgroundColor =
284 context.getColor(R.color.recents_task_view_default_background_color);
285 mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
286 mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
287 int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
289 int thumbnailCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
290 mMaxThumbnailCacheSize;
292 // Create the default assets
293 Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
295 mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
296 mDefaultThumbnail.setHasAlpha(false);
297 mDefaultThumbnail.eraseColor(0xFFffffff);
298 mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
300 // Initialize the proxy, cache and loaders
301 int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
302 mLoadQueue = new TaskResourceLoadQueue();
303 mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
304 mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
305 mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
306 mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
307 mClearActivityInfoOnEviction);
308 mActivityInfoCache = new LruCache(numRecentTasks);
309 mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mThumbnailCache,
310 mDefaultThumbnail, mDefaultIcon);
313 /** Returns the size of the app icon cache. */
314 public int getIconCacheSize() {
315 return mMaxIconCacheSize;
318 /** Returns the size of the thumbnail cache. */
319 public int getThumbnailCacheSize() {
320 return mMaxThumbnailCacheSize;
323 /** Creates a new plan for loading the recent tasks. */
324 public RecentsTaskLoadPlan createLoadPlan(Context context) {
325 RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
329 /** Preloads recents tasks using the specified plan to store the output. */
330 public void preloadTasks(RecentsTaskLoadPlan plan, int topTaskId, boolean isTopTaskHome) {
331 plan.preloadPlan(this, topTaskId, isTopTaskHome);
334 /** Begins loading the heavy task data according to the specified options. */
335 public void loadTasks(Context context, RecentsTaskLoadPlan plan,
336 RecentsTaskLoadPlan.Options opts) {
338 throw new RuntimeException("Requires load options");
340 plan.executePlan(opts, this, mLoadQueue);
341 if (!opts.onlyLoadForCache) {
342 mNumVisibleTasksLoaded = opts.numVisibleTasks;
343 mNumVisibleThumbnailsLoaded = opts.numVisibleTaskThumbnails;
346 mLoader.start(context);
351 * Acquires the task resource data directly from the cache, loading if necessary.
353 * @param fetchAndInvalidateThumbnails If set, will try loading thumbnails, invalidating them
354 * in the cache and loading if necessary. Otherwise, do not
355 * load the thumbnail unless the icon also has to be loaded.
357 public void loadTaskData(Task t, boolean fetchAndInvalidateThumbnails) {
358 Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
359 Bitmap thumbnail = mDefaultThumbnail;
360 if (fetchAndInvalidateThumbnails) {
361 thumbnail = mThumbnailCache.getAndInvalidateIfModified(t.key);
364 // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
365 // use the default assets in their place until they load
366 boolean requiresLoad = (icon == null) || (thumbnail == null);
367 icon = icon != null ? icon : mDefaultIcon;
369 mLoadQueue.addTask(t);
371 t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, icon);
374 /** Releases the task resource data back into the pool. */
375 public void unloadTaskData(Task t) {
376 mLoadQueue.removeTask(t);
377 t.notifyTaskDataUnloaded(null, mDefaultIcon);
380 /** Completely removes the resource data from the pool. */
381 public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
382 mLoadQueue.removeTask(t);
383 mThumbnailCache.remove(t.key);
384 mIconCache.remove(t.key);
385 mActivityLabelCache.remove(t.key);
386 mContentDescriptionCache.remove(t.key);
387 if (notifyTaskDataUnloaded) {
388 t.notifyTaskDataUnloaded(null, mDefaultIcon);
393 * Handles signals from the system, trimming memory when requested to prevent us from running
396 public void onTrimMemory(int level) {
397 RecentsConfiguration config = Recents.getConfiguration();
399 case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
400 // Stop the loader immediately when the UI is no longer visible
402 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
403 mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
404 mMaxThumbnailCacheSize / 2));
405 } else if (config.svelteLevel == RecentsConfiguration.SVELTE_LIMIT_CACHE) {
406 mThumbnailCache.trimToSize(mNumVisibleThumbnailsLoaded);
407 } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) {
408 mThumbnailCache.evictAll();
410 mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
411 mMaxIconCacheSize / 2));
413 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
414 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
415 // We are leaving recents, so trim the data a bit
416 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
417 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
418 mActivityInfoCache.trimToSize(Math.max(1,
419 ActivityManager.getMaxRecentTasksStatic() / 2));
421 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
422 case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
423 // We are going to be low on memory
424 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
425 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
426 mActivityInfoCache.trimToSize(Math.max(1,
427 ActivityManager.getMaxRecentTasksStatic() / 4));
429 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
430 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
431 // We are low on memory, so release everything
432 mThumbnailCache.evictAll();
433 mIconCache.evictAll();
434 mActivityInfoCache.evictAll();
435 // The cache is small, only clear the label cache when we are critical
436 mActivityLabelCache.evictAll();
437 mContentDescriptionCache.evictAll();
445 * Returns the cached task label if the task key is not expired, updating the cache if it is.
447 String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
448 SystemServicesProxy ssp = Recents.getSystemServices();
450 // Return the task description label if it exists
451 if (td != null && td.getLabel() != null) {
452 return td.getLabel();
454 // Return the cached activity label if it exists
455 String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
459 // All short paths failed, load the label from the activity info and cache it
460 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
461 if (activityInfo != null) {
462 label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
463 mActivityLabelCache.put(taskKey, label);
466 // If the activity info does not exist or fails to load, return an empty label for now,
467 // but do not cache it
472 * Returns the cached task content description if the task key is not expired, updating the
475 String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
476 SystemServicesProxy ssp = Recents.getSystemServices();
478 // Return the cached content description if it exists
479 String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
484 // All short paths failed, load the label from the activity info and cache it
485 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
486 if (activityInfo != null) {
487 label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, res);
488 mContentDescriptionCache.put(taskKey, label);
491 // If the content description does not exist, return an empty label for now, but do not
497 * Returns the cached task icon if the task key is not expired, updating the cache if it is.
499 Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
500 Resources res, boolean loadIfNotCached) {
501 SystemServicesProxy ssp = Recents.getSystemServices();
503 // Return the cached activity icon if it exists
504 Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
509 if (loadIfNotCached) {
510 // Return and cache the task description icon if it exists
511 icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res);
513 mIconCache.put(taskKey, icon);
517 // Load the icon from the activity info and cache it
518 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
519 if (activityInfo != null) {
520 icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId);
522 mIconCache.put(taskKey, icon);
527 // We couldn't load any icon
532 * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
534 Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
535 SystemServicesProxy ssp = Recents.getSystemServices();
537 // Return the cached thumbnail if it exists
538 Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
539 if (thumbnail != null) {
543 if (loadIfNotCached) {
544 RecentsConfiguration config = Recents.getConfiguration();
545 if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
546 // Load the thumbnail from the system
547 thumbnail = ssp.getTaskThumbnail(taskKey.id);
548 if (thumbnail != null) {
549 mThumbnailCache.put(taskKey, thumbnail);
554 // We couldn't load any thumbnail
559 * Returns the task's primary color if possible, defaulting to the default color if there is
560 * no specified primary color.
562 int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
563 if (td != null && td.getPrimaryColor() != 0) {
564 return td.getPrimaryColor();
566 return mDefaultTaskBarBackgroundColor;
570 * Returns the task's background color if possible.
572 int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
573 if (td != null && td.getBackgroundColor() != 0) {
574 return td.getBackgroundColor();
576 return mDefaultTaskViewBackgroundColor;
580 * Returns the activity info for the given task key, retrieving one from the system if the
581 * task key is expired.
583 ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
584 SystemServicesProxy ssp = Recents.getSystemServices();
585 ComponentName cn = taskKey.getComponent();
586 ActivityInfo activityInfo = mActivityInfoCache.get(cn);
587 if (activityInfo == null) {
588 activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
589 if (cn == null || activityInfo == null) {
590 Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
594 mActivityInfoCache.put(cn, activityInfo);
600 * Stops the task loader and clears all queued, pending task loads.
602 private void stopLoader() {
604 mLoadQueue.clearTasks();
607 /**** Event Bus Events ****/
609 public final void onBusEvent(PackagesChangedEvent event) {
610 // Remove all the cached activity infos for this package. The other caches do not need to
611 // be pruned at this time, as the TaskKey expiration checks will flush them next time their
612 // cached contents are requested
613 Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
614 for (ComponentName cn : activityInfoCache.keySet()) {
615 if (cn.getPackageName().equals(event.packageName)) {
617 Log.d(TAG, "Removing activity info from cache: " + cn);
619 mActivityInfoCache.remove(cn);