Home | History | Annotate | Download | only in model
      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 
     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 
    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      */
    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. */
    131     public int getIconCacheSize() {
    132         return mMaxIconCacheSize;
    133     }
    134 
    135     /** Returns the size of the thumbnail cache. */
    136     public int getThumbnailCacheSize() {
    137         return mMaxThumbnailCacheSize;
    138     }
    139 
    140     public HighResThumbnailLoader getHighResThumbnailLoader() {
    141         return mHighResThumbnailLoader;
    142     }
    143 
    144     /** Preloads recents tasks using the specified plan to store the output. */
    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. */
    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. */
    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      */
    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. */
    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. */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    387     ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) {
    388         return mIconLoader.getAndUpdateActivityInfo(taskKey);
    389     }
    390 
    391     /**
    392      * Starts loading tasks.
    393      */
    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      */
    401     private void stopLoader() {
    402         mLoader.stop();
    403         mLoadQueue.clearTasks();
    404     }
    405 
    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