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