Preload only visible thumbnails and task icons. (Bug 17672056, Bug 18291345)

This change ensures that only the number of visible thumbnails and task icons are
loaded to minimize the delay required when initializing the stack without the 
cache.  In addition, this change reduces the number of times that the task stack
is recomposed when launching recents (in addition to the number of calls to 
getRecentTasks()).

There is also a fix to a regression where the exit trigger is not run when the
task stack view is empty.

Change-Id: I75834ff3c57c0e5dad6252b982f71c6e740071f2
This commit is contained in:
Winson Chung
2014-11-07 15:02:38 -08:00
parent a11bb74271
commit a91c293be2
10 changed files with 507 additions and 250 deletions

View File

@@ -776,7 +776,7 @@
This needs to match the constants in
policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
-->
<integer name="config_longPressOnHomeBehavior">1</integer>
<integer name="config_longPressOnHomeBehavior">0</integer>
<!-- Control the behavior when the user double-taps the home button.
0 - Nothing

View File

@@ -40,6 +40,7 @@ import com.android.systemui.R;
import com.android.systemui.RecentsComponent;
import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskGrouping;
@@ -63,6 +64,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab";
final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey";
final public static String EXTRA_REUSE_TASK_STACK_VIEWS = "recents.reuseTaskStackViews";
final public static String EXTRA_NUM_VISIBLE_TASKS = "recents.numVisibleTasks";
final public static String EXTRA_NUM_VISIBLE_THUMBNAILS = "recents.numVisibleThumbnails";
final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation";
final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
@@ -75,6 +78,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
static RecentsComponent.Callbacks sRecentsComponentCallbacks;
static RecentsTaskLoadPlan sInstanceLoadPlan;
Context mContext;
LayoutInflater mInflater;
@@ -133,8 +137,15 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
// When we start, preload the metadata associated with the previous tasks
RecentsTaskLoader.getInstance().preload(mContext, RecentsTaskLoader.ALL_TASKS);
// When we start, preload the metadata and icons associated with the recent tasks.
// We can use a new plan since the caches will be the same.
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize();
launchOpts.loadThumbnails = false;
loader.loadTasks(mContext, plan, launchOpts);
}
public void onBootCompleted() {
@@ -182,9 +193,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
public void onPreloadRecents() {
// When we start, preload the metadata associated with the previous tasks
RecentsTaskLoader.getInstance().preload(mContext,
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
// Preload only the raw task list into a new load plan (which will be consumed by the
// RecentsActivity)
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
sInstanceLoadPlan.preloadRawTasks(true);
}
public void onCancelPreloadingRecents() {
@@ -193,8 +206,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
void showRelativeAffiliatedTask(boolean showNextTask) {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(),
-1, -1, RecentsTaskLoader.ALL_TASKS, false, true, null, null);
RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
loader.preloadTasks(plan, true /* isTopTaskHome */);
TaskStack stack = plan.getTaskStack();
// Return early if there are no tasks
if (stack.getTaskCount() == 0) return;
@@ -402,11 +417,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
* Creates the activity options for an app->recents transition.
*/
ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
boolean isTopTaskHome) {
TaskStack stack, TaskStackView stackView) {
// Update the destination rect
Task toTask = new Task();
TaskViewTransform toTransform = getThumbnailTransitionTransform(topTask.id, isTopTaskHome,
toTask);
TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
topTask.id, toTask);
if (toTransform != null && toTask.key != null) {
Rect toTaskRect = toTransform.rect;
int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale);
@@ -434,16 +449,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
/** Returns the transition rect for the given task id. */
TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome,
Task runningTaskOut) {
// Get the stack of tasks that we are animating into
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(),
runningTaskId, -1, RecentsTaskLoader.ALL_TASKS, false, isTopTaskHome, null, null);
if (stack.getTaskCount() == 0) {
return null;
}
TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
int runningTaskId, Task runningTaskOut) {
// Find the running task in the TaskStack
Task task = null;
ArrayList<Task> tasks = stack.getTasks();
@@ -465,30 +472,42 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
// Get the transform for the running task
mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
mDummyStackView.getScroller().setStackScrollToInitialState();
mTmpTransform = mDummyStackView.getStackAlgorithm().getStackTransform(task,
mDummyStackView.getScroller().getStackScroll(), mTmpTransform, null);
stackView.getScroller().setStackScrollToInitialState();
mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
stackView.getScroller().getStackScroll(), mTmpTransform, null);
return mTmpTransform;
}
/** Starts the recents activity */
void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
// If Recents is not the front-most activity and we should animate into it. If
// the activity at the root of the top task stack in the home stack, then we just do a
// simple transition. Otherwise, we animate to the rects defined by the Recents service,
// which can differ depending on the number of items in the list.
SystemServicesProxy ssp = mSystemServicesProxy;
List<ActivityManager.RecentTaskInfo> recentTasks =
ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
boolean useThumbnailTransition = !isTopTaskHome;
boolean hasRecentTasks = !recentTasks.isEmpty();
if (sInstanceLoadPlan == null) {
// Create a new load plan if onPreloadRecents() was never triggered
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
}
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
TaskStack stack = sInstanceLoadPlan.getTaskStack();
// Prepare the dummy stack for the transition
mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome);
TaskStackViewLayoutAlgorithm.VisibilityReport stackVr =
mDummyStackView.computeStackVisibilityReport();
boolean hasRecentTasks = stack.getTaskCount() > 0;
boolean useThumbnailTransition = !isTopTaskHome && hasRecentTasks;
if (useThumbnailTransition) {
// Ensure that we load the running task's icon
RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options();
launchOpts.runningTaskId = topTask.id;
launchOpts.loadThumbnails = false;
loader.loadTasks(mContext, sInstanceLoadPlan, launchOpts);
// Try starting with a thumbnail transition
ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, isTopTaskHome);
ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
mDummyStackView);
if (opts != null) {
startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL);
startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL, stackVr);
} else {
// Fall through below to the non-thumbnail transition
useThumbnailTransition = false;
@@ -522,11 +541,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
startAlternateRecentsActivity(topTask, opts,
fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME);
fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME, stackVr);
} else {
// Otherwise we do the normal fade from an unknown source
ActivityOptions opts = getUnknownTransitionActivityOptions();
startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME);
startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME, stackVr);
}
}
mLastToggleTime = System.currentTimeMillis();
@@ -534,7 +553,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Starts the recents activity */
void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask,
ActivityOptions opts, String extraFlag) {
ActivityOptions opts, String extraFlag,
TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
Intent intent = new Intent(sToggleRecentsAction);
intent.setClassName(sRecentsPackage, sRecentsActivity);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
@@ -546,6 +566,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
intent.putExtra(EXTRA_REUSE_TASK_STACK_VIEWS, mCanReuseTaskStackViews);
intent.putExtra(EXTRA_NUM_VISIBLE_TASKS, vr.numVisibleTasks);
intent.putExtra(EXTRA_NUM_VISIBLE_THUMBNAILS, vr.numVisibleThumbnails);
if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else {
@@ -566,6 +588,15 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
}
}
/**
* Returns the preloaded load plan and invalidates it.
*/
public static RecentsTaskLoadPlan consumeInstanceLoadPlan() {
RecentsTaskLoadPlan plan = sInstanceLoadPlan;
sInstanceLoadPlan = null;
return plan;
}
/**** OnAnimationStartedListener Implementation ****/
@Override

