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.Context;
22 import android.content.pm.ActivityInfo;
23 import android.content.res.Resources;
24 import android.graphics.Bitmap;
25 import android.graphics.drawable.BitmapDrawable;
26 import android.graphics.drawable.Drawable;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.util.Log;
30 
31 import com.android.systemui.R;
32 import com.android.systemui.recents.Constants;
33 import com.android.systemui.recents.RecentsConfiguration;
34 import com.android.systemui.recents.misc.SystemServicesProxy;
35 
36 import java.util.concurrent.ConcurrentLinkedQueue;
37 
38 
39 /** Handle to an ActivityInfo */
40 class ActivityInfoHandle {
41     ActivityInfo info;
42 }
43 
44 /** A bitmap load queue */
45 class TaskResourceLoadQueue {
46     ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
47 
48     /** Adds a new task to the load queue */
addTask(Task t)49     void addTask(Task t) {
50         if (!mQueue.contains(t)) {
51             mQueue.add(t);
52         }
53         synchronized(this) {
54             notifyAll();
55         }
56     }
57 
58     /**
59      * Retrieves the next task from the load queue, as well as whether we want that task to be
60      * force reloaded.
61      */
nextTask()62     Task nextTask() {
63         return mQueue.poll();
64     }
65 
66     /** Removes a task from the load queue */
removeTask(Task t)67     void removeTask(Task t) {
68         mQueue.remove(t);
69     }
70 
71     /** Clears all the tasks from the load queue */
clearTasks()72     void clearTasks() {
73         mQueue.clear();
74     }
75 
76     /** Returns whether the load queue is empty */
isEmpty()77     boolean isEmpty() {
78         return mQueue.isEmpty();
79     }
80 }
81 
82 /* Task resource loader */
83 class TaskResourceLoader implements Runnable {
84     static String TAG = "TaskResourceLoader";
85     static boolean DEBUG = false;
86 
87     Context mContext;
88     HandlerThread mLoadThread;
89     Handler mLoadThreadHandler;
90     Handler mMainThreadHandler;
91 
92     SystemServicesProxy mSystemServicesProxy;
93     TaskResourceLoadQueue mLoadQueue;
94     DrawableLruCache mApplicationIconCache;
95     BitmapLruCache mThumbnailCache;
96     Bitmap mDefaultThumbnail;
97     BitmapDrawable mDefaultApplicationIcon;
98 
99     boolean mCancelled;
100     boolean mWaitingOnLoadQueue;
101 
102     /** Constructor, creates a new loading thread that loads task resources in the background */
TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache, BitmapLruCache thumbnailCache, Bitmap defaultThumbnail, BitmapDrawable defaultApplicationIcon)103     public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache applicationIconCache,
104                               BitmapLruCache thumbnailCache, Bitmap defaultThumbnail,
105                               BitmapDrawable defaultApplicationIcon) {
106         mLoadQueue = loadQueue;
107         mApplicationIconCache = applicationIconCache;
108         mThumbnailCache = thumbnailCache;
109         mDefaultThumbnail = defaultThumbnail;
110         mDefaultApplicationIcon = defaultApplicationIcon;
111         mMainThreadHandler = new Handler();
112         mLoadThread = new HandlerThread("Recents-TaskResourceLoader",
113                 android.os.Process.THREAD_PRIORITY_BACKGROUND);
114         mLoadThread.start();
115         mLoadThreadHandler = new Handler(mLoadThread.getLooper());
116         mLoadThreadHandler.post(this);
117     }
118 
119     /** Restarts the loader thread */
start(Context context)120     void start(Context context) {
121         mContext = context;
122         mCancelled = false;
123         mSystemServicesProxy = new SystemServicesProxy(context);
124         // Notify the load thread to start loading
125         synchronized(mLoadThread) {
126             mLoadThread.notifyAll();
127         }
128     }
129 
130     /** Requests the loader thread to stop after the current iteration */
stop()131     void stop() {
132         // Mark as cancelled for the thread to pick up
133         mCancelled = true;
134         mSystemServicesProxy = null;
135         // If we are waiting for the load queue for more tasks, then we can just reset the
136         // Context now, since nothing is using it
137         if (mWaitingOnLoadQueue) {
138             mContext = null;
139         }
140     }
141 
142     @Override
run()143     public void run() {
144         while (true) {
145             if (mCancelled) {
146                 // We have to unset the context here, since the background thread may be using it
147                 // when we call stop()
148                 mContext = null;
149                 // If we are cancelled, then wait until we are started again
150                 synchronized(mLoadThread) {
151                     try {
152                         mLoadThread.wait();
153                     } catch (InterruptedException ie) {
154                         ie.printStackTrace();
155                     }
156                 }
157             } else {
158                 RecentsConfiguration config = RecentsConfiguration.getInstance();
159                 SystemServicesProxy ssp = mSystemServicesProxy;
160                 // If we've stopped the loader, then fall through to the above logic to wait on
161                 // the load thread
162                 if (ssp != null) {
163                     // Load the next item from the queue
164                     final Task t = mLoadQueue.nextTask();
165                     if (t != null) {
166                         Drawable cachedIcon = mApplicationIconCache.get(t.key);
167                         Bitmap cachedThumbnail = mThumbnailCache.get(t.key);
168 
169                         // Load the application icon if it is stale or we haven't cached one yet
170                         if (cachedIcon == null) {
171                             cachedIcon = getTaskDescriptionIcon(t.key, t.icon, t.iconFilename, ssp,
172                                     mContext.getResources());
173 
174                             if (cachedIcon == null) {
175                                 ActivityInfo info = ssp.getActivityInfo(
176                                         t.key.baseIntent.getComponent(), t.key.userId);
177                                 if (info != null) {
178                                     if (DEBUG) Log.d(TAG, "Loading icon: " + t.key);
179                                     cachedIcon = ssp.getActivityIcon(info, t.key.userId);
180                                 }
181                             }
182 
183                             if (cachedIcon == null) {
184                                 cachedIcon = mDefaultApplicationIcon;
185                             }
186 
187                             // At this point, even if we can't load the icon, we will set the
188                             // default icon.
189                             mApplicationIconCache.put(t.key, cachedIcon);
190                         }
191                         // Load the thumbnail if it is stale or we haven't cached one yet
192                         if (cachedThumbnail == null) {
193                             if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) {
194                                 if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key);
195                                 cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
196                             }
197                             if (cachedThumbnail == null) {
198                                 cachedThumbnail = mDefaultThumbnail;
199                             }
200                             // When svelte, we trim the memory to just the visible thumbnails when
201                             // leaving, so don't thrash the cache as the user scrolls (just load
202                             // them from scratch each time)
203                             if (config.svelteLevel < RecentsConfiguration.SVELTE_LIMIT_CACHE) {
204                                 mThumbnailCache.put(t.key, cachedThumbnail);
205                             }
206                         }
207                         if (!mCancelled) {
208                             // Notify that the task data has changed
209                             final Drawable newIcon = cachedIcon;
210                             final Bitmap newThumbnail = cachedThumbnail == mDefaultThumbnail
211                                     ? null : cachedThumbnail;
212                             mMainThreadHandler.post(new Runnable() {
213                                 @Override
214                                 public void run() {
215                                     t.notifyTaskDataLoaded(newThumbnail, newIcon);
216                                 }
217                             });
218                         }
219                     }
220                 }
221 
222                 // If there are no other items in the list, then just wait until something is added
223                 if (!mCancelled && mLoadQueue.isEmpty()) {
224                     synchronized(mLoadQueue) {
225                         try {
226                             mWaitingOnLoadQueue = true;
227                             mLoadQueue.wait();
228                             mWaitingOnLoadQueue = false;
229                         } catch (InterruptedException ie) {
230                             ie.printStackTrace();
231                         }
232                     }
233                 }
234             }
235         }
236     }
237 
getTaskDescriptionIcon(Task.TaskKey taskKey, Bitmap iconBitmap, String iconFilename, SystemServicesProxy ssp, Resources res)238     Drawable getTaskDescriptionIcon(Task.TaskKey taskKey, Bitmap iconBitmap, String iconFilename,
239             SystemServicesProxy ssp, Resources res) {
240         Bitmap tdIcon = iconBitmap != null
241                 ? iconBitmap
242                 : ActivityManager.TaskDescription.loadTaskDescriptionIcon(iconFilename);
243         if (tdIcon != null) {
244             return ssp.getBadgedIcon(new BitmapDrawable(res, tdIcon), taskKey.userId);
245         }
246         return null;
247     }
248 }
249 
250 /* Recents task loader
251  * NOTE: We should not hold any references to a Context from a static instance */
252 public class RecentsTaskLoader {
253     private static final String TAG = "RecentsTaskLoader";
254 
255     static RecentsTaskLoader sInstance;
256     static int INVALID_TASK_ID = -1;
257 
258     SystemServicesProxy mSystemServicesProxy;
259     DrawableLruCache mApplicationIconCache;
260     BitmapLruCache mThumbnailCache;
261     StringLruCache mActivityLabelCache;
262     StringLruCache mContentDescriptionCache;
263     TaskResourceLoadQueue mLoadQueue;
264     TaskResourceLoader mLoader;
265 
266     RecentsPackageMonitor mPackageMonitor;
267 
268     int mMaxThumbnailCacheSize;
269     int mMaxIconCacheSize;
270     int mNumVisibleTasksLoaded;
271     int mNumVisibleThumbnailsLoaded;
272 
273     BitmapDrawable mDefaultApplicationIcon;
274     Bitmap mDefaultThumbnail;
275 
276     /** Private Constructor */
RecentsTaskLoader(Context context)277     private RecentsTaskLoader(Context context) {
278         mMaxThumbnailCacheSize = context.getResources().getInteger(
279                 R.integer.config_recents_max_thumbnail_count);
280         mMaxIconCacheSize = context.getResources().getInteger(
281                 R.integer.config_recents_max_icon_count);
282         int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
283                 mMaxIconCacheSize;
284         int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
285                 mMaxThumbnailCacheSize;
286 
287         // Create the default assets
288         Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
289         icon.eraseColor(0x00000000);
290         mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
291         mDefaultThumbnail.setHasAlpha(false);
292         mDefaultThumbnail.eraseColor(0xFFffffff);
293         mDefaultApplicationIcon = new BitmapDrawable(context.getResources(), icon);
294 
295         // Initialize the proxy, cache and loaders
296         mSystemServicesProxy = new SystemServicesProxy(context);
297         mPackageMonitor = new RecentsPackageMonitor();
298         mLoadQueue = new TaskResourceLoadQueue();
299         mApplicationIconCache = new DrawableLruCache(iconCacheSize);
300         mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
301         mActivityLabelCache = new StringLruCache(100);
302         mContentDescriptionCache = new StringLruCache(100);
303         mLoader = new TaskResourceLoader(mLoadQueue, mApplicationIconCache, mThumbnailCache,
304                 mDefaultThumbnail, mDefaultApplicationIcon);
305     }
306 
307     /** Initializes the recents task loader */
initialize(Context context)308     public static RecentsTaskLoader initialize(Context context) {
309         if (sInstance == null) {
310             sInstance = new RecentsTaskLoader(context);
311         }
312         return sInstance;
313     }
314 
315     /** Returns the current recents task loader */
getInstance()316     public static RecentsTaskLoader getInstance() {
317         return sInstance;
318     }
319 
320     /** Returns the system services proxy */
getSystemServicesProxy()321     public SystemServicesProxy getSystemServicesProxy() {
322         return mSystemServicesProxy;
323     }
324 
325     /** Returns the activity label using as many cached values as we can. */
getAndUpdateActivityLabel(Task.TaskKey taskKey, ActivityManager.TaskDescription td, SystemServicesProxy ssp, ActivityInfoHandle infoHandle)326     public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
327             ActivityManager.TaskDescription td, SystemServicesProxy ssp,
328             ActivityInfoHandle infoHandle) {
329         // Return the task description label if it exists
330         if (td != null && td.getLabel() != null) {
331             return td.getLabel();
332         }
333         // Return the cached activity label if it exists
334         String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
335         if (label != null) {
336             return label;
337         }
338         // All short paths failed, load the label from the activity info and cache it
339         if (infoHandle.info == null) {
340             infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
341                     taskKey.userId);
342         }
343         if (infoHandle.info != null) {
344             label = ssp.getActivityLabel(infoHandle.info);
345             mActivityLabelCache.put(taskKey, label);
346             return label;
347         } else {
348             Log.w(TAG, "Missing ActivityInfo for " + taskKey.baseIntent.getComponent()
349                     + " u=" + taskKey.userId);
350         }
351         // If the activity info does not exist or fails to load, return an empty label for now,
352         // but do not cache it
353         return "";
354     }
355 
356     /** Returns the content description using as many cached values as we can. */
getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel, SystemServicesProxy ssp, Resources res)357     public String getAndUpdateContentDescription(Task.TaskKey taskKey, String activityLabel,
358             SystemServicesProxy ssp, Resources res) {
359         // Return the cached content description if it exists
360         String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
361         if (label != null) {
362             return label;
363         }
364         // If the given activity label is empty, don't compute or cache the content description
365         if (activityLabel.isEmpty()) {
366             return "";
367         }
368 
369         label = ssp.getContentDescription(taskKey.baseIntent, taskKey.userId, activityLabel, res);
370         if (label != null) {
371             mContentDescriptionCache.put(taskKey, label);
372             return label;
373         } else {
374             Log.w(TAG, "Missing content description for " + taskKey.baseIntent.getComponent()
375                     + " u=" + taskKey.userId);
376         }
377         // If the content description does not exist, return an empty label for now, but do not
378         // cache it
379         return "";
380     }
381 
382     /** Returns the activity icon using as many cached values as we can. */
getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, SystemServicesProxy ssp, Resources res, ActivityInfoHandle infoHandle, boolean loadIfNotCached)383     public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
384             ActivityManager.TaskDescription td, SystemServicesProxy ssp,
385             Resources res, ActivityInfoHandle infoHandle, boolean loadIfNotCached) {
386         // Return the cached activity icon if it exists
387         Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
388         if (icon != null) {
389             return icon;
390         }
391 
392         if (loadIfNotCached) {
393             // Return and cache the task description icon if it exists
394             Drawable tdDrawable = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
395                     td.getIconFilename(), ssp, res);
396             if (tdDrawable != null) {
397                 mApplicationIconCache.put(taskKey, tdDrawable);
398                 return tdDrawable;
399             }
400 
401             // Load the icon from the activity info and cache it
402             if (infoHandle.info == null) {
403                 infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
404                         taskKey.userId);
405             }
406             if (infoHandle.info != null) {
407                 icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
408                 if (icon != null) {
409                     mApplicationIconCache.put(taskKey, icon);
410                     return icon;
411                 }
412             }
413         }
414         // We couldn't load any icon
415         return null;
416     }
417 
418     /** Returns the bitmap using as many cached values as we can. */
getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp, boolean loadIfNotCached)419     public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
420             boolean loadIfNotCached) {
421         // Return the cached thumbnail if it exists
422         Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
423         if (thumbnail != null) {
424             return thumbnail;
425         }
426 
427         RecentsConfiguration config = RecentsConfiguration.getInstance();
428         if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING && loadIfNotCached) {
429             // Load the thumbnail from the system
430             thumbnail = ssp.getTaskThumbnail(taskKey.id);
431             if (thumbnail != null) {
432                 mThumbnailCache.put(taskKey, thumbnail);
433                 return thumbnail;
434             }
435         }
436         // We couldn't load any thumbnail
437         return null;
438     }
439 
440     /** Returns the activity's primary color. */
getActivityPrimaryColor(ActivityManager.TaskDescription td, RecentsConfiguration config)441     public int getActivityPrimaryColor(ActivityManager.TaskDescription td,
442             RecentsConfiguration config) {
443         if (td != null && td.getPrimaryColor() != 0) {
444             return td.getPrimaryColor();
445         }
446         return config.taskBarViewDefaultBackgroundColor;
447     }
448 
449     /** Returns the size of the app icon cache. */
getApplicationIconCacheSize()450     public int getApplicationIconCacheSize() {
451         return mMaxIconCacheSize;
452     }
453 
454     /** Returns the size of the thumbnail cache. */
getThumbnailCacheSize()455     public int getThumbnailCacheSize() {
456         return mMaxThumbnailCacheSize;
457     }
458 
459     /** Creates a new plan for loading the recent tasks. */
createLoadPlan(Context context)460     public RecentsTaskLoadPlan createLoadPlan(Context context) {
461         RecentsConfiguration config = RecentsConfiguration.getInstance();
462         RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy);
463         return plan;
464     }
465 
466     /** Preloads recents tasks using the specified plan to store the output. */
preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome)467     public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) {
468         plan.preloadPlan(this, isTopTaskHome);
469     }
470 
471     /** Begins loading the heavy task data according to the specified options. */
loadTasks(Context context, RecentsTaskLoadPlan plan, RecentsTaskLoadPlan.Options opts)472     public void loadTasks(Context context, RecentsTaskLoadPlan plan,
473             RecentsTaskLoadPlan.Options opts) {
474         if (opts == null) {
475             throw new RuntimeException("Requires load options");
476         }
477         plan.executePlan(opts, this, mLoadQueue);
478         if (!opts.onlyLoadForCache) {
479             mNumVisibleTasksLoaded = opts.numVisibleTasks;
480             mNumVisibleThumbnailsLoaded = opts.numVisibleTaskThumbnails;
481 
482             // Start the loader
483             mLoader.start(context);
484         }
485     }
486 
487     /** Acquires the task resource data directly from the pool. */
loadTaskData(Task t)488     public void loadTaskData(Task t) {
489         Drawable applicationIcon = mApplicationIconCache.getAndInvalidateIfModified(t.key);
490         Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(t.key);
491 
492         // Grab the thumbnail/icon from the cache, if either don't exist, then trigger a reload and
493         // use the default assets in their place until they load
494         boolean requiresLoad = (applicationIcon == null) || (thumbnail == null);
495         applicationIcon = applicationIcon != null ? applicationIcon : mDefaultApplicationIcon;
496         if (requiresLoad) {
497             mLoadQueue.addTask(t);
498         }
499         t.notifyTaskDataLoaded(thumbnail == mDefaultThumbnail ? null : thumbnail, applicationIcon);
500     }
501 
502     /** Releases the task resource data back into the pool. */
unloadTaskData(Task t)503     public void unloadTaskData(Task t) {
504         mLoadQueue.removeTask(t);
505         t.notifyTaskDataUnloaded(null, mDefaultApplicationIcon);
506     }
507 
508     /** Completely removes the resource data from the pool. */
deleteTaskData(Task t, boolean notifyTaskDataUnloaded)509     public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
510         mLoadQueue.removeTask(t);
511         mThumbnailCache.remove(t.key);
512         mApplicationIconCache.remove(t.key);
513         if (notifyTaskDataUnloaded) {
514             t.notifyTaskDataUnloaded(null, mDefaultApplicationIcon);
515         }
516     }
517 
518     /** Stops the task loader and clears all pending tasks */
stopLoader()519     void stopLoader() {
520         mLoader.stop();
521         mLoadQueue.clearTasks();
522     }
523 
524     /** Registers any broadcast receivers. */
registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb)525     public void registerReceivers(Context context, RecentsPackageMonitor.PackageCallbacks cb) {
526         // Register the broadcast receiver to handle messages related to packages being added/removed
527         mPackageMonitor.register(context, cb);
528     }
529 
530     /** Unregisters any broadcast receivers. */
unregisterReceivers()531     public void unregisterReceivers() {
532         mPackageMonitor.unregister();
533     }
534 
535     /**
536      * Handles signals from the system, trimming memory when requested to prevent us from running
537      * out of memory.
538      */
onTrimMemory(int level)539     public void onTrimMemory(int level) {
540         RecentsConfiguration config = RecentsConfiguration.getInstance();
541         switch (level) {
542             case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
543                 // Stop the loader immediately when the UI is no longer visible
544                 stopLoader();
545                 if (config.svelteLevel == RecentsConfiguration.SVELTE_NONE) {
546                     mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
547                             mMaxThumbnailCacheSize / 2));
548                 } else if (config.svelteLevel == RecentsConfiguration.SVELTE_LIMIT_CACHE) {
549                     mThumbnailCache.trimToSize(mNumVisibleThumbnailsLoaded);
550                 } else if (config.svelteLevel >= RecentsConfiguration.SVELTE_DISABLE_CACHE) {
551                     mThumbnailCache.evictAll();
552                 }
553                 mApplicationIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
554                         mMaxIconCacheSize / 2));
555                 break;
556             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
557             case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
558                 // We are leaving recents, so trim the data a bit
559                 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
560                 mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
561                 break;
562             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
563             case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
564                 // We are going to be low on memory
565                 mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
566                 mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
567                 break;
568             case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
569             case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
570                 // We are low on memory, so release everything
571                 mThumbnailCache.evictAll();
572                 mApplicationIconCache.evictAll();
573                 // The cache is small, only clear the label cache when we are critical
574                 mActivityLabelCache.evictAll();
575                 mContentDescriptionCache.evictAll();
576                 break;
577             default:
578                 break;
579         }
580     }
581 }
582