OSDN Git Service

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