Merge "Preload only visible thumbnails and task icons. (Bug 17672056, Bug 18291345)" into lmp-mr1-dev

This commit is contained in:
Winson Chung
2014-11-12 00:10:53 +00:00
committed by Android (Google) Code Review
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

@@ -41,6 +41,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;
@@ -64,6 +65,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";
@@ -76,6 +79,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;
@@ -134,8 +138,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() {
@@ -183,9 +194,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() {
@@ -194,8 +207,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;
@@ -411,11 +426,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);
@@ -443,16 +458,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();
@@ -474,30 +481,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;
@@ -531,11 +550,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();
@@ -543,7 +562,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
@@ -555,6 +575,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 {
@@ -575,6 +597,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;
@@ -43,6 +40,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;
@@ -168,9 +166,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();
@@ -193,6 +192,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(
@@ -204,16 +207,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);
@@ -442,6 +458,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();
@@ -600,13 +618,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

@@ -244,6 +244,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

@@ -216,6 +216,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);
@@ -224,6 +227,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
stackView.startExitToHomeAnimation(ctx);
}
}
ctx.postAnimationTrigger.decrement();
// Notify of the exit animation
mCb.onExitToHomeAnimationTriggered();
@@ -503,7 +507,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
@@ -512,7 +516,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

@@ -623,6 +623,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