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.ComponentName; 22 import android.content.Context; 23 import android.content.pm.ActivityInfo; 24 import android.content.res.Resources; 25 import android.graphics.Bitmap; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.graphics.drawable.Drawable; 28 import android.os.Handler; 29 import android.os.HandlerThread; 30 import android.os.Looper; 31 import android.os.Trace; 32 import android.util.Log; 33 import android.util.LruCache; 34 35 import com.android.internal.annotations.GuardedBy; 36 import com.android.systemui.R; 37 import com.android.systemui.recents.Recents; 38 import com.android.systemui.recents.RecentsConfiguration; 39 import com.android.systemui.recents.RecentsDebugFlags; 40 import com.android.systemui.recents.events.activity.PackagesChangedEvent; 41 import com.android.systemui.recents.misc.SystemServicesProxy; 42 43 import java.io.PrintWriter; 44 import java.util.Map; 45 import java.util.concurrent.ConcurrentLinkedQueue; 46 47 48 /** 49 * A Task load queue 50 */ 51 class TaskResourceLoadQueue { 52 53 ConcurrentLinkedQueue<Task> mQueue = new ConcurrentLinkedQueue<Task>(); 54 55 /** Adds a new task to the load queue */ addTask(Task t)56 void addTask(Task t) { 57 if (!mQueue.contains(t)) { 58 mQueue.add(t); 59 } 60 synchronized(this) { 61 notifyAll(); 62 } 63 } 64 65 /** 66 * Retrieves the next task from the load queue, as well as whether we want that task to be 67 * force reloaded. 68 */ nextTask()69 Task nextTask() { 70 return mQueue.poll(); 71 } 72 73 /** Removes a task from the load queue */ removeTask(Task t)74 void removeTask(Task t) { 75 mQueue.remove(t); 76 } 77 78 /** Clears all the tasks from the load queue */ clearTasks()79 void clearTasks() { 80 mQueue.clear(); 81 } 82 83 /** Returns whether the load queue is empty */ isEmpty()84 boolean isEmpty() { 85 return mQueue.isEmpty(); 86 } 87 } 88 89 /** 90 * Task resource loader 91 */ 92 class BackgroundTaskLoader implements Runnable { 93 static String TAG = "TaskResourceLoader"; 94 static boolean DEBUG = false; 95 96 Context mContext; 97 HandlerThread mLoadThread; 98 Handler mLoadThreadHandler; 99 Handler mMainThreadHandler; 100 101 TaskResourceLoadQueue mLoadQueue; 102 TaskKeyLruCache<Drawable> mIconCache; 103 BitmapDrawable mDefaultIcon; 104 105 boolean mStarted; 106 boolean mCancelled; 107 boolean mWaitingOnLoadQueue; 108 109 private final OnIdleChangedListener mOnIdleChangedListener; 110 111 /** Constructor, creates a new loading thread that loads task resources in the background */ BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon, OnIdleChangedListener onIdleChangedListener)112 public BackgroundTaskLoader(TaskResourceLoadQueue loadQueue, 113 TaskKeyLruCache<Drawable> iconCache, BitmapDrawable defaultIcon, 114 OnIdleChangedListener onIdleChangedListener) { 115 mLoadQueue = loadQueue; 116 mIconCache = iconCache; 117 mDefaultIcon = defaultIcon; 118 mMainThreadHandler = new Handler(); 119 mOnIdleChangedListener = onIdleChangedListener; 120 mLoadThread = new HandlerThread("Recents-TaskResourceLoader", 121 android.os.Process.THREAD_PRIORITY_BACKGROUND); 122 mLoadThread.start(); 123 mLoadThreadHandler = new Handler(mLoadThread.getLooper()); 124 } 125 126 /** Restarts the loader thread */ start(Context context)127 void start(Context context) { 128 mContext = context; 129 mCancelled = false; 130 if (!mStarted) { 131 // Start loading on the load thread 132 mStarted = true; 133 mLoadThreadHandler.post(this); 134 } else { 135 // Notify the load thread to start loading again 136 synchronized (mLoadThread) { 137 mLoadThread.notifyAll(); 138 } 139 } 140 } 141 142 /** Requests the loader thread to stop after the current iteration */ stop()143 void stop() { 144 // Mark as cancelled for the thread to pick up 145 mCancelled = true; 146 // If we are waiting for the load queue for more tasks, then we can just reset the 147 // Context now, since nothing is using it 148 if (mWaitingOnLoadQueue) { 149 mContext = null; 150 } 151 } 152 153 @Override run()154 public void run() { 155 while (true) { 156 if (mCancelled) { 157 // We have to unset the context here, since the background thread may be using it 158 // when we call stop() 159 mContext = null; 160 // If we are cancelled, then wait until we are started again 161 synchronized(mLoadThread) { 162 try { 163 mLoadThread.wait(); 164 } catch (InterruptedException ie) { 165 ie.printStackTrace(); 166 } 167 } 168 } else { 169 SystemServicesProxy ssp = Recents.getSystemServices(); 170 // If we've stopped the loader, then fall through to the above logic to wait on 171 // the load thread 172 if (ssp != null) { 173 processLoadQueueItem(ssp); 174 } 175 176 // If there are no other items in the list, then just wait until something is added 177 if (!mCancelled && mLoadQueue.isEmpty()) { 178 synchronized(mLoadQueue) { 179 try { 180 mWaitingOnLoadQueue = true; 181 mMainThreadHandler.post( 182 () -> mOnIdleChangedListener.onIdleChanged(true)); 183 mLoadQueue.wait(); 184 mMainThreadHandler.post( 185 () -> mOnIdleChangedListener.onIdleChanged(false)); 186 mWaitingOnLoadQueue = false; 187 } catch (InterruptedException ie) { 188 ie.printStackTrace(); 189 } 190 } 191 } 192 } 193 } 194 } 195 196 /** 197 * This needs to be in a separate method to work around an surprising interpreter behavior: 198 * The register will keep the local reference to cachedThumbnailData even if it falls out of 199 * scope. Putting it into a method fixes this issue. 200 */ processLoadQueueItem(SystemServicesProxy ssp)201 private void processLoadQueueItem(SystemServicesProxy ssp) { 202 // Load the next item from the queue 203 final Task t = mLoadQueue.nextTask(); 204 if (t != null) { 205 Drawable cachedIcon = mIconCache.get(t.key); 206 207 // Load the icon if it is stale or we haven't cached one yet 208 if (cachedIcon == null) { 209 cachedIcon = ssp.getBadgedTaskDescriptionIcon(t.taskDescription, 210 t.key.userId, mContext.getResources()); 211 212 if (cachedIcon == null) { 213 ActivityInfo info = ssp.getActivityInfo( 214 t.key.getComponent(), t.key.userId); 215 if (info != null) { 216 if (DEBUG) Log.d(TAG, "Loading icon: " + t.key); 217 cachedIcon = ssp.getBadgedActivityIcon(info, t.key.userId); 218 } 219 } 220 221 if (cachedIcon == null) { 222 cachedIcon = mDefaultIcon; 223 } 224 225 // At this point, even if we can't load the icon, we will set the 226 // default icon. 227 mIconCache.put(t.key, cachedIcon); 228 } 229 230 if (DEBUG) Log.d(TAG, "Loading thumbnail: " + t.key); 231 final ThumbnailData thumbnailData = ssp.getTaskThumbnail(t.key.id, 232 true /* reducedResolution */); 233 234 if (!mCancelled) { 235 // Notify that the task data has changed 236 final Drawable finalIcon = cachedIcon; 237 mMainThreadHandler.post( 238 () -> t.notifyTaskDataLoaded(thumbnailData, finalIcon)); 239 } 240 } 241 } 242 243 interface OnIdleChangedListener { onIdleChanged(boolean idle)244 void onIdleChanged(boolean idle); 245 } 246 } 247 248 /** 249 * Recents task loader 250 */ 251 public class RecentsTaskLoader { 252 253 private static final String TAG = "RecentsTaskLoader"; 254 private static final boolean DEBUG = false; 255 256 // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos 257 // for many tasks, which we use to get the activity labels and icons. Unlike the other caches 258 // below, this is per-package so we can't invalidate the items in the cache based on the last 259 // active time. Instead, we rely on the RecentsPackageMonitor to keep us informed whenever a 260 // package in the cache has been updated, so that we may remove it. 261 private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache; 262 private final TaskKeyLruCache<Drawable> mIconCache; 263 private final TaskKeyLruCache<String> mActivityLabelCache; 264 private final TaskKeyLruCache<String> mContentDescriptionCache; 265 private final TaskResourceLoadQueue mLoadQueue; 266 private final BackgroundTaskLoader mLoader; 267 private final HighResThumbnailLoader mHighResThumbnailLoader; 268 @GuardedBy("this") 269 private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>(); 270 @GuardedBy("this") 271 private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>(); 272 private final int mMaxThumbnailCacheSize; 273 private final int mMaxIconCacheSize; 274 private int mNumVisibleTasksLoaded; 275 276 int mDefaultTaskBarBackgroundColor; 277 int mDefaultTaskViewBackgroundColor; 278 BitmapDrawable mDefaultIcon; 279 280 private TaskKeyLruCache.EvictionCallback mClearActivityInfoOnEviction = 281 new TaskKeyLruCache.EvictionCallback() { 282 @Override 283 public void onEntryEvicted(Task.TaskKey key) { 284 if (key != null) { 285 mActivityInfoCache.remove(key.getComponent()); 286 } 287 } 288 }; 289 RecentsTaskLoader(Context context)290 public RecentsTaskLoader(Context context) { 291 Resources res = context.getResources(); 292 mDefaultTaskBarBackgroundColor = 293 context.getColor(R.color.recents_task_bar_default_background_color); 294 mDefaultTaskViewBackgroundColor = 295 context.getColor(R.color.recents_task_view_default_background_color); 296 mMaxThumbnailCacheSize = res.getInteger(R.integer.config_recents_max_thumbnail_count); 297 mMaxIconCacheSize = res.getInteger(R.integer.config_recents_max_icon_count); 298 int iconCacheSize = RecentsDebugFlags.Static.DisableBackgroundCache ? 1 : 299 mMaxIconCacheSize; 300 301 // Create the default assets 302 Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); 303 icon.eraseColor(0); 304 mDefaultIcon = new BitmapDrawable(context.getResources(), icon); 305 306 // Initialize the proxy, cache and loaders 307 int numRecentTasks = ActivityManager.getMaxRecentTasksStatic(); 308 mHighResThumbnailLoader = new HighResThumbnailLoader(Recents.getSystemServices(), 309 Looper.getMainLooper(), Recents.getConfiguration().isLowRamDevice); 310 mLoadQueue = new TaskResourceLoadQueue(); 311 mIconCache = new TaskKeyLruCache<>(iconCacheSize, mClearActivityInfoOnEviction); 312 mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction); 313 mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks, 314 mClearActivityInfoOnEviction); 315 mActivityInfoCache = new LruCache(numRecentTasks); 316 mLoader = new BackgroundTaskLoader(mLoadQueue, mIconCache, mDefaultIcon, 317 mHighResThumbnailLoader::setTaskLoadQueueIdle); 318 } 319 320 /** Returns the size of the app icon cache. */ getIconCacheSize()321 public int getIconCacheSize() { 322 return mMaxIconCacheSize; 323 } 324 325 /** Returns the size of the thumbnail cache. */ getThumbnailCacheSize()326 public int getThumbnailCacheSize() { 327 return mMaxThumbnailCacheSize; 328 } 329 getHighResThumbnailLoader()330 public HighResThumbnailLoader getHighResThumbnailLoader() { 331 return mHighResThumbnailLoader; 332 } 333 334 /** Creates a new plan for loading the recent tasks. */ createLoadPlan(Context context)335 public RecentsTaskLoadPlan createLoadPlan(Context context) { 336 RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context); 337 return plan; 338 } 339 340 /** Preloads raw recents tasks using the specified plan to store the output. */ preloadRawTasks(RecentsTaskLoadPlan plan, boolean includeFrontMostExcludedTask)341 public synchronized void preloadRawTasks(RecentsTaskLoadPlan plan, 342 boolean includeFrontMostExcludedTask) { 343 plan.preloadRawTasks(includeFrontMostExcludedTask); 344 } 345 346 /** Preloads recents tasks using the specified plan to store the output. */ preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, boolean includeFrontMostExcludedTask)347 public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, 348 boolean includeFrontMostExcludedTask) { 349 try { 350 Trace.beginSection("preloadPlan"); 351 plan.preloadPlan(this, runningTaskId, includeFrontMostExcludedTask); 352 } finally { 353 Trace.endSection(); 354 } 355 } 356 357 /** Begins loading the heavy task data according to the specified options. */ loadTasks(Context context, RecentsTaskLoadPlan plan, RecentsTaskLoadPlan.Options opts)358 public synchronized void loadTasks(Context context, RecentsTaskLoadPlan plan, 359 RecentsTaskLoadPlan.Options opts) { 360 if (opts == null) { 361 throw new RuntimeException("Requires load options"); 362 } 363 if (opts.onlyLoadForCache && opts.loadThumbnails) { 364 365 // If we are loading for the cache, we'd like to have the real cache only include the 366 // visible thumbnails. However, we also don't want to reload already cached thumbnails. 367 // Thus, we copy over the current entries into a second cache, and clear the real cache, 368 // such that the real cache only contains visible thumbnails. 369 mTempCache.copyEntries(mThumbnailCache); 370 mThumbnailCache.evictAll(); 371 } 372 plan.executePlan(opts, this); 373 mTempCache.evictAll(); 374 if (!opts.onlyLoadForCache) { 375 mNumVisibleTasksLoaded = opts.numVisibleTasks; 376 } 377 } 378 379 /** 380 * Acquires the task resource data directly from the cache, loading if necessary. 381 */ loadTaskData(Task t)382 public void loadTaskData(Task t) { 383 Drawable icon = mIconCache.getAndInvalidateIfModified(t.key); 384 icon = icon != null ? icon : mDefaultIcon; 385 mLoadQueue.addTask(t); 386 t.notifyTaskDataLoaded(t.thumbnail, icon); 387 } 388 389 /** Releases the task resource data back into the pool. */ unloadTaskData(Task t)390 public void unloadTaskData(Task t) { 391 mLoadQueue.removeTask(t); 392 t.notifyTaskDataUnloaded(mDefaultIcon); 393 } 394 395 /** Completely removes the resource data from the pool. */ deleteTaskData(Task t, boolean notifyTaskDataUnloaded)396 public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) { 397 mLoadQueue.removeTask(t); 398 mIconCache.remove(t.key); 399 mActivityLabelCache.remove(t.key); 400 mContentDescriptionCache.remove(t.key); 401 if (notifyTaskDataUnloaded) { 402 t.notifyTaskDataUnloaded(mDefaultIcon); 403 } 404 } 405 406 /** 407 * Handles signals from the system, trimming memory when requested to prevent us from running 408 * out of memory. 409 */ onTrimMemory(int level)410 public synchronized void onTrimMemory(int level) { 411 switch (level) { 412 case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: 413 // Stop the loader immediately when the UI is no longer visible 414 stopLoader(); 415 mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded, 416 mMaxIconCacheSize / 2)); 417 break; 418 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: 419 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: 420 // We are leaving recents, so trim the data a bit 421 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2)); 422 mActivityInfoCache.trimToSize(Math.max(1, 423 ActivityManager.getMaxRecentTasksStatic() / 2)); 424 break; 425 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: 426 case ComponentCallbacks2.TRIM_MEMORY_MODERATE: 427 // We are going to be low on memory 428 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4)); 429 mActivityInfoCache.trimToSize(Math.max(1, 430 ActivityManager.getMaxRecentTasksStatic() / 4)); 431 break; 432 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: 433 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: 434 // We are low on memory, so release everything 435 mIconCache.evictAll(); 436 mActivityInfoCache.evictAll(); 437 // The cache is small, only clear the label cache when we are critical 438 mActivityLabelCache.evictAll(); 439 mContentDescriptionCache.evictAll(); 440 mThumbnailCache.evictAll(); 441 break; 442 default: 443 break; 444 } 445 } 446 447 /** 448 * Returns the cached task label if the task key is not expired, updating the cache if it is. 449 */ getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td)450 String getAndUpdateActivityTitle(Task.TaskKey taskKey, ActivityManager.TaskDescription td) { 451 SystemServicesProxy ssp = Recents.getSystemServices(); 452 453 // Return the task description label if it exists 454 if (td != null && td.getLabel() != null) { 455 return td.getLabel(); 456 } 457 // Return the cached activity label if it exists 458 String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey); 459 if (label != null) { 460 return label; 461 } 462 // All short paths failed, load the label from the activity info and cache it 463 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); 464 if (activityInfo != null) { 465 label = ssp.getBadgedActivityLabel(activityInfo, taskKey.userId); 466 mActivityLabelCache.put(taskKey, label); 467 return label; 468 } 469 // If the activity info does not exist or fails to load, return an empty label for now, 470 // but do not cache it 471 return ""; 472 } 473 474 /** 475 * Returns the cached task content description if the task key is not expired, updating the 476 * cache if it is. 477 */ getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td, Resources res)478 String getAndUpdateContentDescription(Task.TaskKey taskKey, ActivityManager.TaskDescription td, 479 Resources res) { 480 SystemServicesProxy ssp = Recents.getSystemServices(); 481 482 // Return the cached content description if it exists 483 String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey); 484 if (label != null) { 485 return label; 486 } 487 488 // All short paths failed, load the label from the activity info and cache it 489 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); 490 if (activityInfo != null) { 491 label = ssp.getBadgedContentDescription(activityInfo, taskKey.userId, td, res); 492 if (td == null) { 493 // Only add to the cache if the task description is null, otherwise, it is possible 494 // for the task description to change between calls without the last active time 495 // changing (ie. between preloading and Overview starting) which would lead to stale 496 // content descriptions 497 // TODO: Investigate improving this 498 mContentDescriptionCache.put(taskKey, label); 499 } 500 return label; 501 } 502 // If the content description does not exist, return an empty label for now, but do not 503 // cache it 504 return ""; 505 } 506 507 /** 508 * Returns the cached task icon if the task key is not expired, updating the cache if it is. 509 */ getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, Resources res, boolean loadIfNotCached)510 Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey, ActivityManager.TaskDescription td, 511 Resources res, boolean loadIfNotCached) { 512 SystemServicesProxy ssp = Recents.getSystemServices(); 513 514 // Return the cached activity icon if it exists 515 Drawable icon = mIconCache.getAndInvalidateIfModified(taskKey); 516 if (icon != null) { 517 return icon; 518 } 519 520 if (loadIfNotCached) { 521 // Return and cache the task description icon if it exists 522 icon = ssp.getBadgedTaskDescriptionIcon(td, taskKey.userId, res); 523 if (icon != null) { 524 mIconCache.put(taskKey, icon); 525 return icon; 526 } 527 528 // Load the icon from the activity info and cache it 529 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); 530 if (activityInfo != null) { 531 icon = ssp.getBadgedActivityIcon(activityInfo, taskKey.userId); 532 if (icon != null) { 533 mIconCache.put(taskKey, icon); 534 return icon; 535 } 536 } 537 } 538 // We couldn't load any icon 539 return null; 540 } 541 542 /** 543 * Returns the cached thumbnail if the task key is not expired, updating the cache if it is. 544 */ getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached, boolean storeInCache)545 synchronized ThumbnailData getAndUpdateThumbnail(Task.TaskKey taskKey, boolean loadIfNotCached, 546 boolean storeInCache) { 547 SystemServicesProxy ssp = Recents.getSystemServices(); 548 549 ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey); 550 if (cached != null) { 551 return cached; 552 } 553 554 cached = mTempCache.getAndInvalidateIfModified(taskKey); 555 if (cached != null) { 556 mThumbnailCache.put(taskKey, cached); 557 return cached; 558 } 559 560 if (loadIfNotCached) { 561 RecentsConfiguration config = Recents.getConfiguration(); 562 if (config.svelteLevel < RecentsConfiguration.SVELTE_DISABLE_LOADING) { 563 // Load the thumbnail from the system 564 ThumbnailData thumbnailData = ssp.getTaskThumbnail(taskKey.id, 565 true /* reducedResolution */); 566 if (thumbnailData.thumbnail != null) { 567 if (storeInCache) { 568 mThumbnailCache.put(taskKey, thumbnailData); 569 } 570 return thumbnailData; 571 } 572 } 573 } 574 575 // We couldn't load any thumbnail 576 return null; 577 } 578 579 /** 580 * Returns the task's primary color if possible, defaulting to the default color if there is 581 * no specified primary color. 582 */ getActivityPrimaryColor(ActivityManager.TaskDescription td)583 int getActivityPrimaryColor(ActivityManager.TaskDescription td) { 584 if (td != null && td.getPrimaryColor() != 0) { 585 return td.getPrimaryColor(); 586 } 587 return mDefaultTaskBarBackgroundColor; 588 } 589 590 /** 591 * Returns the task's background color if possible. 592 */ getActivityBackgroundColor(ActivityManager.TaskDescription td)593 int getActivityBackgroundColor(ActivityManager.TaskDescription td) { 594 if (td != null && td.getBackgroundColor() != 0) { 595 return td.getBackgroundColor(); 596 } 597 return mDefaultTaskViewBackgroundColor; 598 } 599 600 /** 601 * Returns the activity info for the given task key, retrieving one from the system if the 602 * task key is expired. 603 */ getAndUpdateActivityInfo(Task.TaskKey taskKey)604 ActivityInfo getAndUpdateActivityInfo(Task.TaskKey taskKey) { 605 SystemServicesProxy ssp = Recents.getSystemServices(); 606 ComponentName cn = taskKey.getComponent(); 607 ActivityInfo activityInfo = mActivityInfoCache.get(cn); 608 if (activityInfo == null) { 609 activityInfo = ssp.getActivityInfo(cn, taskKey.userId); 610 if (cn == null || activityInfo == null) { 611 Log.e(TAG, "Unexpected null component name or activity info: " + cn + ", " + 612 activityInfo); 613 return null; 614 } 615 mActivityInfoCache.put(cn, activityInfo); 616 } 617 return activityInfo; 618 } 619 620 /** 621 * Starts loading tasks. 622 */ startLoader(Context ctx)623 public void startLoader(Context ctx) { 624 mLoader.start(ctx); 625 } 626 627 /** 628 * Stops the task loader and clears all queued, pending task loads. 629 */ stopLoader()630 private void stopLoader() { 631 mLoader.stop(); 632 mLoadQueue.clearTasks(); 633 } 634 635 /**** Event Bus Events ****/ 636 onBusEvent(PackagesChangedEvent event)637 public final void onBusEvent(PackagesChangedEvent event) { 638 // Remove all the cached activity infos for this package. The other caches do not need to 639 // be pruned at this time, as the TaskKey expiration checks will flush them next time their 640 // cached contents are requested 641 Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot(); 642 for (ComponentName cn : activityInfoCache.keySet()) { 643 if (cn.getPackageName().equals(event.packageName)) { 644 if (DEBUG) { 645 Log.d(TAG, "Removing activity info from cache: " + cn); 646 } 647 mActivityInfoCache.remove(cn); 648 } 649 } 650 } 651 dump(String prefix, PrintWriter writer)652 public synchronized void dump(String prefix, PrintWriter writer) { 653 String innerPrefix = prefix + " "; 654 655 writer.print(prefix); writer.println(TAG); 656 writer.print(prefix); writer.println("Icon Cache"); 657 mIconCache.dump(innerPrefix, writer); 658 writer.print(prefix); writer.println("Thumbnail Cache"); 659 mThumbnailCache.dump(innerPrefix, writer); 660 writer.print(prefix); writer.println("Temp Thumbnail Cache"); 661 mTempCache.dump(innerPrefix, writer); 662 } 663 } 664