RecentsTaskLoader.java revision a1ededd056d51532ab18354f17f8065ec1134535
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
17package com.android.systemui.recents.model;
18
19import android.app.ActivityManager;
20import android.content.ComponentCallbacks2;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.pm.ActivityInfo;
24import android.content.res.Resources;
25import android.graphics.Bitmap;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.drawable.Drawable;
28import android.os.Handler;
29import android.os.HandlerThread;
30import android.util.Log;
31import android.util.LruCache;
32
33import com.android.systemui.R;
34import com.android.systemui.recents.Recents;
35import com.android.systemui.recents.RecentsConfiguration;
36import com.android.systemui.recents.RecentsDebugFlags;
37import com.android.systemui.recents.events.activity.PackagesChangedEvent;
38import com.android.systemui.recents.misc.SystemServicesProxy;
39
40import java.util.Map;
41import java.util.concurrent.ConcurrentLinkedQueue;
42
43
44/**
45 * A Task load queue
46 */
47class 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 */
87class 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 */
245public 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            if (key != null) {
278                mActivityInfoCache.remove(key.getComponent());
279            }
280        }
281    };
282
283    public RecentsTaskLoader(Context context) {
284        Resources res = context.getResources();
285        mDefaultTaskBarBackgroundColor =
286                context.getColor(R.color.recents_task_bar_default_background_color);
287        mDefaultTaskViewBackgroundColor =
288                context.getColor(R.color.recents_task_view_default_background_color);
289        mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count);
290        mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count);
291        int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
292                mMaxIconCacheSize;
293        int thumbnailCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 :
294                mMaxThumbnailCacheSize;
295
296        // Create the default assets
297        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
298        icon.eraseColor(0);
299        mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
300        mDefaultThumbnail.setHasAlpha(false);
301        mDefaultThumbnail.eraseColor(0xFFffffff);
302        mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
303
304        // Initialize the proxy, cache and loaders
305        int numRecentTasks = ActivityManager.getMaxRecentTasksStatic();
306        mLoadQueue = new TaskResourceLoadQueue();
307        mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction);
308        mThumbnailCache = new TaskKeyLruCache<>(thumbnailCacheSize);
309        mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
310        mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
311                mClearActivityInfoOnEviction);
312        mActivityInfoCache = new LruCache(numRecentTasks);
313        mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mThumbnailCache,
314                mDefaultThumbnail, mDefaultIcon);
315    }
316
317    /** Returns the size of the app icon cache. */
318    public int getIconCacheSize() {
319        return mMaxIconCacheSize;
320    }
321
322    /** Returns the size of the thumbnail cache. */
323    public int getThumbnailCacheSize() {
324        return mMaxThumbnailCacheSize;
325    }
326
327    /** Creates a new plan for loading the recent tasks. */
328    public RecentsTaskLoadPlan createLoadPlan(Context context) {
329        RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context);
330        return plan;
331    }
332
333    /** Preloads recents tasks using the specified plan to store the output. */
334    public void preloadTasks(RecentsTaskLoadPlan plan, int topTaskId, boolean isTopTaskHome) {
335        plan.preloadPlan(this, topTaskId, isTopTaskHome);
336    }
337
338    /** Begins loading the heavy task data according to the specified options. */
339    public void loadTasks(Context context, RecentsTaskLoadPlan plan,
340            RecentsTaskLoadPlan.Options opts) {
341        if (opts == null) {
342            throw new RuntimeException("Requires load options");
343        }
344        plan.executePlan(opts, this, mLoadQueue);
345        if (!opts.onlyLoadForCache) {
346            mNumVisibleTasksLoaded = opts.numVisibleTasks;
347            mNumVisibleThumbnailsLoaded = opts.numVisibleTaskThumbnails;
348
349            // Start the loader
350            mLoader.start(context);
351        }
352    }
353
354    /**
355     * Acquires the task resource data directly from the cache, loading if necessary.
356     *
357     * @param fetchAndInvalidateThumbnails If set, will try loading thumbnails, invalidating them
358     *                                     in the cache and loading if necessary. Otherwise, do not
359     *                                     load the thumbnail unless the icon also has to be loaded.
360     */
361    public void loadTaskData(Task t, boolean fetchAndInvalidateThumbnails) {
362        Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
363        Bitmap thumbnail = null;
364        ActivityManager.TaskThumbnailInfo thumbnailInfo = null;
365        if (fetchAndInvalidateThumbnails) {
366            ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(t.key);
367            if (thumbnailData != null) {
368                thumbnail = thumbnailData.thumbnail;
369                thumbnailInfo = thumbnailData.thumbnailInfo;
370            }
371        } else {
372            thumbnail = mDefaultThumbnail;
373        }
374
375        // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
376        // use the default assets in their place until they load
377        boolean requiresLoad = (icon == null) || (thumbnail == null);
378        icon = icon != null ? icon : mDefaultIcon;
379        if (requiresLoad) {
380            mLoadQueue.addTask(t);
381        }
382        t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, icon,
383                thumbnailInfo);
384    }
385
386    /** Releases the task resource data back into the pool. */
387    public void unloadTaskData(Task t) {
388        mLoadQueue.removeTask(t);
389        t.notifyTaskDataUnloaded(null, mDefaultIcon);
390    }
391
392    /** Completely removes the resource data from the pool. */
393    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
394        mLoadQueue.removeTask(t);
395        mThumbnailCache.remove(t.key);
396        mIconCache.remove(t.key);
397        mActivityLabelCache.remove(t.key);
398        mContentDescriptionCache.remove(t.key);
399        if (notifyTaskDataUnloaded) {
400            t.notifyTaskDataUnloaded(null, mDefaultIcon);
401        }
402    }
403
404    /**
405     * Handles signals from the system, trimming memory when requested to prevent us from running
406     * out of memory.
407     */
408    public void onTrimMemory(int level) {
409        RecentsConfiguration config = Recents.getConfiguration();
410        switch (level) {
411            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
412                // Stop the loader immediately when the UI is no longer visible
413                stopLoader();
414                if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
415                    mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
416                            mMaxThumbnailCacheSize / 2));
417                } else if (config.svelteLevel == RecentsConfiguration.SVELTE_LIMIT_CACHE) {
418                    mThumbnailCache.trimToSize(mNumVisibleThumbnailsLoaded);
419                } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) {
420                    mThumbnailCache.evictAll();
421                }
422                mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
423                        mMaxIconCacheSize / 2));
424                break;
425            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
426            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
427                // We are leaving recents, so trim the data a bit
428                mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
429                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
430                mActivityInfoCache.trimToSize(Math.max(1,
431                        ActivityManager.getMaxRecentTasksStatic() / 2));
432                break;
433            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
434            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
435                // We are going to be low on memory
436                mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
437                mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
438                mActivityInfoCache.trimToSize(Math.max(1,
439                        ActivityManager.getMaxRecentTasksStatic() / 4));
440                break;
441            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
442            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
443                // We are low on memory, so release everything
444                mThumbnailCache.evictAll();
445                mIconCache.evictAll();
446                mActivityInfoCache.evictAll();
447                // The cache is small, only clear the label cache when we are critical
448                mActivityLabelCache.evictAll();
449                mContentDescriptionCache.evictAll();
450                break;
451            default:
452                break;
453        }
454    }
455
456    /**
457     * Returns the cached task label if the task key is not expired, updating the cache if it is.
458     */
459    String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) {
460        SystemServicesProxy ssp = Recents.getSystemServices();
461
462        // Return the task description label if it exists
463        if (td != null && td.getLabel() != null) {
464            return td.getLabel();
465        }
466        // Return the cached activity label if it exists
467        String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
468        if (label != null) {
469            return label;
470        }
471        // All short paths failed, load the label from the activity info and cache it
472        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
473        if (activityInfo != null) {
474            label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId);
475            mActivityLabelCache.put(taskKey, label);
476            return label;
477        }
478        // If the activity info does not exist or fails to load, return an empty label for now,
479        // but do not cache it
480        return "";
481    }
482
483    /**
484     * Returns the cached task content description if the task key is not expired, updating the
485     * cache if it is.
486     */
487    String getAndUpdateContentDescription(Task.TaskKey taskKey, Resources res) {
488        SystemServicesProxy ssp = Recents.getSystemServices();
489
490        // Return the cached content description if it exists
491        String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
492        if (label != null) {
493            return label;
494        }
495
496        // All short paths failed, load the label from the activity info and cache it
497        ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
498        if (activityInfo != null) {
499            label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, res);
500            mContentDescriptionCache.put(taskKey, label);
501            return label;
502        }
503        // If the content description does not exist, return an empty label for now, but do not
504        // cache it
505        return "";
506    }
507
508    /**
509     * Returns the cached task icon if the task key is not expired, updating the cache if it is.
510     */
511    Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td,
512            Resources res, boolean loadIfNotCached) {
513        SystemServicesProxy ssp = Recents.getSystemServices();
514
515        // Return the cached activity icon if it exists
516        Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey);
517        if (icon != null) {
518            return icon;
519        }
520
521        if (loadIfNotCached) {
522            // Return and cache the task description icon if it exists
523            icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res);
524            if (icon != null) {
525                mIconCache.put(taskKey, icon);
526                return icon;
527            }
528
529            // Load the icon from the activity info and cache it
530            ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
531            if (activityInfo != null) {
532                icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId);
533                if (icon != null) {
534                    mIconCache.put(taskKey, icon);
535                    return icon;
536                }
537            }
538        }
539        // We couldn't load any icon
540        return null;
541    }
542
543    /**
544     * Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
545     */
546    Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached) {
547        SystemServicesProxy ssp = Recents.getSystemServices();
548
549        // Return the cached thumbnail if it exists
550        ThumbnailData thumbnailData = mThumbnailCache.getAndInvalidateIfModified(taskKey);
551        if (thumbnailData != null) {
552            return thumbnailData.thumbnail;
553        }
554
555        if (loadIfNotCached) {
556            RecentsConfiguration config = Recents.getConfiguration();
557            if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
558                // Load the thumbnail from the system
559                thumbnailData = ssp.getTaskThumbnail(taskKey.id);
560                if (thumbnailData.thumbnail != null) {
561                    mThumbnailCache.put(taskKey, thumbnailData);
562                    return thumbnailData.thumbnail;
563                }
564            }
565        }
566        // We couldn't load any thumbnail
567        return null;
568    }
569
570    /**
571     * Returns the task's primary color if possible, defaulting to the default color if there is
572     * no specified primary color.
573     */
574    int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
575        if (td != null && td.getPrimaryColor() != 0) {
576            return td.getPrimaryColor();
577        }
578        return mDefaultTaskBarBackgroundColor;
579    }
580
581    /**
582     * Returns the task's background color if possible.
583     */
584    int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
585        if (td != null && td.getBackgroundColor() != 0) {
586            return td.getBackgroundColor();
587        }
588        return mDefaultTaskViewBackgroundColor;
589    }
590
591    /**
592     * Returns the activity info for the given task key, retrieving one from the system if the
593     * task key is expired.
594     */
595    ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) {
596        SystemServicesProxy ssp = Recents.getSystemServices();
597        ComponentName cn = taskKey.getComponent();
598        ActivityInfo activityInfo = mActivityInfoCache.get(cn);
599        if (activityInfo == null) {
600            activityInfo = ssp.getActivityInfo(cn, taskKey.userId);
601            if (cn == null || activityInfo == null) {
602                Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " +
603                        activityInfo);
604                return null;
605            }
606            mActivityInfoCache.put(cn, activityInfo);
607        }
608        return activityInfo;
609    }
610
611    /**
612     * Stops the task loader and clears all queued, pending task loads.
613     */
614    private void stopLoader() {
615        mLoader.stop();
616        mLoadQueue.clearTasks();
617    }
618
619    /**** Event Bus Events ****/
620
621    public final void onBusEvent(PackagesChangedEvent event) {
622        // Remove all the cached activity infos for this package.  The other caches do not need to
623        // be pruned at this time, as the TaskKey expiration checks will flush them next time their
624        // cached contents are requested
625        Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
626        for (ComponentName cn : activityInfoCache.keySet()) {
627            if (cn.getPackageName().equals(event.packageName)) {
628                if (DEBUG) {
629                    Log.d(TAG, "Removing activity info from cache: " + cn);
630                }
631                mActivityInfoCache.remove(cn);
632            }
633        }
634    }
635}
636