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.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 */
     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      */
     69     Task nextTask() {
     70         return mQueue.poll();
     71     }
     72 
     73     /** Removes a task from the load queue */
     74     void removeTask(Task t) {
     75         mQueue.remove(t);
     76     }
     77 
     78     /** Clears all the tasks from the load queue */
     79     void clearTasks() {
     80         mQueue.clear();
     81     }
     82 
     83     /** Returns whether the load queue is empty */
     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 */
    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 */
    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 */
    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
    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      */
    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 {
    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 
    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());
    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. */
    321     public int getIconCacheSize() {
    322         return mMaxIconCacheSize;
    323     }
    324 
    325     /** Returns the size of the thumbnail cache. */
    326     public int getThumbnailCacheSize() {
    327         return mMaxThumbnailCacheSize;
    328     }
    329 
    330     public HighResThumbnailLoader getHighResThumbnailLoader() {
    331         return mHighResThumbnailLoader;
    332     }
    333 
    334     /** Creates a new plan for loading the recent tasks. */
    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. */
    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. */
    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. */
    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      */
    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. */
    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. */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    630     private void stopLoader() {
    631         mLoader.stop();
    632         mLoadQueue.clearTasks();
    633     }
    634 
    635     /**** Event Bus Events ****/
    636 
    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 
    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