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