2 * Copyright (C) 2014 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.systemui.recents;
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.pm.PackageManager;
24 import android.content.res.Resources;
25 import android.graphics.Bitmap;
26 import android.graphics.Canvas;
27 import android.graphics.drawable.BitmapDrawable;
28 import android.graphics.drawable.Drawable;
29 import android.os.Handler;
30 import android.os.HandlerThread;
31 import android.os.UserHandle;
32 import android.util.LruCache;
33 import android.util.Pair;
34 import com.android.systemui.recents.model.SpaceNode;
35 import com.android.systemui.recents.model.Task;
36 import com.android.systemui.recents.model.TaskStack;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Iterator;
41 import java.util.List;
42 import java.util.concurrent.ConcurrentHashMap;
43 import java.util.concurrent.ConcurrentLinkedQueue;
46 /** A bitmap load queue */
47 class TaskResourceLoadQueue {
48 ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>();
49 ConcurrentHashMap<Task.TaskKey, Boolean> mForceLoadSet =
50 new ConcurrentHashMap<Task.TaskKey, Boolean>();
52 static final Boolean sFalse = new Boolean(false);
54 /** Adds a new task to the load queue */
55 void addTask(Task t, boolean forceLoad) {
56 Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|addTask]");
57 if (!mQueue.contains(t)) {
61 mForceLoadSet.put(t.key, new Boolean(true));
69 * Retrieves the next task from the load queue, as well as whether we want that task to be
72 Pair<Task, Boolean> nextTask() {
73 Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|nextTask]");
74 Task task = mQueue.poll();
75 Boolean forceLoadTask = null;
77 forceLoadTask = mForceLoadSet.remove(task.key);
79 if (forceLoadTask == null) {
80 forceLoadTask = sFalse;
82 return new Pair<Task, Boolean>(task, forceLoadTask);
85 /** Removes a task from the load queue */
86 void removeTask(Task t) {
87 Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|removeTask]");
89 mForceLoadSet.remove(t.key);
92 /** Clears all the tasks from the load queue */
94 Console.log(Constants.DebugFlags.App.TaskDataLoader, " [TaskResourceLoadQueue|clearTasks]");
96 mForceLoadSet.clear();
99 /** Returns whether the load queue is empty */
101 return mQueue.isEmpty();
105 /* Task resource loader */
106 class TaskResourceLoader implements Runnable {
108 HandlerThread mLoadThread;
109 Handler mLoadThreadHandler;
110 Handler mMainThreadHandler;
112 TaskResourceLoadQueue mLoadQueue;
113 DrawableLruCache mIconCache;
114 BitmapLruCache mThumbnailCache;
117 boolean mWaitingOnLoadQueue;
119 /** Constructor, creates a new loading thread that loads task resources in the background */
120 public TaskResourceLoader(TaskResourceLoadQueue loadQueue, DrawableLruCache iconCache,
121 BitmapLruCache thumbnailCache) {
122 mLoadQueue = loadQueue;
123 mIconCache = iconCache;
124 mThumbnailCache = thumbnailCache;
125 mMainThreadHandler = new Handler();
126 mLoadThread = new HandlerThread("Recents-TaskResourceLoader");
127 mLoadThread.setPriority(Thread.NORM_PRIORITY - 1);
129 mLoadThreadHandler = new Handler(mLoadThread.getLooper());
130 mLoadThreadHandler.post(this);
133 /** Restarts the loader thread */
134 void start(Context context) {
135 Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|start]");
138 // Notify the load thread to start loading
139 synchronized(mLoadThread) {
140 mLoadThread.notifyAll();
144 /** Requests the loader thread to stop after the current iteration */
146 Console.log(Constants.DebugFlags.App.TaskDataLoader, "[TaskResourceLoader|stop]");
147 // Mark as cancelled for the thread to pick up
149 // If we are waiting for the load queue for more tasks, then we can just reset the
150 // Context now, since nothing is using it
151 if (mWaitingOnLoadQueue) {
159 Console.log(Constants.DebugFlags.App.TaskDataLoader,
160 "[TaskResourceLoader|run|" + Thread.currentThread().getId() + "]");
162 Console.log(Constants.DebugFlags.App.TaskDataLoader,
163 "[TaskResourceLoader|cancel|" + Thread.currentThread().getId() + "]");
164 // We have to unset the context here, since the background thread may be using it
165 // when we call stop()
167 // If we are cancelled, then wait until we are started again
168 synchronized(mLoadThread) {
170 Console.log(Constants.DebugFlags.App.TaskDataLoader,
171 "[TaskResourceLoader|waitOnLoadThreadCancelled]");
173 } catch (InterruptedException ie) {
174 ie.printStackTrace();
178 // Load the next item from the queue
179 Pair<Task, Boolean> nextTaskData = mLoadQueue.nextTask();
180 final Task t = nextTaskData.first;
181 final boolean forceLoadTask = nextTaskData.second;
184 Drawable loadIcon = mIconCache.get(t.key);
185 Bitmap loadThumbnail = mThumbnailCache.get(t.key);
186 Console.log(Constants.DebugFlags.App.TaskDataLoader,
187 " [TaskResourceLoader|load]",
188 t + " icon: " + loadIcon + " thumbnail: " + loadThumbnail +
189 " forceLoad: " + forceLoadTask);
191 if (loadIcon == null || forceLoadTask) {
192 PackageManager pm = mContext.getPackageManager();
193 ActivityInfo info = pm.getActivityInfo(t.key.intent.getComponent(),
194 PackageManager.GET_META_DATA);
195 Drawable icon = info.loadIcon(pm);
198 Console.log(Constants.DebugFlags.App.TaskDataLoader,
199 " [TaskResourceLoader|loadIcon]",
202 mIconCache.put(t.key, icon);
206 // Load the thumbnail
207 if (loadThumbnail == null || forceLoadTask) {
208 ActivityManager am = (ActivityManager)
209 mContext.getSystemService(Context.ACTIVITY_SERVICE);
210 Bitmap thumbnail = am.getTaskTopThumbnail(t.key.id);
212 if (thumbnail != null) {
213 Console.log(Constants.DebugFlags.App.TaskDataLoader,
214 " [TaskResourceLoader|loadThumbnail]",
216 loadThumbnail = thumbnail;
217 mThumbnailCache.put(t.key, thumbnail);
219 Console.logError(mContext,
220 "Failed to load task top thumbnail for: " +
221 t.key.intent.getComponent().getPackageName());
226 // Notify that the task data has changed
227 final Drawable newIcon = loadIcon;
228 final Bitmap newThumbnail = loadThumbnail;
229 mMainThreadHandler.post(new Runnable() {
232 t.notifyTaskDataLoaded(newThumbnail, newIcon, forceLoadTask);
236 } catch (PackageManager.NameNotFoundException ne) {
237 ne.printStackTrace();
241 // If there are no other items in the list, then just wait until something is added
242 if (!mCancelled && mLoadQueue.isEmpty()) {
243 synchronized(mLoadQueue) {
245 Console.log(Constants.DebugFlags.App.TaskDataLoader,
246 "[TaskResourceLoader|waitOnLoadQueue]");
247 mWaitingOnLoadQueue = true;
249 mWaitingOnLoadQueue = false;
250 } catch (InterruptedException ie) {
251 ie.printStackTrace();
261 * The drawable cache. By using the Task's key, we can prevent holding onto a reference to the Task
262 * resource data, while keeping the cache data in memory where necessary.
264 class DrawableLruCache extends LruCache<Task.TaskKey, Drawable> {
265 public DrawableLruCache(int cacheSize) {
270 protected int sizeOf(Task.TaskKey t, Drawable d) {
271 // The cache size will be measured in kilobytes rather than number of items
272 // NOTE: this isn't actually correct, as the icon may be smaller
273 int maxBytes = (d.getIntrinsicWidth() * d.getIntrinsicHeight() * 4);
274 return maxBytes / 1024;
279 * The bitmap cache. By using the Task's key, we can prevent holding onto a reference to the Task
280 * resource data, while keeping the cache data in memory where necessary.
282 class BitmapLruCache extends LruCache<Task.TaskKey, Bitmap> {
283 public BitmapLruCache(int cacheSize) {
288 protected int sizeOf(Task.TaskKey t, Bitmap bitmap) {
289 // The cache size will be measured in kilobytes rather than number of items
290 return bitmap.getAllocationByteCount() / 1024;
294 /* Recents task loader
295 * NOTE: We should not hold any references to a Context from a static instance */
296 public class RecentsTaskLoader {
297 static RecentsTaskLoader sInstance;
299 DrawableLruCache mIconCache;
300 BitmapLruCache mThumbnailCache;
301 TaskResourceLoadQueue mLoadQueue;
302 TaskResourceLoader mLoader;
304 int mMaxThumbnailCacheSize;
305 int mMaxIconCacheSize;
307 BitmapDrawable mDefaultIcon;
308 Bitmap mDefaultThumbnail;
310 /** Private Constructor */
311 private RecentsTaskLoader(Context context) {
312 // Calculate the cache sizes, we just use a reasonable number here similar to those
313 // suggested in the Android docs, 1/8th for the thumbnail cache and 1/32 of the max memory
315 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
316 mMaxThumbnailCacheSize = maxMemory / 8;
317 mMaxIconCacheSize = mMaxThumbnailCacheSize / 4;
318 int iconCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
320 int thumbnailCacheSize = Constants.DebugFlags.App.DisableBackgroundCache ? 1 :
321 mMaxThumbnailCacheSize;
323 Console.log(Constants.DebugFlags.App.TaskDataLoader,
324 "[RecentsTaskLoader|init]", "thumbnailCache: " + thumbnailCacheSize +
325 " iconCache: " + iconCacheSize);
327 // Initialize the cache and loaders
328 mLoadQueue = new TaskResourceLoadQueue();
329 mIconCache = new DrawableLruCache(iconCacheSize);
330 mThumbnailCache = new BitmapLruCache(thumbnailCacheSize);
331 mLoader = new TaskResourceLoader(mLoadQueue, mIconCache, mThumbnailCache);
333 // Create the default assets
334 Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
335 mDefaultThumbnail = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
336 Canvas c = new Canvas();
338 c.drawColor(0x00000000);
339 c.setBitmap(mDefaultThumbnail);
340 c.drawColor(0x00000000);
342 mDefaultIcon = new BitmapDrawable(context.getResources(), icon);
343 Console.log(Constants.DebugFlags.App.TaskDataLoader,
344 "[RecentsTaskLoader|defaultBitmaps]",
345 "icon: " + mDefaultIcon + " thumbnail: " + mDefaultThumbnail, Console.AnsiRed);
348 /** Initializes the recents task loader */
349 public static RecentsTaskLoader initialize(Context context) {
350 if (sInstance == null) {
351 sInstance = new RecentsTaskLoader(context);
356 /** Returns the current recents task loader */
357 public static RecentsTaskLoader getInstance() {
361 /** Reload the set of recent tasks */
362 SpaceNode reload(Context context, int preloadCount) {
363 Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|reload]");
364 Resources res = context.getResources();
365 ArrayList<Task> tasksToForceLoad = new ArrayList<Task>();
366 TaskStack stack = new TaskStack(context);
367 SpaceNode root = new SpaceNode(context);
368 root.setStack(stack);
371 long t1 = System.currentTimeMillis();
373 PackageManager pm = context.getPackageManager();
374 ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
376 // Get the recent tasks
377 List<ActivityManager.RecentTaskInfo> tasks = am.getRecentTasksForUser(25,
378 ActivityManager.RECENT_IGNORE_UNAVAILABLE |
379 ActivityManager.RECENT_INCLUDE_PROFILES, UserHandle.CURRENT.getIdentifier());
380 Collections.reverse(tasks);
381 Console.log(Constants.DebugFlags.App.TimeSystemCalls,
382 "[RecentsTaskLoader|getRecentTasks]",
383 "" + (System.currentTimeMillis() - t1) + "ms");
384 Console.log(Constants.DebugFlags.App.TaskDataLoader,
385 "[RecentsTaskLoader|tasks]", "" + tasks.size());
387 // Remove home/recents tasks
388 Iterator<ActivityManager.RecentTaskInfo> iter = tasks.iterator();
389 while (iter.hasNext()) {
390 ActivityManager.RecentTaskInfo t = iter.next();
392 // Skip tasks in the home stack
393 if (am.isInHomeStack(t.persistentId)) {
397 // Skip tasks from this Recents package
398 if (t.baseIntent.getComponent().getPackageName().equals(context.getPackageName())) {
404 // Add each task to the task stack
405 t1 = System.currentTimeMillis();
406 int taskCount = tasks.size();
407 for (int i = 0; i < taskCount; i++) {
408 ActivityManager.RecentTaskInfo t = tasks.get(i);
409 ActivityInfo info = pm.getActivityInfo(t.baseIntent.getComponent(),
410 PackageManager.GET_META_DATA);
411 String title = info.loadLabel(pm).toString();
412 boolean isForemostTask = (i == (taskCount - 1));
414 // Preload the specified number of apps
415 if (i >= (taskCount - preloadCount)) {
416 Console.log(Constants.DebugFlags.App.TaskDataLoader,
417 "[RecentsTaskLoader|preloadTask]",
418 "i: " + i + " task: " + t.baseIntent.getComponent().getPackageName());
420 String label = (t.activityLabel == null ? title : t.activityLabel.toString());
421 BitmapDrawable bd = null;
422 if (t.activityIcon != null) {
423 bd = new BitmapDrawable(res, t.activityIcon);
425 Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, label, bd);
427 // Load the icon (if possible and not the foremost task, from the cache)
428 if (task.icon != null) {
429 mIconCache.put(task.key, task.icon);
431 if (!isForemostTask) {
432 task.icon = mIconCache.get(task.key);
433 if (task.icon != null) {
434 // Even though we get things from the cache, we should update them
435 // if they've changed in the bg
436 tasksToForceLoad.add(task);
439 if (task.icon == null) {
440 task.icon = info.loadIcon(pm);
441 if (task.icon != null) {
442 mIconCache.put(task.key, task.icon);
444 task.icon = mDefaultIcon;
449 // Load the thumbnail (if possible and not the foremost task, from the cache)
450 if (!isForemostTask) {
451 task.thumbnail = mThumbnailCache.get(task.key);
452 if (task.thumbnail != null) {
453 // Even though we get things from the cache, we should update them if
454 // they've changed in the bg
455 tasksToForceLoad.add(task);
458 if (task.thumbnail == null) {
459 Console.log(Constants.DebugFlags.App.TaskDataLoader,
460 "[RecentsTaskLoader|loadingTaskThumbnail]");
461 task.thumbnail = am.getTaskTopThumbnail(t.id);
462 if (task.thumbnail != null) {
463 mThumbnailCache.put(task.key, task.thumbnail);
465 task.thumbnail = mDefaultThumbnail;
469 // Create as many tasks a we want to multiply by
470 for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) {
471 Console.log(Constants.DebugFlags.App.TaskDataLoader,
472 " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
476 // Create as many tasks a we want to multiply by
477 for (int j = 0; j < Constants.Values.RecentsTaskLoader.TaskEntryMultiplier; j++) {
478 Console.log(Constants.DebugFlags.App.TaskDataLoader,
479 " [RecentsTaskLoader|task]", t.baseIntent.getComponent().getPackageName());
480 stack.addTask(new Task(t.persistentId, (t.id > -1), t.baseIntent, title,
485 Console.log(Constants.DebugFlags.App.TimeSystemCalls,
486 "[RecentsTaskLoader|getAllTaskTopThumbnail]",
487 "" + (System.currentTimeMillis() - t1) + "ms");
490 // Get all the stacks
491 t1 = System.currentTimeMillis();
492 List<ActivityManager.StackInfo> stackInfos = ams.getAllStackInfos();
493 Console.log(Constants.DebugFlags.App.TimeSystemCalls, "[RecentsTaskLoader|getAllStackInfos]", "" + (System.currentTimeMillis() - t1) + "ms");
494 Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|stacks]", "" + tasks.size());
495 for (ActivityManager.StackInfo s : stackInfos) {
496 Console.log(Constants.DebugFlags.App.TaskDataLoader, " [RecentsTaskLoader|stack]", s.toString());
497 if (stacks.containsKey(s.stackId)) {
498 stacks.get(s.stackId).setRect(s.bounds);
502 } catch (Exception e) {
506 // Start the task loader
507 mLoader.start(context);
509 // Add all the tasks that we are force/re-loading
510 for (Task t : tasksToForceLoad) {
511 mLoadQueue.addTask(t, true);
517 /** Acquires the task resource data from the pool. */
518 public void loadTaskData(Task t) {
519 Drawable icon = mIconCache.get(t.key);
520 Bitmap thumbnail = mThumbnailCache.get(t.key);
522 Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|loadTask]",
523 t + " icon: " + icon + " thumbnail: " + thumbnail +
524 " thumbnailCacheSize: " + mThumbnailCache.size());
526 boolean requiresLoad = false;
531 if (thumbnail == null) {
532 thumbnail = mDefaultThumbnail;
536 mLoadQueue.addTask(t, false);
538 t.notifyTaskDataLoaded(thumbnail, icon, false);
541 /** Releases the task resource data back into the pool. */
542 public void unloadTaskData(Task t) {
543 Console.log(Constants.DebugFlags.App.TaskDataLoader,
544 "[RecentsTaskLoader|unloadTask]", t +
545 " thumbnailCacheSize: " + mThumbnailCache.size());
547 mLoadQueue.removeTask(t);
548 t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultIcon);
551 /** Completely removes the resource data from the pool. */
552 public void deleteTaskData(Task t) {
553 Console.log(Constants.DebugFlags.App.TaskDataLoader,
554 "[RecentsTaskLoader|deleteTask]", t);
556 mLoadQueue.removeTask(t);
557 mThumbnailCache.remove(t.key);
558 mIconCache.remove(t.key);
559 t.notifyTaskDataUnloaded(mDefaultThumbnail, mDefaultIcon);
562 /** Stops the task loader and clears all pending tasks */
564 Console.log(Constants.DebugFlags.App.TaskDataLoader, "[RecentsTaskLoader|stopLoader]");
566 mLoadQueue.clearTasks();
569 void onTrimMemory(int level) {
570 Console.log(Constants.DebugFlags.App.Memory, "[RecentsTaskLoader|onTrimMemory]",
571 Console.trimMemoryLevelToString(level));
574 case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
575 // Stop the loader immediately when the UI is no longer visible
578 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
579 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
580 // We are leaving recents, so trim the data a bit
581 mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
582 mIconCache.trimToSize(mMaxIconCacheSize / 2);
584 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
585 case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
586 // We are going to be low on memory
587 mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
588 mIconCache.trimToSize(mMaxIconCacheSize / 4);
590 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
591 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
592 // We are low on memory, so release everything
593 mThumbnailCache.evictAll();
594 mIconCache.evictAll();