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