RecentsTaskLoader.java revision 1f24c7e37bc794057a156a730c7e4b53b01212ed
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.Context;
22import android.content.pm.ActivityInfo;
23import android.content.res.Resources;
24import android.graphics.Bitmap;
25import android.graphics.drawable.BitmapDrawable;
26import android.graphics.drawable.Drawable;
27import android.os.Handler;
28import android.os.HandlerThread;
29import android.os.UserHandle;
30import android.util.Pair;
31import com.android.systemui.recents.Constants;
32import com.android.systemui.recents.RecentsConfiguration;
33import com.android.systemui.recents.misc.Console;
34import com.android.systemui.recents.misc.SystemServicesProxy;
35
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.List;
39import java.util.concurrent.ConcurrentHashMap;
40import java.util.concurrent.ConcurrentLinkedQueue;
41
42
43/** A bitmap load queue */
44class TaskResourceLoadQueue {
45    ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
46    ConcurrentHashMap<Task.TaskKey, Boolean> mForceLoadSet =
47            new ConcurrentHashMap<Task.TaskKey, Boolean>();
48
49    static final Boolean sFalse = new Boolean(false);
50
51    /** Adds a new task to the load queue */
52    void addTask(Task t, boolean forceLoad) {
53        if (Console.Enabled) {
54            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|addTask]");
55        }
56        if (!mQueue.contains(t)) {
57            mQueue.add(t);
58        }
59        if (forceLoad) {
60            mForceLoadSet.put(t.key, new Boolean(true));
61        }
62        synchronized(this) {
63            notifyAll();
64        }
65    }
66
67    /**
68     * Retrieves the next task from the load queue, as well as whether we want that task to be
69     * force reloaded.
70     */
71    Pair<Task, Boolean> nextTask() {
72        if (Console.Enabled) {
73            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|nextTask]");
74        }
75        Task task = mQueue.poll();
76        Boolean forceLoadTask = null;
77        if (task != null) {
78            forceLoadTask = mForceLoadSet.remove(task.key);
79        }
80        if (forceLoadTask == null) {
81            forceLoadTask = sFalse;
82        }
83        return new Pair<Task, Boolean>(task, forceLoadTask);
84    }
85
86    /** Removes a task from the load queue */
87    void removeTask(Task t) {
88        if (Console.Enabled) {
89            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|removeTask]");
90        }
91        mQueue.remove(t);
92        mForceLoadSet.remove(t.key);
93    }
94
95    /** Clears all the tasks from the load queue */
96    void clearTasks() {
97        if (Console.Enabled) {
98            Console.log(Constants.Log.App.TaskDataLoader, "  [TaskResourceLoadQueue|clearTasks]");
99        }
100        mQueue.clear();
101        mForceLoadSet.clear();
102    }
103
104    /** Returns whether the load queue is empty */
105    boolean isEmpty() {
106        return mQueue.isEmpty();
107    }
108}
109
110/* Task resource loader */
111class TaskResourceLoader implements Runnable {
112    Context mContext;
113    HandlerThread mLoadThread;
114    Handler mLoadThreadHandler;
115    Handler mMainThreadHandler;
116
117    SystemServicesProxy mSystemServicesProxy;
118    TaskResourceLoadQueue mLoadQueue;
119    DrawableLruCache mApplicationIconCache;
120    BitmapLruCache mThumbnailCache;
121    Bitmap mDefaultThumbnail;
122
123    boolean mCancelled;
124    boolean mWaitingOnLoadQueue;
125
126    /** Constructor, creates a new loading thread that loads task resources in the background */
127    public TaskResourceLoader(TaskResourceLoadQueue loadQueue,
128                              DrawableLruCache applicationIconCache,
129                              BitmapLruCache thumbnailCache,
130                              Bitmap defaultThumbnail) {
131        mLoadQueue = loadQueue;
132        mApplicationIconCache = applicationIconCache;
133        mThumbnailCache = thumbnailCache;
134        mDefaultThumbnail = defaultThumbnail;
135        mMainThreadHandler = new Handler();
136        mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
137        mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
138        mLoadThread.start();
139        mLoadThreadHandler = new Handler(mLoadThread.getLooper());
140        mLoadThreadHandler.post(this);
141    }
142
143    /** Restarts the loader thread */
144    void start(Context context) {
145        if (Console.Enabled) {
146            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|start]");
147        }
148        mContext = context;
149        mCancelled = false;
150        mSystemServicesProxy = new SystemServicesProxy(context);
151        // Notify the load thread to start loading
152        synchronized(mLoadThread) {
153            mLoadThread.notifyAll();
154        }
155    }
156
157    /** Requests the loader thread to stop after the current iteration */
158    void stop() {
159        if (Console.Enabled) {
160            Console.log(Constants.Log.App.TaskDataLoader, "[TaskResourceLoader|stop]");
161        }
162        // Mark as cancelled for the thread to pick up
163        mCancelled = true;
164        mSystemServicesProxy = null;
165        // If we are waiting for the load queue for more tasks, then we can just reset the
166        // Context now, since nothing is using it
167        if (mWaitingOnLoadQueue) {
168            mContext = null;
169        }
170    }
171
172    @Override
173    public void run() {
174        while (true) {
175            if (Console.Enabled) {
176                Console.log(Constants.Log.App.TaskDataLoader,
177                        "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
178            }
179            if (mCancelled) {
180                if (Console.Enabled) {
181                    Console.log(Constants.Log.App.TaskDataLoader,
182                            "[TaskResourceLoader|cancel|" + Thread.currentThread().getId() + "]");
183                }
184                // We have to unset the context here, since the background thread may be using it
185                // when we call stop()
186                mContext = null;
187                // If we are cancelled, then wait until we are started again
188                synchronized(mLoadThread) {
189                    try {
190                        if (Console.Enabled) {
191                            Console.log(Constants.Log.App.TaskDataLoader,
192                                    "[TaskResourceLoader|waitOnLoadThreadCancelled]");
193                        }
194                        mLoadThread.wait();
195                    } catch (InterruptedException ie) {
196                        ie.printStackTrace();
197                    }
198                }
199            } else {
200                SystemServicesProxy ssp = mSystemServicesProxy;
201
202                // Load the next item from the queue
203                Pair<Task, Boolean> nextTaskData = mLoadQueue.nextTask();
204                final Task t = nextTaskData.first;
205                final boolean forceLoadTask = nextTaskData.second;
206                if (t != null) {
207                    Drawable loadIcon = mApplicationIconCache.getCheckLastActiveTime(t.key);
208                    Bitmap loadThumbnail = mThumbnailCache.getCheckLastActiveTime(t.key);
209                    if (Console.Enabled) {
210                        Console.log(Constants.Log.App.TaskDataLoader,
211                                "  [TaskResourceLoader|load]",
212                                t + " icon: " + loadIcon + " thumbnail: " + loadThumbnail +
213                                        " forceLoad: " + forceLoadTask);
214                    }
215                    // Load the application icon
216                    if (loadIcon == null || forceLoadTask) {
217                        ActivityInfo info = ssp.getActivityInfo(t.key.baseIntent.getComponent(),
218                                t.userId);
219                        Drawable icon = ssp.getActivityIcon(info, t.userId);
220                        if (!mCancelled) {
221                            if (icon != null) {
222                                if (Console.Enabled) {
223                                    Console.log(Constants.Log.App.TaskDataLoader,
224                                            "    [TaskResourceLoader|loadIcon]", icon);
225                                }
226                                loadIcon = icon;
227                                mApplicationIconCache.put(t.key, icon);
228                            }
229                        }
230                    }
231                    // Load the thumbnail
232                    if (loadThumbnail == null || forceLoadTask) {
233                        Bitmap thumbnail = ssp.getTaskThumbnail(t.key.id);
234                        if (!mCancelled) {
235                            if (thumbnail != null) {
236                                if (Console.Enabled) {
237                                    Console.log(Constants.Log.App.TaskDataLoader,
238                                            "    [TaskResourceLoader|loadThumbnail]", thumbnail);
239                                }
240                                thumbnail.setHasAlpha(false);
241                                loadThumbnail = thumbnail;
242                            } else {
243                                loadThumbnail = mDefaultThumbnail;
244                                Console.logError(mContext,
245                                        "Failed to load task top thumbnail for: " +
246                                                t.key.baseIntent.getComponent().getPackageName());
247                            }
248                            // We put the default thumbnail in the cache anyways
249                            mThumbnailCache.put(t.key, loadThumbnail);
250                        }
251                    }
252                    if (!mCancelled) {
253                        // Notify that the task data has changed
254                        final Drawable newIcon = loadIcon;
255                        final Bitmap newThumbnail = loadThumbnail;
256                        mMainThreadHandler.post(new Runnable() {
257                            @Override
258                            public void run() {
259                                t.notifyTaskDataLoaded(newThumbnail, newIcon);
260                            }
261                        });
262                    }
263                }
264
265                // If there are no other items in the list, then just wait until something is added
266                if (!mCancelled && mLoadQueue.isEmpty()) {
267                    synchronized(mLoadQueue) {
268                        try {
269                            if (Console.Enabled) {
270                                Console.log(Constants.Log.App.TaskDataLoader,
271                                        "[TaskResourceLoader|waitOnLoadQueue]");
272                            }
273                            mWaitingOnLoadQueue = true;
274                            mLoadQueue.wait();
275                            mWaitingOnLoadQueue = false;
276                        } catch (InterruptedException ie) {
277                            ie.printStackTrace();
278                        }
279                    }
280                }
281            }
282        }
283    }
284}
285
286/* Recents task loader
287 * NOTE: We should not hold any references to a Context from a static instance */
288public class RecentsTaskLoader {
289    static RecentsTaskLoader sInstance;
290
291    SystemServicesProxy mSystemServicesProxy;
292    DrawableLruCache mApplicationIconCache;
293    BitmapLruCache mThumbnailCache;
294    TaskResourceLoadQueue mLoadQueue;
295    TaskResourceLoader mLoader;
296
297    RecentsPackageMonitor mPackageMonitor;
298
299    int mMaxThumbnailCacheSize;
300    int mMaxIconCacheSize;
301
302    BitmapDrawable mDefaultApplicationIcon;
303    Bitmap mDefaultThumbnail;
304    Bitmap mLoadingThumbnail;
305
306    /** Private Constructor */
307    private RecentsTaskLoader(Context context) {
308        // Calculate the cache sizes, we just use a reasonable number here similar to those
309        // suggested in the Android docs, 1/8th for the thumbnail cache and 1/32 of the max memory
310        // for icons.
311        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
312        mMaxThumbnailCacheSize = maxMemory / 8;
313        mMaxIconCacheSize = mMaxThumbnailCacheSize / 4;
314        int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
315                mMaxIconCacheSize;
316        int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
317                mMaxThumbnailCacheSize;
318
319        if (Console.Enabled) {
320            Console.log(Constants.Log.App.TaskDataLoader,
321                    "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
322                    " iconCache: " + iconCacheSize);
323        }
324
325        // Create the default assets
326        Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
327        icon.eraseColor(0x00000000);
328        mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
329        mDefaultThumbnail.setHasAlpha(false);
330        mDefaultThumbnail.eraseColor(0xFFffffff);
331        mLoadingThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
332        mLoadingThumbnail.setHasAlpha(false);
333        mLoadingThumbnail.eraseColor(0xFFffffff);
334        mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
335
336        // Initialize the proxy, cache and loaders
337        mSystemServicesProxy = new SystemServicesProxy(context);
338        mPackageMonitor = new RecentsPackageMonitor();
339        mLoadQueue = new TaskResourceLoadQueue();
340        mApplicationIconCache = new DrawableLruCache(iconCacheSize);
341        mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
342        mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
343                mDefaultThumbnail);
344
345        if (Console.Enabled) {
346            Console.log(Constants.Log.App.TaskDataLoader,
347                    "[RecentsTaskLoader|defaultBitmaps]",
348                    "icon: " + mDefaultApplicationIcon +
349                    " default thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
350        }
351    }
352
353    /** Initializes the recents task loader */
354    public static RecentsTaskLoader initialize(Context context) {
355        if (sInstance == null) {
356            sInstance = new RecentsTaskLoader(context);
357        }
358        return sInstance;
359    }
360
361    /** Returns the current recents task loader */
362    public static RecentsTaskLoader getInstance() {
363        return sInstance;
364    }
365
366    /** Returns the system services proxy */
367    public SystemServicesProxy getSystemServicesProxy() {
368        return mSystemServicesProxy;
369    }
370
371    private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp) {
372        long t1 = System.currentTimeMillis();
373
374        List<ActivityManager.RecentTaskInfo> tasks =
375                ssp.getRecentTasks(50, UserHandle.CURRENT.getIdentifier());
376        Collections.reverse(tasks);
377        if (Console.Enabled) {
378            Console.log(Constants.Log.App.TimeSystemCalls,
379                    "[RecentsTaskLoader|getRecentTasks]",
380                    "" + (System.currentTimeMillis() - t1) + "ms");
381            Console.log(Constants.Log.App.TaskDataLoader,
382                    "[RecentsTaskLoader|tasks]", "" + tasks.size());
383        }
384
385        return tasks;
386    }
387
388    /** Reload the set of recent tasks */
389    public SpaceNode reload(Context context, int preloadCount) {
390        long t1 = System.currentTimeMillis();
391
392        if (Console.Enabled) {
393            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
394        }
395        RecentsConfiguration config = RecentsConfiguration.getInstance();
396        Resources res = context.getResources();
397        ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
398        TaskStack stack = new TaskStack();
399        SpaceNode root = new SpaceNode();
400        root.setStack(stack);
401
402        // Get the recent tasks
403        SystemServicesProxy ssp = mSystemServicesProxy;
404        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
405
406        // Add each task to the task stack
407        t1 = System.currentTimeMillis();
408        int taskCount = tasks.size();
409        for (int i = 0; i < taskCount; i++) {
410            ActivityManager.RecentTaskInfo t = tasks.get(i);
411            ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
412            if (info == null) continue;
413
414            ActivityManager.TaskDescription av = t.taskDescription;
415            String activityLabel = null;
416            Drawable activityIcon = null;
417            int activityColor = config.taskBarViewDefaultBackgroundColor;
418            if (av != null) {
419                activityLabel = (av.getLabel() != null ? av.getLabel() : ssp.getActivityLabel(info));
420                activityIcon = (av.getIcon() != null) ?
421                        ssp.getBadgedIcon(new BitmapDrawable(res, av.getIcon()), t.userId) : null;
422                if (av.getPrimaryColor() != 0) {
423                    activityColor = av.getPrimaryColor();
424                }
425            } else {
426                activityLabel = ssp.getActivityLabel(info);
427            }
428            boolean isForemostTask = (i == (taskCount - 1));
429
430            // Create a new task
431            Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, 0, activityLabel,
432                    activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime,
433                    (i == (taskCount - 1)));
434
435            // Preload the specified number of apps
436            if (i >= (taskCount - preloadCount)) {
437                if (Console.Enabled) {
438                    Console.log(Constants.Log.App.TaskDataLoader,
439                            "[RecentsTaskLoader|preloadTask]",
440                            "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
441                }
442
443                // Load the icon from the cache if possible
444                task.applicationIcon = mApplicationIconCache.getCheckLastActiveTime(task.key);
445                if (task.applicationIcon == null) {
446                    if (isForemostTask) {
447                        // We force loading the application icon for the foremost task
448                        task.applicationIcon = ssp.getActivityIcon(info, task.userId);
449                        if (task.applicationIcon != null) {
450                            mApplicationIconCache.put(task.key, task.applicationIcon);
451                        } else {
452                            task.applicationIcon = mDefaultApplicationIcon;
453                        }
454                    } else {
455                        // Either the task has updated, or we haven't cached any information for the
456                        // task, so reload it
457                        tasksToForceLoad.add(task);
458                    }
459                }
460
461                // Load the thumbnail (if possible and not the foremost task, from the cache)
462                task.thumbnail = mThumbnailCache.getCheckLastActiveTime(task.key);
463                if (task.thumbnail == null) {
464                    if (Console.Enabled) {
465                        Console.log(Constants.Log.App.TaskDataLoader,
466                                "[RecentsTaskLoader|loadingTaskThumbnail]");
467                    }
468                    if (isForemostTask) {
469                        // We force loading the thumbnail icon for the foremost task
470                        task.thumbnail = ssp.getTaskThumbnail(task.key.id);
471                        if (task.thumbnail != null) {
472                            task.thumbnail.setHasAlpha(false);
473                        } else {
474                            task.thumbnail = mDefaultThumbnail;
475                        }
476                        mThumbnailCache.put(task.key, task.thumbnail);
477                    } else {
478                        // Either the task has updated, or we haven't cached any information for the
479                        // task, so reload it
480                        tasksToForceLoad.add(task);
481                    }
482                }
483            }
484
485            // Add the task to the stack
486            if (Console.Enabled) {
487                Console.log(Constants.Log.App.TaskDataLoader,
488                        "  [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
489            }
490            stack.addTask(task);
491        }
492        if (Console.Enabled) {
493            Console.log(Constants.Log.App.TimeSystemCalls,
494                    "[RecentsTaskLoader|getAllTaskTopThumbnail]",
495                    "" + (System.currentTimeMillis() - t1) + "ms");
496        }
497
498        // Simulate the groupings that we describe
499        stack.createSimulatedAffiliatedGroupings();
500
501        // Start the task loader
502        mLoader.start(context);
503
504        // Add all the tasks that we are force/re-loading
505        for (Task t : tasksToForceLoad) {
506            mLoadQueue.addTask(t, true);
507        }
508
509        // Update the package monitor with the list of packages to listen for
510        mPackageMonitor.setTasks(tasks);
511
512        return root;
513    }
514
515    /** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */
516    public static TaskStack getShallowTaskStack(SystemServicesProxy ssp) {
517        List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp);
518        TaskStack stack = new TaskStack();
519
520        int taskCount = tasks.size();
521        for (int i = 0; i < taskCount; i++) {
522            ActivityManager.RecentTaskInfo t = tasks.get(i);
523            ActivityInfo info = ssp.getActivityInfo(t.baseIntent.getComponent(), t.userId);
524            if (info == null) continue;
525
526            stack.addTask(new Task(t.persistentId, true, t.baseIntent, 0, null, null, 0, 0,
527                    t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1))));
528        }
529        stack.createSimulatedAffiliatedGroupings();
530        return stack;
531    }
532
533    /** Acquires the task resource data directly from the pool. */
534    public void loadTaskData(Task t) {
535        Drawable applicationIcon = mApplicationIconCache.get(t.key);
536        Bitmap thumbnail = mThumbnailCache.get(t.key);
537
538        if (Console.Enabled) {
539            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
540                    t + " applicationIcon: " + applicationIcon + " thumbnail: " + thumbnail +
541                            " thumbnailCacheSize: " + mThumbnailCache.size());
542        }
543
544        boolean requiresLoad = false;
545        if (applicationIcon == null) {
546            applicationIcon = mDefaultApplicationIcon;
547            requiresLoad = true;
548        }
549        if (thumbnail == null) {
550            thumbnail = mLoadingThumbnail;
551            requiresLoad = true;
552        }
553        if (requiresLoad) {
554            mLoadQueue.addTask(t, false);
555        }
556        t.notifyTaskDataLoaded(thumbnail, applicationIcon);
557    }
558
559    /** Releases the task resource data back into the pool. */
560    public void unloadTaskData(Task t) {
561        if (Console.Enabled) {
562            Console.log(Constants.Log.App.TaskDataLoader,
563                    "[RecentsTaskLoader|unloadTask]", t +
564                    " thumbnailCacheSize: " + mThumbnailCache.size());
565        }
566
567        mLoadQueue.removeTask(t);
568        t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
569    }
570
571    /** Completely removes the resource data from the pool. */
572    public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
573        if (Console.Enabled) {
574            Console.log(Constants.Log.App.TaskDataLoader,
575                    "[RecentsTaskLoader|deleteTask]", t);
576        }
577
578        mLoadQueue.removeTask(t);
579        mThumbnailCache.remove(t.key);
580        mApplicationIconCache.remove(t.key);
581        if (notifyTaskDataUnloaded) {
582            t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultApplicationIcon);
583        }
584    }
585
586    /** Stops the task loader and clears all pending tasks */
587    void stopLoader() {
588        if (Console.Enabled) {
589            Console.log(Constants.Log.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
590        }
591        mLoader.stop();
592        mLoadQueue.clearTasks();
593    }
594
595    /** Registers any broadcast receivers. */
596    public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) {
597        // Register the broadcast receiver to handle messages related to packages being added/removed
598        mPackageMonitor.register(context, cb);
599    }
600
601    /** Unregisters any broadcast receivers. */
602    public void unregisterReceivers() {
603        mPackageMonitor.unregister();
604    }
605
606    /**
607     * Handles signals from the system, trimming memory when requested to prevent us from running
608     * out of memory.
609     */
610    public void onTrimMemory(int level) {
611        if (Console.Enabled) {
612            Console.log(Constants.Log.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
613                    Console.trimMemoryLevelToString(level));
614        }
615
616        switch (level) {
617            case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
618                // Stop the loader immediately when the UI is no longer visible
619                stopLoader();
620                break;
621            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
622            case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
623                // We are leaving recents, so trim the data a bit
624                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
625                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2);
626                break;
627            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
628            case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
629                // We are going to be low on memory
630                mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
631                mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4);
632                break;
633            case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
634            case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
635                // We are low on memory, so release everything
636                mThumbnailCache.evictAll();
637                mApplicationIconCache.evictAll();
638                break;
639            default:
640                break;
641        }
642    }
643}
644