View File

@@ -64,11 +64,6 @@ public class Constants {
public static String DebugModeVersion = "A";
}
public static class RecentsTaskLoader {
// XXX: This should be calculated on the first load
public static final int PreloadFirstTasksCount = 6;
}
public static class TaskStackView {
public static final int TaskStackOverscrollRange = 150;
public static final int FilterStartDelay = 25;

View File

@@ -27,10 +27,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.util.Pair;
import android.view.KeyEvent;
@@ -42,6 +39,7 @@ import com.android.systemui.recents.misc.DebugTrigger;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.RecentsTaskLoadPlan;
import com.android.systemui.recents.model.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task;
@@ -163,9 +161,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
if (action.equals(Intent.ACTION_SCREEN_OFF)) {
// When the screen turns off, dismiss Recents to Home
dismissRecentsToHome(false);
// Start preloading some tasks in the background
RecentsTaskLoader.getInstance().preload(RecentsActivity.this,
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount);
// Preload the metadata for all tasks in the background
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
RecentsTaskLoadPlan plan = loader.createLoadPlan(context);
loader.preloadTasks(plan, true /* isTopTaskHome */);
} else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
// When the search activity changes, update the Search widget
refreshSearchWidget();
@@ -188,6 +187,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
// Update the configuration based on the launch intent
boolean fromSearchHome = launchIntent.getBooleanExtra(
AlternateRecentsComponent.EXTRA_FROM_SEARCH_HOME, false);
int numVisibleTasks = launchIntent.getIntExtra(
AlternateRecentsComponent.EXTRA_NUM_VISIBLE_TASKS, 0);
int numVisibleThumbnails = launchIntent.getIntExtra(
AlternateRecentsComponent.EXTRA_NUM_VISIBLE_THUMBNAILS, 0);
mConfig.launchedFromHome = fromSearchHome || launchIntent.getBooleanExtra(
AlternateRecentsComponent.EXTRA_FROM_HOME, false);
mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra(
@@ -199,16 +202,29 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mConfig.launchedReuseTaskStackViews = launchIntent.getBooleanExtra(
AlternateRecentsComponent.EXTRA_REUSE_TASK_STACK_VIEWS, false);
// Load all the tasks
// If AlternateRecentsComponent has preloaded a load plan, then use that to prevent
// reconstructing the task stack
RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SpaceNode root = loader.reload(this,
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
mConfig.launchedFromHome);
ArrayList<TaskStack> stacks = root.getStacks();
if (!stacks.isEmpty()) {
mRecentsView.setTaskStacks(root.getStacks());
RecentsTaskLoadPlan plan = AlternateRecentsComponent.consumeInstanceLoadPlan();
if (plan == null) {
plan = loader.createLoadPlan(this);
loader.preloadTasks(plan, mConfig.launchedFromHome);
}
mConfig.launchedWithNoRecentTasks = !root.hasTasks();
// Start loading tasks according to the load plan
RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
loadOpts.runningTaskId = mConfig.launchedToTaskId;
loadOpts.numVisibleTasks = numVisibleTasks;
loadOpts.numVisibleTaskThumbnails = numVisibleThumbnails;
loader.loadTasks(this, plan, loadOpts);
SpaceNode root = plan.getSpaceNode();
ArrayList<TaskStack> stacks = root.getStacks();
boolean hasTasks = root.hasTasks();
if (hasTasks) {
mRecentsView.setTaskStacks(stacks);
}
mConfig.launchedWithNoRecentTasks = !hasTasks;
// Create the home intent runnable
Intent homeIntent = new Intent(Intent.ACTION_MAIN, null);
@@ -434,6 +450,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
/** Inflates the debug overlay if debug mode is enabled. */
void inflateDebugOverlay() {
if (!Constants.DebugFlags.App.EnableDebugMode) return;
if (mConfig.debugModeEnabled && mDebugOverlay == null) {
// Inflate the overlay and seek bars
mDebugOverlay = (DebugOverlayView) mDebugOverlayStub.inflate();
@@ -592,13 +610,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
mConfig.debugModeEnabled = false;
inflateDebugOverlay();
mDebugOverlay.disable();
if (mDebugOverlay != null) {
mDebugOverlay.disable();
}
} else {
// Enable the debug mode
settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
mConfig.debugModeEnabled = true;
inflateDebugOverlay();
mDebugOverlay.enable();
if (mDebugOverlay != null) {
mDebugOverlay.enable();
}
}
Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " +
(mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now",

View File

@@ -242,6 +242,7 @@ public class SystemServicesProxy {
Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
if (thumbnail != null) {
thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top
// left pixel, then assume the whole thumbnail is transparent. Generally, proper
// screenshots are always composed onto a bitmap that has no alpha.

View File

@@ -0,0 +1,224 @@
/*
* 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.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.UserHandle;
import android.util.Log;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
/**
* This class stores the loading state as it goes through multiple stages of loading:
* - preloadRawTasks() will load the raw set of recents tasks from the system
* - preloadPlan() will construct a new task stack with all metadata and only icons and thumbnails
* that are currently in the cache
* - executePlan() will actually load and fill in the icons and thumbnails according to the load
* options specified, such that we can transition into the Recents activity seamlessly
*/
public class RecentsTaskLoadPlan {
static String TAG = "RecentsTaskLoadPlan";
static boolean DEBUG = false;
/** The set of conditions to load tasks. */
public static class Options {
public int runningTaskId = -1;
public boolean loadIcons = true;
public boolean loadThumbnails = true;
public int numVisibleTasks = 0;
public int numVisibleTaskThumbnails = 0;
}
Context mContext;
RecentsConfiguration mConfig;
SystemServicesProxy mSystemServicesProxy;
List<ActivityManager.RecentTaskInfo> mRawTasks;
TaskStack mStack;
HashMap<Task.ComponentNameKey, ActivityInfoHandle> mActivityInfoCache =
new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
/** Package level ctor */
RecentsTaskLoadPlan(Context context, RecentsConfiguration config, SystemServicesProxy ssp) {
mContext = context;
mConfig = config;
mSystemServicesProxy = ssp;
}
/**
* An optimization to preload the raw list of tasks.
*/
public synchronized void preloadRawTasks(boolean isTopTaskHome) {
mRawTasks = mSystemServicesProxy.getRecentTasks(mConfig.maxNumTasksToLoad,
UserHandle.CURRENT.getIdentifier(), isTopTaskHome);
Collections.reverse(mRawTasks);
if (DEBUG) Log.d(TAG, "preloadRawTasks, tasks: " + mRawTasks.size());
}
/**
* Preloads the list of recent tasks from the system. After this call, the TaskStack will
* have a list of all the recent tasks with their metadata, not including icons or
* thumbnails which were not cached and have to be loaded.
*/
synchronized void preloadPlan(RecentsTaskLoader loader, boolean isTopTaskHome) {
if (DEBUG) Log.d(TAG, "preloadPlan");
mActivityInfoCache.clear();
mStack = new TaskStack();
Resources res = mContext.getResources();
ArrayList<Task> loadedTasks = new ArrayList<Task>();
if (mRawTasks == null) {
preloadRawTasks(isTopTaskHome);
}
int taskCount = mRawTasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
// Compose the task key
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
t.firstActiveTime, t.lastActiveTime);
// Get an existing activity info handle if possible
Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
ActivityInfoHandle infoHandle;
boolean hadCachedActivityInfo = false;
if (mActivityInfoCache.containsKey(cnKey)) {
infoHandle = mActivityInfoCache.get(cnKey);
hadCachedActivityInfo = true;
} else {
infoHandle = new ActivityInfoHandle();
}
// Load the label, icon, and color
String activityLabel = loader.getAndUpdateActivityLabel(taskKey, t.taskDescription,
mSystemServicesProxy, infoHandle);
Drawable activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
mSystemServicesProxy, res, infoHandle, false);
int activityColor = loader.getActivityPrimaryColor(t.taskDescription, mConfig);
// Update the activity info cache
if (!hadCachedActivityInfo && infoHandle.info != null) {
mActivityInfoCache.put(cnKey, infoHandle);
}
Bitmap icon = t.taskDescription != null
? t.taskDescription.getInMemoryIcon()
: null;
String iconFilename = t.taskDescription != null
? t.taskDescription.getIconFilename()
: null;
// Add the task to the stack
Task task = new Task(taskKey, (t.id != RecentsTaskLoader.INVALID_TASK_ID),
t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel, activityIcon,
activityColor, (i == (taskCount - 1)), mConfig.lockToAppEnabled, icon,
iconFilename);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy, false);
loadedTasks.add(task);
}
mStack.setTasks(loadedTasks);
mStack.createAffiliatedGroupings(mConfig);
// Assertion
if (mStack.getTaskCount() != mRawTasks.size()) {
throw new RuntimeException("Loading failed");
}
}
/**
* Called to apply the actual loading based on the specified conditions.
*/
synchronized void executePlan(Options opts, RecentsTaskLoader loader) {
if (DEBUG) Log.d(TAG, "executePlan, # tasks: " + opts.numVisibleTasks +
", # thumbnails: " + opts.numVisibleTaskThumbnails +
", running task id: " + opts.runningTaskId);
Resources res = mContext.getResources();
// Iterate through each of the tasks and load them according to the load conditions.
ArrayList<Task> tasks = mStack.getTasks();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
Task task = tasks.get(i);
Task.TaskKey taskKey = task.key;
// Get an existing activity info handle if possible
Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
ActivityInfoHandle infoHandle;
boolean hadCachedActivityInfo = false;
if (mActivityInfoCache.containsKey(cnKey)) {
infoHandle = mActivityInfoCache.get(cnKey);
hadCachedActivityInfo = true;
} else {
infoHandle = new ActivityInfoHandle();
}
boolean isRunningTask = (task.key.id == opts.runningTaskId);
boolean isVisibleTask = i >= (taskCount - opts.numVisibleTasks);
boolean isVisibleThumbnail = i >= (taskCount - opts.numVisibleTaskThumbnails);
if (opts.loadIcons && (isRunningTask || isVisibleTask)) {
if (task.activityIcon == null) {
if (DEBUG) Log.d(TAG, "\tLoading icon: " + taskKey);
task.activityIcon = loader.getAndUpdateActivityIcon(taskKey, t.taskDescription,
mSystemServicesProxy, res, infoHandle, true);
}
}
if (opts.loadThumbnails && (isRunningTask || isVisibleThumbnail)) {
if (task.thumbnail == null) {
if (DEBUG) Log.d(TAG, "\tLoading thumbnail: " + taskKey);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, mSystemServicesProxy,
true);
}
}
// Update the activity info cache
if (!hadCachedActivityInfo && infoHandle.info != null) {
mActivityInfoCache.put(cnKey, infoHandle);
}
}
}
/**
* Composes and returns a TaskStack from the preloaded list of recent tasks.
*/
public TaskStack getTaskStack() {
return mStack;
}
/**
* Composes and returns a SpaceNode from the preloaded list of recent tasks.
*/
public SpaceNode getSpaceNode() {
SpaceNode node = new SpaceNode();
node.setStack(mStack);
return node;
}
}

View File

@@ -26,7 +26,6 @@ import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.UserHandle;
import android.util.Log;
import com.android.systemui.R;
@@ -34,11 +33,7 @@ import com.android.systemui.recents.Constants;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
@@ -205,9 +200,7 @@ class TaskResourceLoader implements Runnable {
// Load the thumbnail if it is stale or we haven't cached one yet
if (cachedThumbnail == null) {
cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
if (cachedThumbnail != null) {
cachedThumbnail.setHasAlpha(false);
} else {
if (cachedThumbnail == null) {
cachedThumbnail = mDefaultThumbnail;
}
mThumbnailCache.put(t.key, cachedThumbnail);
@@ -260,7 +253,7 @@ public class RecentsTaskLoader {
private static final String TAG = "RecentsTaskLoader";
static RecentsTaskLoader sInstance;
public static final int ALL_TASKS = -1;
static int INVALID_TASK_ID = -1;
SystemServicesProxy mSystemServicesProxy;
DrawableLruCache mApplicationIconCache;
@@ -273,6 +266,7 @@ public class RecentsTaskLoader {
int mMaxThumbnailCacheSize;
int mMaxIconCacheSize;
int mNumVisibleTasksLoaded;
BitmapDrawable mDefaultApplicationIcon;
Bitmap mDefaultThumbnail;
@@ -325,55 +319,6 @@ public class RecentsTaskLoader {
return mSystemServicesProxy;
}
/** Gets the list of recent tasks, ordered from back to front. */
private static List<ActivityManager.RecentTaskInfo> getRecentTasks(SystemServicesProxy ssp,
int numTasksToLoad, boolean isTopTaskHome) {
List<ActivityManager.RecentTaskInfo> tasks =
ssp.getRecentTasks(numTasksToLoad, UserHandle.CURRENT.getIdentifier(),
isTopTaskHome);
Collections.reverse(tasks);
return tasks;
}
/** Returns the activity icon using as many cached values as we can. */
public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
ActivityManager.TaskDescription td, SystemServicesProxy ssp,
Resources res, ActivityInfoHandle infoHandle, boolean preloadTask) {
// Return the cached activity icon if it exists
Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
if (icon != null) {
return icon;
}
// If we are preloading this task, continue to load the task description icon or the
// activity icon
if (preloadTask) {
// Return and cache the task description icon if it exists
Drawable tdDrawable = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
td.getIconFilename(), ssp, res);
if (tdDrawable != null) {
mApplicationIconCache.put(taskKey, tdDrawable);
return tdDrawable;
}
// Load the icon from the activity info and cache it
if (infoHandle.info == null) {
infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
taskKey.userId);
}
if (infoHandle.info != null) {
icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
if (icon != null) {
mApplicationIconCache.put(taskKey, icon);
return icon;
}
}
}
// If we couldn't load any icon, return null
return null;
}
/** Returns the activity label using as many cached values as we can. */
public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
ActivityManager.TaskDescription td, SystemServicesProxy ssp,
@@ -402,6 +347,63 @@ public class RecentsTaskLoader {
return label;
}
/** Returns the activity icon using as many cached values as we can. */
public Drawable getAndUpdateActivityIcon(Task.TaskKey taskKey,
ActivityManager.TaskDescription td, SystemServicesProxy ssp,
Resources res, ActivityInfoHandle infoHandle, boolean loadIfNotCached) {
// Return the cached activity icon if it exists
Drawable icon = mApplicationIconCache.getAndInvalidateIfModified(taskKey);
if (icon != null) {
return icon;
}
if (loadIfNotCached) {
// Return and cache the task description icon if it exists
Drawable tdDrawable = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(),
td.getIconFilename(), ssp, res);
if (tdDrawable != null) {
mApplicationIconCache.put(taskKey, tdDrawable);
return tdDrawable;
}
// Load the icon from the activity info and cache it
if (infoHandle.info == null) {
infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(),
taskKey.userId);
}
if (infoHandle.info != null) {
icon = ssp.getActivityIcon(infoHandle.info, taskKey.userId);
if (icon != null) {
mApplicationIconCache.put(taskKey, icon);
return icon;
}
}
}
// We couldn't load any icon
return null;
}
/** Returns the bitmap using as many cached values as we can. */
public Bitmap getAndUpdateThumbnail(Task.TaskKey taskKey, SystemServicesProxy ssp,
boolean loadIfNotCached) {
// Return the cached thumbnail if it exists
Bitmap thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
if (thumbnail != null) {
return thumbnail;
}
if (loadIfNotCached) {
// Load the thumbnail from the system
thumbnail = ssp.getTaskThumbnail(taskKey.id);
if (thumbnail != null) {
mThumbnailCache.put(taskKey, thumbnail);
return thumbnail;
}
}
// We couldn't load any thumbnail
return null;
}
/** Returns the activity's primary color. */
public int getActivityPrimaryColor(ActivityManager.TaskDescription td,
RecentsConfiguration config) {
@@ -411,127 +413,33 @@ public class RecentsTaskLoader {
return config.taskBarViewDefaultBackgroundColor;
}
/** Reload the set of recent tasks */
public SpaceNode reload(Context context, int preloadCount, boolean isTopTaskHome) {
ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>();
ArrayList<Task> tasksToLoad = new ArrayList<Task>();
TaskStack stack = getTaskStack(mSystemServicesProxy, context.getResources(),
-1, preloadCount, RecentsTaskLoader.ALL_TASKS, true, isTopTaskHome, taskKeys,
tasksToLoad);
SpaceNode root = new SpaceNode();
root.setStack(stack);
// Start the task loader and add all the tasks we need to load
mLoadQueue.addTasks(tasksToLoad);
mLoader.start(context);
return root;
/** Returns the size of the app icon cache. */
public int getApplicationIconCacheSize() {
return mMaxIconCacheSize;
}
/** Preloads the set of recent tasks (not including thumbnails). */
public void preload(Context context, int numTasksToPreload) {
ArrayList<Task> tasksToLoad = new ArrayList<Task>();
getTaskStack(mSystemServicesProxy, context.getResources(),
-1, -1, numTasksToPreload, true, true, null, tasksToLoad);
// Start the task loader and add all the tasks we need to load
mLoadQueue.addTasks(tasksToLoad);
mLoader.start(context);
}
/** Creates a lightweight stack of the current recent tasks, without thumbnails and icons. */
public synchronized TaskStack getTaskStack(SystemServicesProxy ssp, Resources res,
int preloadTaskId, int preloadTaskCount, int loadTaskCount,
boolean loadTaskThumbnails, boolean isTopTaskHome,
List<Task.TaskKey> taskKeysOut, List<Task> tasksToLoadOut) {
public RecentsTaskLoadPlan createLoadPlan(Context context) {
RecentsConfiguration config = RecentsConfiguration.getInstance();
List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp,
(loadTaskCount == ALL_TASKS ? config.maxNumTasksToLoad : loadTaskCount),
isTopTaskHome);
HashMap<Task.ComponentNameKey, ActivityInfoHandle> activityInfoCache =
new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
ArrayList<Task> tasksToAdd = new ArrayList<Task>();
TaskStack stack = new TaskStack();
RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy);
return plan;
}
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = tasks.get(i);
public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) {
plan.preloadPlan(this, isTopTaskHome);
}
// Compose the task key
Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.baseIntent, t.userId,
t.firstActiveTime, t.lastActiveTime);
// Get an existing activity info handle if possible
Task.ComponentNameKey cnKey = taskKey.getComponentNameKey();
ActivityInfoHandle infoHandle;
boolean hasCachedActivityInfo = false;
if (activityInfoCache.containsKey(cnKey)) {
infoHandle = activityInfoCache.get(cnKey);
hasCachedActivityInfo = true;
} else {
infoHandle = new ActivityInfoHandle();
}
// Determine whether to preload this task
boolean preloadTask = false;
if (preloadTaskId > 0) {
preloadTask = (t.id == preloadTaskId);
} else if (preloadTaskCount > 0) {
preloadTask = (i >= (taskCount - preloadTaskCount));
}
// Load the label, icon, and color
String activityLabel = getAndUpdateActivityLabel(taskKey, t.taskDescription,
ssp, infoHandle);
Drawable activityIcon = getAndUpdateActivityIcon(taskKey, t.taskDescription,
ssp, res, infoHandle, preloadTask);
int activityColor = getActivityPrimaryColor(t.taskDescription, config);
// Update the activity info cache
if (!hasCachedActivityInfo && infoHandle.info != null) {
activityInfoCache.put(cnKey, infoHandle);
}
Bitmap icon = t.taskDescription != null
? t.taskDescription.getInMemoryIcon()
: null;
String iconFilename = t.taskDescription != null
? t.taskDescription.getIconFilename()
: null;
// Add the task to the stack
Task task = new Task(taskKey, (t.id > -1), t.affiliatedTaskId, t.affiliatedTaskColor,
activityLabel, activityIcon, activityColor, (i == (taskCount - 1)),
config.lockToAppEnabled, icon, iconFilename);
if (preloadTask && loadTaskThumbnails) {
// Load the thumbnail from the cache if possible
task.thumbnail = mThumbnailCache.getAndInvalidateIfModified(taskKey);
if (task.thumbnail == null) {
// Load the thumbnail from the system
task.thumbnail = ssp.getTaskThumbnail(taskKey.id);
if (task.thumbnail != null) {
task.thumbnail.setHasAlpha(false);
mThumbnailCache.put(taskKey, task.thumbnail);
}
}
if (task.thumbnail == null && tasksToLoadOut != null) {
// Either the task has changed since the last active time, or it was not
// previously cached, so try and load the task anew.
tasksToLoadOut.add(task);
}
}
// Add to the list of task keys
if (taskKeysOut != null) {
taskKeysOut.add(taskKey);
}
// Add the task to the stack
tasksToAdd.add(task);
public void loadTasks(Context context, RecentsTaskLoadPlan plan,
RecentsTaskLoadPlan.Options opts) {
if (opts == null) {
throw new RuntimeException("Requires load options");
}
stack.setTasks(tasksToAdd);
stack.createAffiliatedGroupings(config);
return stack;
plan.executePlan(opts, this);
if (opts.numVisibleTasks > 0) {
mNumVisibleTasksLoaded = opts.numVisibleTasks;
}
// Start the loader
mLoader.start(context);
}
/** Acquires the task resource data directly from the pool. */
@@ -591,24 +499,22 @@ public class RecentsTaskLoader {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
// Stop the loader immediately when the UI is no longer visible
stopLoader();
mThumbnailCache.trimToSize(Math.max(
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
mMaxThumbnailCacheSize / 2));
mApplicationIconCache.trimToSize(Math.max(
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
mApplicationIconCache.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
mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2);
mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2);
mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
// We are going to be low on memory
mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4);
mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4);
mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:

