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