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