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.shared.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.graphics.drawable.Drawable; 25 import android.os.Looper; 26 import android.os.Trace; 27 import android.util.Log; 28 import android.util.LruCache; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options; 32 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions; 33 import com.android.systemui.shared.recents.model.Task.TaskKey; 34 import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback; 35 import com.android.systemui.shared.system.ActivityManagerWrapper; 36 37 import java.io.PrintWriter; 38 import java.util.Map; 39 40 41 /** 42 * Recents task loader 43 */ 44 public class RecentsTaskLoader { 45 private static final String TAG = "RecentsTaskLoader"; 46 private static final boolean DEBUG = false; 47 48 /** Levels of svelte in increasing severity/austerity. */ 49 // No svelting. 50 public static final int SVELTE_NONE = 0; 51 // Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable 52 // caching thumbnails as you scroll. 53 public static final int SVELTE_LIMIT_CACHE = 1; 54 // Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and 55 // evict all thumbnails when hidden. 56 public static final int SVELTE_DISABLE_CACHE = 2; 57 // Disable all thumbnail loading. 58 public static final int SVELTE_DISABLE_LOADING = 3; 59 60 // This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos 61 // for many tasks, which we use to get the activity labels and icons. Unlike the other caches 62 // below, this is per-package so we can't invalidate the items in the cache based on the last 63 // active time. Instead, we rely on the PackageMonitor to keep us informed whenever a 64 // package in the cache has been updated, so that we may remove it. 65 private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache; 66 private final TaskKeyLruCache<Drawable> mIconCache; 67 private final TaskKeyLruCache<String> mActivityLabelCache; 68 private final TaskKeyLruCache<String> mContentDescriptionCache; 69 private final TaskResourceLoadQueue mLoadQueue; 70 private final IconLoader mIconLoader; 71 private final BackgroundTaskLoader mLoader; 72 private final HighResThumbnailLoader mHighResThumbnailLoader; 73 @GuardedBy("this") 74 private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>(); 75 @GuardedBy("this") 76 private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>(); 77 private final int mMaxThumbnailCacheSize; 78 private final int mMaxIconCacheSize; 79 private int mNumVisibleTasksLoaded; 80 private int mSvelteLevel; 81 82 private int mDefaultTaskBarBackgroundColor; 83 private int mDefaultTaskViewBackgroundColor; 84 85 private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() { 86 @Override 87 public void onEntryEvicted(TaskKey key) { 88 if (key != null) { 89 mActivityInfoCache.remove(key.getComponent()); 90 } 91 } 92 }; 93 RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize, int svelteLevel)94 public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize, 95 int svelteLevel) { 96 mMaxThumbnailCacheSize = maxThumbnailCacheSize; 97 mMaxIconCacheSize = maxIconCacheSize; 98 mSvelteLevel = svelteLevel; 99 100 // Initialize the proxy, cache and loaders 101 int numRecentTasks = ActivityManager.getMaxRecentTasksStatic(); 102 mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(), 103 Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic()); 104 mLoadQueue = new TaskResourceLoadQueue(); 105 mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction); 106 mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction); 107 mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks, 108 mClearActivityInfoOnEviction); 109 mActivityInfoCache = new LruCache<>(numRecentTasks); 110 111 mIconLoader = createNewIconLoader(context, mIconCache, mActivityInfoCache); 112 mLoader = new BackgroundTaskLoader(mLoadQueue, mIconLoader, 113 mHighResThumbnailLoader::setTaskLoadQueueIdle); 114 } 115 createNewIconLoader(Context context,TaskKeyLruCache<Drawable> iconCache, LruCache<ComponentName, ActivityInfo> activityInfoCache)116 protected IconLoader createNewIconLoader(Context context,TaskKeyLruCache<Drawable> iconCache, 117 LruCache<ComponentName, ActivityInfo> activityInfoCache) { 118 return new IconLoader.DefaultIconLoader(context, iconCache, activityInfoCache); 119 } 120 121 /** 122 * Sets the default task bar/view colors if none are provided by the app. 123 */ setDefaultColors(int defaultTaskBarBackgroundColor, int defaultTaskViewBackgroundColor)124 public void setDefaultColors(int defaultTaskBarBackgroundColor, 125 int defaultTaskViewBackgroundColor) { 126 mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor; 127 mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor; 128 } 129 130 /** Returns the size of the app icon cache. */ getIconCacheSize()131 public int getIconCacheSize() { 132 return mMaxIconCacheSize; 133 } 134 135 /** Returns the size of the thumbnail cache. */ getThumbnailCacheSize()136 public int getThumbnailCacheSize() { 137 return mMaxThumbnailCacheSize; 138 } 139 getHighResThumbnailLoader()140 public HighResThumbnailLoader getHighResThumbnailLoader() { 141 return mHighResThumbnailLoader; 142 } 143 144 /** Preloads recents tasks using the specified plan to store the output. */ preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId)145 public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) { 146 preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId()); 147 } 148 149 /** Preloads recents tasks using the specified plan to store the output. */ preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, int currentUserId)150 public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId, 151 int currentUserId) { 152 try { 153 Trace.beginSection("preloadPlan"); 154 plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId); 155 } finally { 156 Trace.endSection(); 157 } 158 } 159 160 /** Begins loading the heavy task data according to the specified options. */ loadTasks(RecentsTaskLoadPlan plan, Options opts)161 public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) { 162 if (opts == null) { 163 throw new RuntimeException("Requires load options"); 164 } 165 if (opts.onlyLoadForCache && opts.loadThumbnails) { 166 // If we are loading for the cache, we'd like to have the real cache only include the 167 // visible thumbnails. However, we also don't want to reload already cached thumbnails. 168 // Thus, we copy over the current entries into a second cache, and clear the real cache, 169 // such that the real cache only contains visible thumbnails. 170 mTempCache.copyEntries(mThumbnailCache); 171 mThumbnailCache.evictAll(); 172 } 173 plan.executePlan(opts, this); 174 mTempCache.evictAll(); 175 if (!opts.onlyLoadForCache) { 176 mNumVisibleTasksLoaded = opts.numVisibleTasks; 177 } 178 } 179 180 /** 181 * Acquires the task resource data directly from the cache, loading if necessary. 182 */ loadTaskData(Task t)183 public void loadTaskData(Task t) { 184 Drawable icon = mIconCache.getAndInvalidateIfModified(t.key); 185 icon = icon != null ? icon : mIconLoader.getDefaultIcon(t.key.userId); 186 mLoadQueue.addTask(t); 187 t.notifyTaskDataLoaded(t.thumbnail, icon); 188 } 189 190 /** Releases the task resource data back into the pool. */ unloadTaskData(Task t)191 public void unloadTaskData(Task t) { 192 mLoadQueue.removeTask(t); 193 t.notifyTaskDataUnloaded(mIconLoader.getDefaultIcon(t.key.userId)); 194 } 195 196 /** Completely removes the resource data from the pool. */ deleteTaskData(Task t, boolean notifyTaskDataUnloaded)197 public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) { 198 mLoadQueue.removeTask(t); 199 mIconCache.remove(t.key); 200 mActivityLabelCache.remove(t.key); 201 mContentDescriptionCache.remove(t.key); 202 if (notifyTaskDataUnloaded) { 203 t.notifyTaskDataUnloaded(mIconLoader.getDefaultIcon(t.key.userId)); 204 } 205 } 206 207 /** 208 * Handles signals from the system, trimming memory when requested to prevent us from running 209 * out of memory. 210 */ onTrimMemory(int level)211 public synchronized void onTrimMemory(int level) { 212 switch (level) { 213 case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: 214 // Stop the loader immediately when the UI is no longer visible 215 stopLoader(); 216 mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded, 217 mMaxIconCacheSize / 2)); 218 break; 219 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: 220 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: 221 // We are leaving recents, so trim the data a bit 222 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2)); 223 mActivityInfoCache.trimToSize(Math.max(1, 224 ActivityManager.getMaxRecentTasksStatic() / 2)); 225 break; 226 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: 227 case ComponentCallbacks2.TRIM_MEMORY_MODERATE: 228 // We are going to be low on memory 229 mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4)); 230 mActivityInfoCache.trimToSize(Math.max(1, 231 ActivityManager.getMaxRecentTasksStatic() / 4)); 232 break; 233 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: 234 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: 235 // We are low on memory, so release everything 236 mIconCache.evictAll(); 237 mActivityInfoCache.evictAll(); 238 // The cache is small, only clear the label cache when we are critical 239 mActivityLabelCache.evictAll(); 240 mContentDescriptionCache.evictAll(); 241 mThumbnailCache.evictAll(); 242 break; 243 default: 244 break; 245 } 246 } 247 onPackageChanged(String packageName)248 public void onPackageChanged(String packageName) { 249 // Remove all the cached activity infos for this package. The other caches do not need to 250 // be pruned at this time, as the TaskKey expiration checks will flush them next time their 251 // cached contents are requested 252 Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot(); 253 for (ComponentName cn : activityInfoCache.keySet()) { 254 if (cn.getPackageName().equals(packageName)) { 255 if (DEBUG) { 256 Log.d(TAG, "Removing activity info from cache: " + cn); 257 } 258 mActivityInfoCache.remove(cn); 259 } 260 } 261 } 262 263 /** 264 * Returns the cached task label if the task key is not expired, updating the cache if it is. 265 */ getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td)266 String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) { 267 // Return the task description label if it exists 268 if (td != null && td.getLabel() != null) { 269 return td.getLabel(); 270 } 271 // Return the cached activity label if it exists 272 String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey); 273 if (label != null) { 274 return label; 275 } 276 // All short paths failed, load the label from the activity info and cache it 277 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); 278 if (activityInfo != null) { 279 label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo, 280 taskKey.userId); 281 mActivityLabelCache.put(taskKey, label); 282 return label; 283 } 284 // If the activity info does not exist or fails to load, return an empty label for now, 285 // but do not cache it 286 return ""; 287 } 288 289 /** 290 * Returns the cached task content description if the task key is not expired, updating the 291 * cache if it is. 292 */ getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td)293 String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) { 294 // Return the cached content description if it exists 295 String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey); 296 if (label != null) { 297 return label; 298 } 299 300 // All short paths failed, load the label from the activity info and cache it 301 ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey); 302 if (activityInfo != null) { 303 label = ActivityManagerWrapper.getInstance().getBadgedContentDescription( 304 activityInfo, taskKey.userId, td); 305 if (td == null) { 306 // Only add to the cache if the task description is null, otherwise, it is possible 307 // for the task description to change between calls without the last active time 308 // changing (ie. between preloading and Overview starting) which would lead to stale 309 // content descriptions 310 // TODO: Investigate improving this 311 mContentDescriptionCache.put(taskKey, label); 312 } 313 return label; 314 } 315 // If the content description does not exist, return an empty label for now, but do not 316 // cache it 317 return ""; 318 } 319 320 /** 321 * Returns the cached task icon if the task key is not expired, updating the cache if it is. 322 */ getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td, boolean loadIfNotCached)323 Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td, 324 boolean loadIfNotCached) { 325 return mIconLoader.getAndInvalidateIfModified(taskKey, td, loadIfNotCached); 326 } 327 328 /** 329 * Returns the cached thumbnail if the task key is not expired, updating the cache if it is. 330 */ getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached, boolean storeInCache)331 synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached, 332 boolean storeInCache) { 333 ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey); 334 if (cached != null) { 335 return cached; 336 } 337 338 cached = mTempCache.getAndInvalidateIfModified(taskKey); 339 if (cached != null) { 340 mThumbnailCache.put(taskKey, cached); 341 return cached; 342 } 343 344 if (loadIfNotCached) { 345 if (mSvelteLevel < SVELTE_DISABLE_LOADING) { 346 // Load the thumbnail from the system 347 ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail( 348 taskKey.id, true /* reducedResolution */); 349 if (thumbnailData.thumbnail != null) { 350 if (storeInCache) { 351 mThumbnailCache.put(taskKey, thumbnailData); 352 } 353 return thumbnailData; 354 } 355 } 356 } 357 358 // We couldn't load any thumbnail 359 return null; 360 } 361 362 /** 363 * Returns the task's primary color if possible, defaulting to the default color if there is 364 * no specified primary color. 365 */ getActivityPrimaryColor(ActivityManager.TaskDescription td)366 int getActivityPrimaryColor(ActivityManager.TaskDescription td) { 367 if (td != null && td.getPrimaryColor() != 0) { 368 return td.getPrimaryColor(); 369 } 370 return mDefaultTaskBarBackgroundColor; 371 } 372 373 /** 374 * Returns the task's background color if possible. 375 */ getActivityBackgroundColor(ActivityManager.TaskDescription td)376 int getActivityBackgroundColor(ActivityManager.TaskDescription td) { 377 if (td != null && td.getBackgroundColor() != 0) { 378 return td.getBackgroundColor(); 379 } 380 return mDefaultTaskViewBackgroundColor; 381 } 382 383 /** 384 * Returns the activity info for the given task key, retrieving one from the system if the 385 * task key is expired. 386 */ getAndUpdateActivityInfo(TaskKey taskKey)387 ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) { 388 return mIconLoader.getAndUpdateActivityInfo(taskKey); 389 } 390 391 /** 392 * Starts loading tasks. 393 */ startLoader(Context ctx)394 public void startLoader(Context ctx) { 395 mLoader.start(ctx); 396 } 397 398 /** 399 * Stops the task loader and clears all queued, pending task loads. 400 */ stopLoader()401 private void stopLoader() { 402 mLoader.stop(); 403 mLoadQueue.clearTasks(); 404 } 405 dump(String prefix, PrintWriter writer)406 public synchronized void dump(String prefix, PrintWriter writer) { 407 String innerPrefix = prefix + " "; 408 409 writer.print(prefix); writer.println(TAG); 410 writer.print(prefix); writer.println("Icon Cache"); 411 mIconCache.dump(innerPrefix, writer); 412 writer.print(prefix); writer.println("Thumbnail Cache"); 413 mThumbnailCache.dump(innerPrefix, writer); 414 writer.print(prefix); writer.println("Temp Thumbnail Cache"); 415 mTempCache.dump(innerPrefix, writer); 416 } 417 } 418