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