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;
39import com.android.systemui.recents.misc.Utilities;
40
41import java.io.PrintWriter;
42import java.util.Map;
43import java.util.concurrent.ConcurrentLinkedQueue;
44
45
46/**
47 * A Task load queue
48 */
49class 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 */
89class 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 */
247public 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