View File

@@ -214,6 +214,9 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
/** Requests all task stacks to start their exit-recents animation */
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
// We have to increment/decrement the post animation trigger in case there are no children
// to ensure that it runs
ctx.postAnimationTrigger.increment();
int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
@@ -222,6 +225,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
stackView.startExitToHomeAnimation(ctx);
}
}
ctx.postAnimationTrigger.decrement();
// Notify of the exit animation
mCb.onExitToHomeAnimationTriggered();
@@ -501,7 +505,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
// Launch the app right away if there is no task view, otherwise, animate the icon out first
if (tv == null) {
post(launchRunnable);
launchRunnable.run();
} else {
if (!task.group.isFrontMostTask(task)) {
// For affiliated tasks that are behind other tasks, we must animate the front cards
@@ -510,7 +514,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
} else {
// Otherwise, we can start the task transition immediately
stackView.startLaunchTaskAnimation(tv, null, lockToTask);
postDelayed(launchRunnable, 17);
launchRunnable.run();
}
}
}

View File

@@ -621,6 +621,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
updateMinMaxScroll(false, launchedWithAltTab, launchedFromHome);
}
/**
* Computes the maximum number of visible tasks and thumbnails. Requires that
* updateMinMaxScrollForStack() is called first.
*/
public TaskStackViewLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
}
/**
* This is called with the full window width and height to allow stack view children to
* perform the full screen transition down.

View File

@@ -35,6 +35,18 @@ public class TaskStackViewLayoutAlgorithm {
// These are all going to change
static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area
// A report of the visibility state of the stack
public class VisibilityReport {
public int numVisibleTasks;
public int numVisibleThumbnails;
/** Package level ctor */
VisibilityReport(int tasks, int thumbnails) {
numVisibleTasks = tasks;
numVisibleThumbnails = thumbnails;
}
}
RecentsConfiguration mConfig;
// The various rects that define the stack view
@@ -117,7 +129,8 @@ public class TaskStackViewLayoutAlgorithm {
float pTaskHeightOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
float pNavBarOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom - mStackRect.bottom));
screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
mStackRect.bottom));
// Update the task offsets
float pAtBackMostCardTop = 0.5f;
@@ -130,8 +143,8 @@ public class TaskStackViewLayoutAlgorithm {
if (i < (taskCount - 1)) {
// Increment the peek height
float pPeek = task.group.isFrontMostTask(task) ? pBetweenAffiliateOffset :
pWithinAffiliateOffset;
float pPeek = task.group.isFrontMostTask(task) ?
pBetweenAffiliateOffset : pWithinAffiliateOffset;
pAtSecondFrontMostCardTop = pAtFrontMostCardTop;
pAtFrontMostCardTop += pPeek;
}
@@ -153,19 +166,72 @@ public class TaskStackViewLayoutAlgorithm {
mInitialScrollP = Math.max(0, mInitialScrollP);
}
/**
* Computes the maximum number of visible tasks and thumbnails. Requires that
* computeMinMaxScroll() is called first.
*/
public VisibilityReport computeStackVisibilityReport(ArrayList<Task> tasks) {
if (tasks.size() <= 1) {
return new VisibilityReport(1, 1);
}
// Walk backwards in the task stack and count the number of tasks and visible thumbnails
int taskHeight = mTaskRect.height();
int numVisibleTasks = 1;
int numVisibleThumbnails = 1;
float progress = mTaskProgressMap.get(tasks.get(tasks.size() - 1).key) - mInitialScrollP;
int prevScreenY = curveProgressToScreenY(progress);
for (int i = tasks.size() - 2; i >= 0; i--) {
Task task = tasks.get(i);
progress = mTaskProgressMap.get(task.key) - mInitialScrollP;
if (progress < 0) {
break;
}
boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
float scaleAtP = curveProgressToScale(progress);
int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
int screenY = curveProgressToScreenY(progress) + scaleYOffsetAtP;
boolean hasVisibleThumbnail = (prevScreenY - screenY) > mConfig.taskBarHeight;
if (hasVisibleThumbnail) {
numVisibleThumbnails++;
numVisibleTasks++;
prevScreenY = screenY;
} else {
// Once we hit the next front most task that does not have a visible thumbnail,
// walk through remaining visible set
for (int j = i; j >= 0; j--) {
numVisibleTasks++;
progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP;
if (progress < 0) {
break;
}
}
break;
}
} else if (!isFrontMostTaskInGroup) {
// Affiliated task, no thumbnail
numVisibleTasks++;
}
}
return new VisibilityReport(numVisibleTasks, numVisibleThumbnails);
}
/** Update/get the transform */
public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut,
TaskViewTransform prevTransform) {
public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
// Return early if we have an invalid index
if (task == null || !mTaskProgressMap.containsKey(task.key)) {
transformOut.reset();
return transformOut;
}
return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut, prevTransform);
return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
prevTransform);
}
/** Update/get the transform */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, TaskViewTransform transformOut, TaskViewTransform prevTransform) {
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
float pTaskRelative = taskProgress - stackScroll;
float pBounded = Math.max(0, Math.min(pTaskRelative, 1f));
// If the task top is outside of the bounds below the screen, then immediately reset it