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