blob: 012913a60b66ead3a9e7112aed7e0e829ba6d5d5 [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.recents.model;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.Trace;
import android.util.Log;
import android.util.LruCache;
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.recents.model.RecentsTaskLoadPlan.Options;
import com.android.systemui.recents.model.RecentsTaskLoadPlan.PreloadOptions;
import com.android.systemui.shared.recents.model.IconLoader;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.recents.model.TaskKeyLruCache;
import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import java.io.PrintWriter;
import java.util.Map;
/**
* Recents task loader
*/
public class RecentsTaskLoader {
private static final String TAG = "RecentsTaskLoader";
private static final boolean DEBUG = false;
/** Levels of svelte in increasing severity/austerity. */
// No svelting.
public static final int SVELTE_NONE = 0;
// Limit thumbnail cache to number of visible thumbnails when Recents was loaded, disable
// caching thumbnails as you scroll.
public static final int SVELTE_LIMIT_CACHE = 1;
// Disable the thumbnail cache, load thumbnails asynchronously when the activity loads and
// evict all thumbnails when hidden.
public static final int SVELTE_DISABLE_CACHE = 2;
// Disable all thumbnail loading.
public static final int SVELTE_DISABLE_LOADING = 3;
// This activity info LruCache is useful because it can be expensive to retrieve ActivityInfos
// for many tasks, which we use to get the activity labels and icons. Unlike the other caches
// below, this is per-package so we can't invalidate the items in the cache based on the last
// active time. Instead, we rely on the PackageMonitor to keep us informed whenever a
// package in the cache has been updated, so that we may remove it.
private final LruCache<ComponentName, ActivityInfo> mActivityInfoCache;
private final TaskKeyLruCache<Drawable> mIconCache;
private final TaskKeyLruCache<String> mActivityLabelCache;
private final TaskKeyLruCache<String> mContentDescriptionCache;
private final TaskResourceLoadQueue mLoadQueue;
private final IconLoader mIconLoader;
private final BackgroundTaskLoader mLoader;
private final HighResThumbnailLoader mHighResThumbnailLoader;
@GuardedBy("this")
private final TaskKeyStrongCache<ThumbnailData> mThumbnailCache = new TaskKeyStrongCache<>();
@GuardedBy("this")
private final TaskKeyStrongCache<ThumbnailData> mTempCache = new TaskKeyStrongCache<>();
private final int mMaxThumbnailCacheSize;
private final int mMaxIconCacheSize;
private int mNumVisibleTasksLoaded;
private int mSvelteLevel;
private int mDefaultTaskBarBackgroundColor;
private int mDefaultTaskViewBackgroundColor;
private EvictionCallback mClearActivityInfoOnEviction = new EvictionCallback() {
@Override
public void onEntryEvicted(TaskKey key) {
if (key != null) {
mActivityInfoCache.remove(key.getComponent());
}
}
};
public RecentsTaskLoader(Context context, int maxThumbnailCacheSize, int maxIconCacheSize,
int svelteLevel) {
mMaxThumbnailCacheSize = maxThumbnailCacheSize;
mMaxIconCacheSize = maxIconCacheSize;
mSvelteLevel = svelteLevel;
// Initialize the proxy, cache and loaders
int numRecentTasks = ActivityTaskManager.getMaxRecentTasksStatic();
mHighResThumbnailLoader = new HighResThumbnailLoader(ActivityManagerWrapper.getInstance(),
Looper.getMainLooper(), ActivityManager.isLowRamDeviceStatic());
mLoadQueue = new TaskResourceLoadQueue();
mIconCache = new TaskKeyLruCache<>(mMaxIconCacheSize, mClearActivityInfoOnEviction);
mActivityLabelCache = new TaskKeyLruCache<>(numRecentTasks, mClearActivityInfoOnEviction);
mContentDescriptionCache = new TaskKeyLruCache<>(numRecentTasks,
mClearActivityInfoOnEviction);
mActivityInfoCache = new LruCache<>(numRecentTasks);
mIconLoader = createNewIconLoader(context, mIconCache, mActivityInfoCache);
mLoader = new BackgroundTaskLoader(mLoadQueue, mIconLoader, mHighResThumbnailLoader);
}
protected IconLoader createNewIconLoader(Context context,TaskKeyLruCache<Drawable> iconCache,
LruCache<ComponentName, ActivityInfo> activityInfoCache) {
return new IconLoader.DefaultIconLoader(context, iconCache, activityInfoCache);
}
/**
* Sets the default task bar/view colors if none are provided by the app.
*/
public void setDefaultColors(int defaultTaskBarBackgroundColor,
int defaultTaskViewBackgroundColor) {
mDefaultTaskBarBackgroundColor = defaultTaskBarBackgroundColor;
mDefaultTaskViewBackgroundColor = defaultTaskViewBackgroundColor;
}
/** Returns the size of the app icon cache. */
public int getIconCacheSize() {
return mMaxIconCacheSize;
}
/** Returns the size of the thumbnail cache. */
public int getThumbnailCacheSize() {
return mMaxThumbnailCacheSize;
}
public HighResThumbnailLoader getHighResThumbnailLoader() {
return mHighResThumbnailLoader;
}
/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId) {
preloadTasks(plan, runningTaskId, ActivityManagerWrapper.getInstance().getCurrentUserId());
}
/** Preloads recents tasks using the specified plan to store the output. */
public synchronized void preloadTasks(RecentsTaskLoadPlan plan, int runningTaskId,
int currentUserId) {
try {
Trace.beginSection("preloadPlan");
plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
} finally {
Trace.endSection();
}
}
/** Begins loading the heavy task data according to the specified options. */
public synchronized void loadTasks(RecentsTaskLoadPlan plan, Options opts) {
if (opts == null) {
throw new RuntimeException("Requires load options");
}
if (opts.onlyLoadForCache && opts.loadThumbnails) {
// If we are loading for the cache, we'd like to have the real cache only include the
// visible thumbnails. However, we also don't want to reload already cached thumbnails.
// Thus, we copy over the current entries into a second cache, and clear the real cache,
// such that the real cache only contains visible thumbnails.
mTempCache.copyEntries(mThumbnailCache);
mThumbnailCache.evictAll();
}
plan.executePlan(opts, this);
mTempCache.evictAll();
if (!opts.onlyLoadForCache) {
mNumVisibleTasksLoaded = opts.numVisibleTasks;
}
}
/**
* Acquires the task resource data directly from the cache, loading if necessary.
*/
public void loadTaskData(Task t) {
Drawable icon = mIconCache.getAndInvalidateIfModified(t.key);
icon = icon != null ? icon : mIconLoader.getDefaultIcon(t.key.userId);
mLoadQueue.addTask(t);
t.notifyTaskDataLoaded(t.thumbnail, icon);
}
/** Releases the task resource data back into the pool. */
public void unloadTaskData(Task t) {
mLoadQueue.removeTask(t);
t.notifyTaskDataUnloaded(mIconLoader.getDefaultIcon(t.key.userId));
}
/** Completely removes the resource data from the pool. */
public void deleteTaskData(Task t, boolean notifyTaskDataUnloaded) {
mLoadQueue.removeTask(t);
mIconCache.remove(t.key);
mActivityLabelCache.remove(t.key);
mContentDescriptionCache.remove(t.key);
if (notifyTaskDataUnloaded) {
t.notifyTaskDataUnloaded(mIconLoader.getDefaultIcon(t.key.userId));
}
}
/**
* Handles signals from the system, trimming memory when requested to prevent us from running
* out of memory.
*/
public synchronized void onTrimMemory(int level) {
switch (level) {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
// Stop the loader immediately when the UI is no longer visible
stopLoader();
mIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
mMaxIconCacheSize / 2));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
// We are leaving recents, so trim the data a bit
mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
mActivityInfoCache.trimToSize(Math.max(1,
ActivityTaskManager.getMaxRecentTasksStatic() / 2));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
// We are going to be low on memory
mIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
mActivityInfoCache.trimToSize(Math.max(1,
ActivityTaskManager.getMaxRecentTasksStatic() / 4));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
// We are low on memory, so release everything
mIconCache.evictAll();
mActivityInfoCache.evictAll();
// The cache is small, only clear the label cache when we are critical
mActivityLabelCache.evictAll();
mContentDescriptionCache.evictAll();
mThumbnailCache.evictAll();
break;
default:
break;
}
}
public void onPackageChanged(String packageName) {
// Remove all the cached activity infos for this package. The other caches do not need to
// be pruned at this time, as the TaskKey expiration checks will flush them next time their
// cached contents are requested
Map<ComponentName, ActivityInfo> activityInfoCache = mActivityInfoCache.snapshot();
for (ComponentName cn : activityInfoCache.keySet()) {
if (cn.getPackageName().equals(packageName)) {
if (DEBUG) {
Log.d(TAG, "Removing activity info from cache: " + cn);
}
mActivityInfoCache.remove(cn);
}
}
}
/**
* Returns the cached task label if the task key is not expired, updating the cache if it is.
*/
String getAndUpdateActivityTitle(TaskKey taskKey, ActivityManager.TaskDescription td) {
// Return the task description label if it exists
if (td != null && td.getLabel() != null) {
return td.getLabel();
}
// Return the cached activity label if it exists
String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey);
if (label != null) {
return label;
}
// All short paths failed, load the label from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
label = ActivityManagerWrapper.getInstance().getBadgedActivityLabel(activityInfo,
taskKey.userId);
mActivityLabelCache.put(taskKey, label);
return label;
}
// If the activity info does not exist or fails to load, return an empty label for now,
// but do not cache it
return "";
}
/**
* Returns the cached task content description if the task key is not expired, updating the
* cache if it is.
*/
String getAndUpdateContentDescription(TaskKey taskKey, ActivityManager.TaskDescription td) {
// Return the cached content description if it exists
String label = mContentDescriptionCache.getAndInvalidateIfModified(taskKey);
if (label != null) {
return label;
}
// All short paths failed, load the label from the activity info and cache it
ActivityInfo activityInfo = getAndUpdateActivityInfo(taskKey);
if (activityInfo != null) {
label = ActivityManagerWrapper.getInstance().getBadgedContentDescription(
activityInfo, taskKey.userId, td);
if (td == null) {
// Only add to the cache if the task description is null, otherwise, it is possible
// for the task description to change between calls without the last active time
// changing (ie. between preloading and Overview starting) which would lead to stale
// content descriptions
// TODO: Investigate improving this
mContentDescriptionCache.put(taskKey, label);
}
return label;
}
// If the content description does not exist, return an empty label for now, but do not
// cache it
return "";
}
/**
* Returns the cached task icon if the task key is not expired, updating the cache if it is.
*/
Drawable getAndUpdateActivityIcon(TaskKey taskKey, ActivityManager.TaskDescription td,
boolean loadIfNotCached) {
return mIconLoader.getAndInvalidateIfModified(taskKey, td, loadIfNotCached);
}
/**
* Returns the cached thumbnail if the task key is not expired, updating the cache if it is.
*/
synchronized ThumbnailData getAndUpdateThumbnail(TaskKey taskKey, boolean loadIfNotCached,
boolean storeInCache) {
ThumbnailData cached = mThumbnailCache.getAndInvalidateIfModified(taskKey);
if (cached != null) {
return cached;
}
cached = mTempCache.getAndInvalidateIfModified(taskKey);
if (cached != null) {
mThumbnailCache.put(taskKey, cached);
return cached;
}
if (loadIfNotCached) {
if (mSvelteLevel < SVELTE_DISABLE_LOADING) {
// Load the thumbnail from the system
ThumbnailData thumbnailData = ActivityManagerWrapper.getInstance().getTaskThumbnail(
taskKey.id, true /* reducedResolution */);
if (thumbnailData.thumbnail != null) {
if (storeInCache) {
mThumbnailCache.put(taskKey, thumbnailData);
}
return thumbnailData;
}
}
}
// We couldn't load any thumbnail
return null;
}
/**
* Returns the task's primary color if possible, defaulting to the default color if there is
* no specified primary color.
*/
int getActivityPrimaryColor(ActivityManager.TaskDescription td) {
if (td != null && td.getPrimaryColor() != 0) {
return td.getPrimaryColor();
}
return mDefaultTaskBarBackgroundColor;
}
/**
* Returns the task's background color if possible.
*/
int getActivityBackgroundColor(ActivityManager.TaskDescription td) {
if (td != null && td.getBackgroundColor() != 0) {
return td.getBackgroundColor();
}
return mDefaultTaskViewBackgroundColor;
}
/**
* Returns the activity info for the given task key, retrieving one from the system if the
* task key is expired.
*/
ActivityInfo getAndUpdateActivityInfo(TaskKey taskKey) {
return mIconLoader.getAndUpdateActivityInfo(taskKey);
}
/**
* Starts loading tasks.
*/
public void startLoader(Context ctx) {
mLoader.start(ctx);
}
/**
* Stops the task loader and clears all queued, pending task loads.
*/
private void stopLoader() {
mLoader.stop();
mLoadQueue.clearTasks();
}
public synchronized void dump(String prefix, PrintWriter writer) {
String innerPrefix = prefix + " ";
writer.print(prefix); writer.println(TAG);
writer.print(prefix); writer.println("Icon Cache");
mIconCache.dump(innerPrefix, writer);
writer.print(prefix); writer.println("Thumbnail Cache");
mThumbnailCache.dump(innerPrefix, writer);
writer.print(prefix); writer.println("Temp Thumbnail Cache");
mTempCache.dump(innerPrefix, writer);
}
}