• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 
17 package com.android.systemui.recents.model;
18 
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.os.Looper;
31 import android.os.Trace;
32 import android.util.Log;
33 import android.util.LruCache;
34 
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.systemui.R;
37 import com.android.systemui.recents.Recents;
38 import com.android.systemui.recents.RecentsConfiguration;
39 import com.android.systemui.recents.RecentsDebugFlags;
40 import com.android.systemui.recents.events.activity.PackagesChangedEvent;
41 import com.android.systemui.recents.misc.SystemServicesProxy;
42 
43 import java.io.PrintWriter;
44 import java.util.Map;
45 import java.util.concurrent.ConcurrentLinkedQueue;
46 
47 
48 /**
49  * A Task load queue
50  */
51 class TaskResourceLoadQueue {
52 
53     ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
54 
55     /** Adds a new task to the load queue */
addTask(Task t)56     void addTask(Task t) {
57         if (!mQueue.contains(t)) {
58             mQueue.add(t);
59         }
60         synchronized(this) {
61             notifyAll();
62         }
63     }
64 
65     /**
66      * Retrieves the next task from the load queue, as well as whether we want that task to be
67      * force reloaded.
68      */
nextTask()69     Task nextTask() {
70         return mQueue.poll();
71     }
72 
73     /** Removes a task from the load queue */
removeTask(Task t)74     void removeTask(Task t) {
75         mQueue.remove(t);
76     }
77 
78     /** Clears all the tasks from the load queue */
clearTasks()79     void clearTasks() {
80         mQueue.clear();
81     }
82 
83     /** Returns whether the load queue is empty */
isEmpty()84     boolean isEmpty() {
85         return mQueue.isEmpty();
86     }
87 }
88 
89 /**
90  * Task resource loader
91  */
92 class BackgroundTaskLoader implements Runnable {
93     static String TAG = "TaskResourceLoader";
94     static boolean DEBUG = false;
95 
96     Context mContext;
97     HandlerThread mLoadThread;
98     Handler mLoadThreadHandler;
99     Handler mMainThreadHandler;
100 
101     TaskResourceLoadQueue mLoadQueue;
102     TaskKeyLruCache<Drawable> mIconCache;
103     BitmapDrawable mDefaultIcon;
104 
105     boolean mStarted;
106     boolean mCancelled;
107     boolean mWaitingOnLoadQueue;
108 
109     private final OnIdleChangedListener mOnIdleChangedListener;
110 
111     /** Constructor, creates a new loading thread that loads task resources in the background */
BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon, OnIdleChangedListener onIdleChangedListener)112     public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue,
113             TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon,
114             OnIdleChangedListener onIdleChangedListener) {
115         mLoadQueue = loadQueue;
116         mIconCache = iconCache;
117         mDefaultIcon = defaultIcon;
118         mMainThreadHandler = new Handler();
119         mOnIdleChangedListener = onIdleChangedListener;
120         mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
121                 android.os.Process.THREAD_PRIORITY_BACKGROUND);
122         mLoadThread.start();
123         mLoadThreadHandler = new Handler(mLoadThread.getLooper());
124     }
125 
126     /** Restarts the loader thread */
start(Context context)127     void start(Context context) {
128         mContext = context;
129         mCancelled = false;
130         if (!mStarted) {
131             // Start loading on the load thread
132             mStarted = true;
133             mLoadThreadHandler.post(this);
134         } else {
135             // Notify the load thread to start loading again
136             synchronized (mLoadThread) {
137                 mLoadThread.notifyAll();
138             }
139         }
140     }
141 
142     /** Requests the loader thread to stop after the current iteration */
stop()143     void stop() {
144         // Mark as cancelled for the thread to pick up
145         mCancelled = true;
146         // If we are waiting for the load queue for more tasks, then we can just reset the
147         // Context now, since nothing is using it
148         if (mWaitingOnLoadQueue) {
149             mContext = null;
150         }
151     }
152 
153     @Override
run()154     public void run() {
155         while (true) {
156             if (mCancelled) {
157                 // We have to unset the context here, since the background thread may be using it
158                 // when we call stop()
159                 mContext = null;
160                 // If we are cancelled, then wait until we are started again
161                 synchronized(mLoadThread) {
162                     try {
163                         mLoadThread.wait();
164                     } catch (InterruptedException ie) {
165                         ie.printStackTrace();
166                     }
167                 }
168             } else {
169                 SystemServicesProxy ssp = Recents.getSystemServices();
170                 // If we've stopped the loader, then fall through to the above logic to wait on
171                 // the load thread
172                 if (ssp != null) {
173                     processLoadQueueItem(ssp);
174                 }
175 
176                 // If there are no other items in the list, then just wait until something is added
177                 if (!mCancelled && mLoadQueue.isEmpty()) {
178                     synchronized(mLoadQueue) {
179                         try {
180                             mWaitingOnLoadQueue = true;
181                             mMainThreadHandler.post(
182                                     () -> mOnIdleChangedListener.onIdleChanged(true));
183                             mLoadQueue.wait();
184                             mMainThreadHandler.post(
185                                     () -> mOnIdleChangedListener.onIdleChanged(false));
186                             mWaitingOnLoadQueue = false;
187                         } catch (InterruptedException ie) {
188                             ie.printStackTrace();
189                         }
190                     }
191                 }
192             }
193         }
194     }
195 
196     /**
197      * This needs to be in a separate method to work around an surprising interpreter behavior:
198      * The register will keep the local reference to cachedThumbnailData even if it falls out of
199      * scope. Putting it into a method fixes this issue.
200      */
processLoadQueueItem(SystemServicesProxy ssp)201     private void processLoadQueueItem(SystemServicesProxy ssp) {
202         // Load the next item from the queue
203         final Task t = mLoadQueue.nextTask();
204         if (t != null) {
205             Drawable cachedIcon = mIconCache.get(t.key);
206 
207             // Load the icon if it is stale or we haven't cached one yet
208             if (cachedIcon == null) {
209                 cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription,
210                         t.key.userId, mContext.getResources());
211 
212                 if (cachedIcon == null) {
213                     ActivityInfo info = ssp.getActivityInfo(
214                             t.key.getComponent(), t.key.userId);
215                     if (info != null) {
216                         if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
217                         cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId);
218                     }
219                 }
220 
221                 if (cachedIcon == null) {
222                     cachedIcon = mDefaultIcon;
223                 }
224 
225                 // At this point, even if we can't load the icon, we will set the
226                 // default icon.
227                 mIconCache.put(t.key, cachedIcon);
228             }
229 
230             if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
231             final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id,
232                     true /* reducedResolution */);
233 
234             if (!mCancelled) {
235                 // Notify that the task data has changed
236                 final Drawable finalIcon = cachedIcon;
237                 mMainThreadHandler.post(
238                         () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon));
239             }
240         }
241     }
242 
243     interface OnIdleChangedListener {
onIdleChanged(boolean idle)244         void onIdleChanged(boolean idle);
245     }
246 }
247 
248 /**
249  * Recents task loader
250  */
251 public class RecentsTaskLoader {
252 
253     private static final String TAG = "RecentsTaskLoader";
254     private static final boolean DEBUG = false;
255 
256     // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
257     // for many tasks, which we use to get the activity labels and icons.  Unlike the other caches
258     // below, this is per-package so we can't invalidate the items in the cache based on the last
259     // active time.  Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a
260     // package in the cache has been updated, so that we may remove it.
261     private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
262     private final TaskKeyLruCache<Drawable> mIconCache;
263     private final TaskKeyLruCache<String> mActivityLabelCache;
264     private final TaskKeyLruCache<String> mContentDescriptionCache;
265     private final TaskResourceLoadQueue mLoadQueue;
266     private final BackgroundTaskLoader mLoader;
267     private final HighResThumbnailLoader mHighResThumbnailLoader;
268     @GuardedBy("this")
269     private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>();
270     @GuardedBy("this")
271     private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>();
272     private final int mMaxThumbnailCacheSize;
273     private final int mMaxIconCacheSize;
274     private int mNumVisibleTasksLoaded;
275 
276     int mDefaultTaskBarBackgroundColor;
277     int mDefaultTaskViewBackgroundColor;
278     BitmapDrawable mDefaultIcon;
279 
280     private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction =
281             new TaskKeyLruCache.EvictionCallback() {
282         @Override
283         public void onEntryEvicted(Task.TaskKey key) {
284             if (key != null) {
285                 mActivityInfoCache.remove(key.getComponent());
286             }
287         }
288     };
289 
RecentsTaskLoader(Context context)290     public RecentsTaskLoader(Context context) {
291         Resources res = context.getResources();
292         mDefaultTaskBarBackgroundColor =
293                 context.getColor(R.color.recents_task_bar_default_background_color);
294         mDefaultTaskViewBackgroundColor =
295                 context.getColor(R.color.recents_task_view_default_background_color);
296         mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
297         mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
298         int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
299                 mMaxIconCacheSize;
300 
301         // Create the default assets
302         Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
303         icon.eraseColor(0);
304         mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
305 
306         // Initialize the proxy, cache and loaders
307         int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
308         mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(),
309                 Looper.getMainLooper(), Recents.getConfiguration().isLowRamDevice);
310         mLoadQueue = new TaskResourceLoadQueue();
311         mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
312         mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
313         mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
314                 mClearActivityInfoOnEviction);
315         mActivityInfoCache = new LruCache(numRecentTasks);
316         mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon,
317                 mHighResThumbnailLoader::setTaskLoadQueueIdle);
318     }
319 
320     /** Returns the size of the app icon cache. */
getIconCacheSize()321     public int getIconCacheSize() {
322         return mMaxIconCacheSize;
323     }
324 
325     /** Returns the size of the thumbnail cache. */
getThumbnailCacheSize()326     public int getThumbnailCacheSize() {
327         return mMaxThumbnailCacheSize;
328     }
329 
getHighResThumbnailLoader()330     public HighResThumbnailLoader getHighResThumbnailLoader() {
331         return mHighResThumbnailLoader;
332     }
333 
334     /** Creates a new plan for loading the recent tasks. */
createLoadPlan(Context context)335     public RecentsTaskLoadPlan createLoadPlan(Context context) {
336         RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
337         return plan;
338     }
339 
340     /** Preloads raw recents tasks using the specified plan to store the output. */
preloadRawTasks(RecentsTaskLoadPlan plan, boolean includeFrontMostExcludedTask)341     public synchronized void preloadRawTasks(RecentsTaskLoadPlan plan,
342             boolean includeFrontMostExcludedTask) {
343         plan.preloadRawTasks(includeFrontMostExcludedTask);
344     }
345 
346     /** Preloads recents tasks using the specified plan to store the output. */
preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, boolean includeFrontMostExcludedTask)347     public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
348             boolean includeFrontMostExcludedTask) {
349         try {
350             Trace.beginSection("preloadPlan");
351             plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask);
352         } finally {
353             Trace.endSection();
354         }
355     }
356 
357     /** Begins loading the heavy task data according to the specified options. */
loadTasks(Context context, RecentsTaskLoadPlan plan, RecentsTaskLoadPlan.Options opts)358     public synchronized void loadTasks(Context context, RecentsTaskLoadPlan plan,
359             RecentsTaskLoadPlan.Options opts) {
360         if (opts == null) {
361             throw new RuntimeException("Requires load options");
362         }
363         if (opts.onlyLoadForCache && opts.loadThumbnails) {
364 
365             // If we are loading for the cache, we'd like to have the real cache only include the
366             // visible thumbnails. However, we also don't want to reload already cached thumbnails.
367             // Thus, we copy over the current entries into a second cache, and clear the real cache,
368             // such that the real cache only contains visible thumbnails.
369             mTempCache.copyEntries(mThumbnailCache);
370             mThumbnailCache.evictAll();
371         }
372         plan.executePlan(opts, this);
373         mTempCache.evictAll();
374         if (!opts.onlyLoadForCache) {
375             mNumVisibleTasksLoaded = opts.numVisibleTasks;
376         }
377     }
378 
379     /**
380      * Acquires the task resource data directly from the cache, loading if necessary.
381      */
loadTaskData(Task t)382     public void loadTaskData(Task t) {
383         Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
384         icon = icon != null ? icon : mDefaultIcon;
385         mLoadQueue.addTask(t);
386         t.notifyTaskDataLoaded(t.thumbnail, icon);
387     }
388 
389     /** Releases the task resource data back into the pool. */
unloadTaskData(Task t)390     public void unloadTaskData(Task t) {
391         mLoadQueue.removeTask(t);
392         t.notifyTaskDataUnloaded(mDefaultIcon);
393     }
394 
395     /** Completely removes the resource data from the pool. */
deleteTaskData(Task t, boolean notifyTaskDataUnloaded)396     public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
397         mLoadQueue.removeTask(t);
398         mIconCache.remove(t.key);
399         mActivityLabelCache.remove(t.key);
400         mContentDescriptionCache.remove(t.key);
401         if (notifyTaskDataUnloaded) {
402             t.notifyTaskDataUnloaded(mDefaultIcon);
403         }
404     }
405 
406     /**
407      * Handles signals from the system, trimming memory when requested to prevent us from running
408      * out of memory.
409      */
onTrimMemory(int level)410     public synchronized void onTrimMemory(int level) {
411         switch (level) {
412             case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
413                 // Stop the loader immediately when the UI is no longer visible
414                 stopLoader();
415                 mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
416                         mMaxIconCacheSize / 2));
417                 break;
418             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
419             case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
420                 // We are leaving recents, so trim the data a bit
421                 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
422                 mActivityInfoCache.trimToSize(Math.max(1,
423                         ActivityManager.getMaxRecentTasksStatic() / 2));
424                 break;
425             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
426             case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
427                 // We are going to be low on memory
428                 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
429                 mActivityInfoCache.trimToSize(Math.max(1,
430                         ActivityManager.getMaxRecentTasksStatic() / 4));
431                 break;
432             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
433             case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
434                 // We are low on memory, so release everything
435                 mIconCache.evictAll();
436                 mActivityInfoCache.evictAll();
437                 // The cache is small, only clear the label cache when we are critical
438                 mActivityLabelCache.evictAll();
439                 mContentDescriptionCache.evictAll();
440                 mThumbnailCache.evictAll();
441                 break;
442             default:
443                 break;
444         }
445     }
446 
447     /**
448      * Returns the cached task label if the task key is not expired, updating the cache if it is.
449      */
getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td)450     String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
451         SystemServicesProxy ssp = Recents.getSystemServices();
452 
453         // Return the task description label if it exists
454         if (td != null && td.getLabel() != null) {
455             return td.getLabel();
456         }
457         // Return the cached activity label if it exists
458         String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
459         if (label != null) {
460             return label;
461         }
462         // All short paths failed, load the label from the activity info and cache it
463         ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
464         if (activityInfo != null) {
465             label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
466             mActivityLabelCache.put(taskKey, label);
467             return label;
468         }
469         // If the activity info does not exist or fails to load, return an empty label for now,
470         // but do not cache it
471         return "";
472     }
473 
474     /**
475      * Returns the cached task content description if the task key is not expired, updating the
476      * cache if it is.
477      */
getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td, Resources res)478     String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
479             Resources res) {
480         SystemServicesProxy ssp = Recents.getSystemServices();
481 
482         // Return the cached content description if it exists
483         String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
484         if (label != null) {
485             return label;
486         }
487 
488         // All short paths failed, load the label from the activity info and cache it
489         ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
490         if (activityInfo != null) {
491             label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res);
492             if (td == null) {
493                 // Only add to the cache if the task description is null, otherwise, it is possible
494                 // for the task description to change between calls without the last active time
495                 // changing (ie. between preloading and Overview starting) which would lead to stale
496                 // content descriptions
497                 // TODO: Investigate improving this
498                 mContentDescriptionCache.put(taskKey, label);
499             }
500             return label;
501         }
502         // If the content description does not exist, return an empty label for now, but do not
503         // cache it
504         return "";
505     }
506 
507     /**
508      * Returns the cached task icon if the task key is not expired, updating the cache if it is.
509      */
getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, Resources res, boolean loadIfNotCached)510     Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
511             Resources res, boolean loadIfNotCached) {
512         SystemServicesProxy ssp = Recents.getSystemServices();
513 
514         // Return the cached activity icon if it exists
515         Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
516         if (icon != null) {
517             return icon;
518         }
519 
520         if (loadIfNotCached) {
521             // Return and cache the task description icon if it exists
522             icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res);
523             if (icon != null) {
524                 mIconCache.put(taskKey, icon);
525                 return icon;
526             }
527 
528             // Load the icon from the activity info and cache it
529             ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
530             if (activityInfo != null) {
531                 icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId);
532                 if (icon != null) {
533                     mIconCache.put(taskKey, icon);
534                     return icon;
535                 }
536             }
537         }
538         // We couldn't load any icon
539         return null;
540     }
541 
542     /**
543      * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
544      */
getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached, boolean storeInCache)545     synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached,
546             boolean storeInCache) {
547         SystemServicesProxy ssp = Recents.getSystemServices();
548 
549         ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
550         if (cached != null) {
551             return cached;
552         }
553 
554         cached = mTempCache.getAndInvalidateIfModified(taskKey);
555         if (cached != null) {
556             mThumbnailCache.put(taskKey, cached);
557             return cached;
558         }
559 
560         if (loadIfNotCached) {
561             RecentsConfiguration config = Recents.getConfiguration();
562             if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
563                 // Load the thumbnail from the system
564                 ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id,
565                         true /* reducedResolution */);
566                 if (thumbnailData.thumbnail != null) {
567                     if (storeInCache) {
568                         mThumbnailCache.put(taskKey, thumbnailData);
569                     }
570                     return thumbnailData;
571                 }
572             }
573         }
574 
575         // We couldn't load any thumbnail
576         return null;
577     }
578 
579     /**
580      * Returns the task's primary color if possible, defaulting to the default color if there is
581      * no specified primary color.
582      */
getActivityPrimaryColor(ActivityManager.TaskDescription td)583     int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
584         if (td != null && td.getPrimaryColor() != 0) {
585             return td.getPrimaryColor();
586         }
587         return mDefaultTaskBarBackgroundColor;
588     }
589 
590     /**
591      * Returns the task's background color if possible.
592      */
getActivityBackgroundColor(ActivityManager.TaskDescription td)593     int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
594         if (td != null && td.getBackgroundColor() != 0) {
595             return td.getBackgroundColor();
596         }
597         return mDefaultTaskViewBackgroundColor;
598     }
599 
600     /**
601      * Returns the activity info for the given task key, retrieving one from the system if the
602      * task key is expired.
603      */
getAndUpdateActivityInfo(Task.TaskKey taskKey)604     ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
605         SystemServicesProxy ssp = Recents.getSystemServices();
606         ComponentName cn = taskKey.getComponent();
607         ActivityInfo activityInfo = mActivityInfoCache.get(cn);
608         if (activityInfo == null) {
609             activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
610             if (cn == null || activityInfo == null) {
611                 Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
612                         activityInfo);
613                 return null;
614             }
615             mActivityInfoCache.put(cn, activityInfo);
616         }
617         return activityInfo;
618     }
619 
620     /**
621      * Starts loading tasks.
622      */
startLoader(Context ctx)623     public void startLoader(Context ctx) {
624         mLoader.start(ctx);
625     }
626 
627     /**
628      * Stops the task loader and clears all queued, pending task loads.
629      */
stopLoader()630     private void stopLoader() {
631         mLoader.stop();
632         mLoadQueue.clearTasks();
633     }
634 
635     /**** Event Bus Events ****/
636 
onBusEvent(PackagesChangedEvent event)637     public final void onBusEvent(PackagesChangedEvent event) {
638         // Remove all the cached activity infos for this package.  The other caches do not need to
639         // be pruned at this time, as the TaskKey expiration checks will flush them next time their
640         // cached contents are requested
641         Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
642         for (ComponentName cn : activityInfoCache.keySet()) {
643             if (cn.getPackageName().equals(event.packageName)) {
644                 if (DEBUG) {
645                     Log.d(TAG, "Removing activity info from cache: " + cn);
646                 }
647                 mActivityInfoCache.remove(cn);
648             }
649         }
650     }
651 
dump(String prefix, PrintWriter writer)652     public synchronized void dump(String prefix, PrintWriter writer) {
653         String innerPrefix = prefix + "  ";
654 
655         writer.print(prefix); writer.println(TAG);
656         writer.print(prefix); writer.println("Icon Cache");
657         mIconCache.dump(innerPrefix, writer);
658         writer.print(prefix); writer.println("Thumbnail Cache");
659         mThumbnailCache.dump(innerPrefix, writer);
660         writer.print(prefix); writer.println("Temp Thumbnail Cache");
661         mTempCache.dump(innerPrefix, writer);
662     }
663 }
664