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 This needs to match the constants in
policy/src/com/android/internal/policy/impl/PhoneWindowManager.java 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. <!-- Control the behavior when the user double-taps the home button.
0 - Nothing 0 - Nothing

View File

@@ -41,6 +41,7 @@ import com.android.systemui.R;
import com.android.systemui.RecentsComponent; import com.android.systemui.RecentsComponent;
import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.Console;
import com.android.systemui.recents.misc.SystemServicesProxy; 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.RecentsTaskLoader;
import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskGrouping; 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_ALT_TAB = "recents.triggeredFromAltTab";
final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey"; 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_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_START_ENTER_ANIMATION = "action_start_enter_animation";
final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity"; 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"; final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity";
static RecentsComponent.Callbacks sRecentsComponentCallbacks; static RecentsComponent.Callbacks sRecentsComponentCallbacks;
static RecentsTaskLoadPlan sInstanceLoadPlan;
Context mContext; Context mContext;
LayoutInflater mInflater; LayoutInflater mInflater;
@@ -134,8 +138,15 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
} }
} }
// When we start, preload the metadata associated with the previous tasks // When we start, preload the metadata and icons associated with the recent tasks.
RecentsTaskLoader.getInstance().preload(mContext, RecentsTaskLoader.ALL_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() { public void onBootCompleted() {
@@ -183,9 +194,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
} }
public void onPreloadRecents() { public void onPreloadRecents() {
// When we start, preload the metadata associated with the previous tasks // Preload only the raw task list into a new load plan (which will be consumed by the
RecentsTaskLoader.getInstance().preload(mContext, // RecentsActivity)
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
sInstanceLoadPlan = loader.createLoadPlan(mContext);
sInstanceLoadPlan.preloadRawTasks(true);
} }
public void onCancelPreloadingRecents() { public void onCancelPreloadingRecents() {
@@ -194,8 +207,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
void showRelativeAffiliatedTask(boolean showNextTask) { void showRelativeAffiliatedTask(boolean showNextTask) {
RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(), RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext);
-1, -1, RecentsTaskLoader.ALL_TASKS, false, true, null, null); loader.preloadTasks(plan, true /* isTopTaskHome */);
TaskStack stack = plan.getTaskStack();
// Return early if there are no tasks // Return early if there are no tasks
if (stack.getTaskCount() == 0) return; if (stack.getTaskCount() == 0) return;
@@ -411,11 +426,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
* Creates the activity options for an app->recents transition. * Creates the activity options for an app->recents transition.
*/ */
ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask, ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask,
boolean isTopTaskHome) { TaskStack stack, TaskStackView stackView) {
// Update the destination rect // Update the destination rect
Task toTask = new Task(); Task toTask = new Task();
TaskViewTransform toTransform = getThumbnailTransitionTransform(topTask.id, isTopTaskHome, TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
toTask); topTask.id, toTask);
if (toTransform != null && toTask.key != null) { if (toTransform != null && toTask.key != null) {
Rect toTaskRect = toTransform.rect; Rect toTaskRect = toTransform.rect;
int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); 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. */ /** Returns the transition rect for the given task id. */
TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome, TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView,
Task runningTaskOut) { int runningTaskId, 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;
}
// Find the running task in the TaskStack // Find the running task in the TaskStack
Task task = null; Task task = null;
ArrayList<Task> tasks = stack.getTasks(); ArrayList<Task> tasks = stack.getTasks();
@@ -474,30 +481,42 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
} }
// Get the transform for the running task // Get the transform for the running task
mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); stackView.getScroller().setStackScrollToInitialState();
mDummyStackView.getScroller().setStackScrollToInitialState(); mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task,
mTmpTransform = mDummyStackView.getStackAlgorithm().getStackTransform(task, stackView.getScroller().getStackScroll(), mTmpTransform, null);
mDummyStackView.getScroller().getStackScroll(), mTmpTransform, null);
return mTmpTransform; return mTmpTransform;
} }
/** Starts the recents activity */ /** Starts the recents activity */
void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) { void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) {
// If Recents is not the front-most activity and we should animate into it. If if (sInstanceLoadPlan == null) {
// the activity at the root of the top task stack in the home stack, then we just do a // Create a new load plan if onPreloadRecents() was never triggered
// simple transition. Otherwise, we animate to the rects defined by the Recents service, RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
// which can differ depending on the number of items in the list. sInstanceLoadPlan = loader.createLoadPlan(mContext);
SystemServicesProxy ssp = mSystemServicesProxy; }
List<ActivityManager.RecentTaskInfo> recentTasks = RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier(), isTopTaskHome); loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome);
boolean useThumbnailTransition = !isTopTaskHome; TaskStack stack = sInstanceLoadPlan.getTaskStack();
boolean hasRecentTasks = !recentTasks.isEmpty();
// 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) { 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 // Try starting with a thumbnail transition
ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, isTopTaskHome); ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack,
mDummyStackView);
if (opts != null) { if (opts != null) {
startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL); startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL, stackVr);
} else { } else {
// Fall through below to the non-thumbnail transition // Fall through below to the non-thumbnail transition
useThumbnailTransition = false; useThumbnailTransition = false;
@@ -531,11 +550,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome);
startAlternateRecentsActivity(topTask, opts, startAlternateRecentsActivity(topTask, opts,
fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME); fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME, stackVr);
} else { } else {
// Otherwise we do the normal fade from an unknown source // Otherwise we do the normal fade from an unknown source
ActivityOptions opts = getUnknownTransitionActivityOptions(); ActivityOptions opts = getUnknownTransitionActivityOptions();
startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME); startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME, stackVr);
} }
} }
mLastToggleTime = System.currentTimeMillis(); mLastToggleTime = System.currentTimeMillis();
@@ -543,7 +562,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta
/** Starts the recents activity */ /** Starts the recents activity */
void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask, void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask,
ActivityOptions opts, String extraFlag) { ActivityOptions opts, String extraFlag,
TaskStackViewLayoutAlgorithm.VisibilityReport vr) {
Intent intent = new Intent(sToggleRecentsAction); Intent intent = new Intent(sToggleRecentsAction);
intent.setClassName(sRecentsPackage, sRecentsActivity); intent.setClassName(sRecentsPackage, sRecentsActivity);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 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_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab);
intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1); intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1);
intent.putExtra(EXTRA_REUSE_TASK_STACK_VIEWS, mCanReuseTaskStackViews); 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) { if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else { } 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 ****/ /**** OnAnimationStartedListener Implementation ****/
@Override @Override

View File

@@ -64,11 +64,6 @@ public class Constants {
public static String DebugModeVersion = "A"; 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 class TaskStackView {
public static final int TaskStackOverscrollRange = 150; public static final int TaskStackOverscrollRange = 150;
public static final int FilterStartDelay = 25; public static final int FilterStartDelay = 25;

View File

@@ -27,10 +27,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle; import android.os.UserHandle;
import android.util.Pair; import android.util.Pair;
import android.view.KeyEvent; 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.ReferenceCountedTrigger;
import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities; 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.RecentsTaskLoader;
import com.android.systemui.recents.model.SpaceNode; import com.android.systemui.recents.model.SpaceNode;
import com.android.systemui.recents.model.Task; 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)) { if (action.equals(Intent.ACTION_SCREEN_OFF)) {
// When the screen turns off, dismiss Recents to Home // When the screen turns off, dismiss Recents to Home
dismissRecentsToHome(false); dismissRecentsToHome(false);
// Start preloading some tasks in the background // Preload the metadata for all tasks in the background
RecentsTaskLoader.getInstance().preload(RecentsActivity.this, RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); RecentsTaskLoadPlan plan = loader.createLoadPlan(context);
loader.preloadTasks(plan, true /* isTopTaskHome */);
} else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) { } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) {
// When the search activity changes, update the Search widget // When the search activity changes, update the Search widget
refreshSearchWidget(); refreshSearchWidget();
@@ -193,6 +192,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
// Update the configuration based on the launch intent // Update the configuration based on the launch intent
boolean fromSearchHome = launchIntent.getBooleanExtra( boolean fromSearchHome = launchIntent.getBooleanExtra(
AlternateRecentsComponent.EXTRA_FROM_SEARCH_HOME, false); 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( mConfig.launchedFromHome = fromSearchHome || launchIntent.getBooleanExtra(
AlternateRecentsComponent.EXTRA_FROM_HOME, false); AlternateRecentsComponent.EXTRA_FROM_HOME, false);
mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra( mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra(
@@ -204,16 +207,29 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
mConfig.launchedReuseTaskStackViews = launchIntent.getBooleanExtra( mConfig.launchedReuseTaskStackViews = launchIntent.getBooleanExtra(
AlternateRecentsComponent.EXTRA_REUSE_TASK_STACK_VIEWS, false); 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(); RecentsTaskLoader loader = RecentsTaskLoader.getInstance();
SpaceNode root = loader.reload(this, RecentsTaskLoadPlan plan = AlternateRecentsComponent.consumeInstanceLoadPlan();
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount, if (plan == null) {
mConfig.launchedFromHome); plan = loader.createLoadPlan(this);
ArrayList<TaskStack> stacks = root.getStacks(); loader.preloadTasks(plan, mConfig.launchedFromHome);
if (!stacks.isEmpty()) {
mRecentsView.setTaskStacks(root.getStacks());
} }
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 // Create the home intent runnable
Intent homeIntent = new Intent(Intent.ACTION_MAIN, null); 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. */ /** Inflates the debug overlay if debug mode is enabled. */
void inflateDebugOverlay() { void inflateDebugOverlay() {
if (!Constants.DebugFlags.App.EnableDebugMode) return;
if (mConfig.debugModeEnabled && mDebugOverlay == null) { if (mConfig.debugModeEnabled && mDebugOverlay == null) {
// Inflate the overlay and seek bars // Inflate the overlay and seek bars
mDebugOverlay = (DebugOverlayView) mDebugOverlayStub.inflate(); mDebugOverlay = (DebugOverlayView) mDebugOverlayStub.inflate();
@@ -600,14 +618,18 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply(); settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply();
mConfig.debugModeEnabled = false; mConfig.debugModeEnabled = false;
inflateDebugOverlay(); inflateDebugOverlay();
if (mDebugOverlay != null) {
mDebugOverlay.disable(); mDebugOverlay.disable();
}
} else { } else {
// Enable the debug mode // Enable the debug mode
settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply(); settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply();
mConfig.debugModeEnabled = true; mConfig.debugModeEnabled = true;
inflateDebugOverlay(); inflateDebugOverlay();
if (mDebugOverlay != null) {
mDebugOverlay.enable(); mDebugOverlay.enable();
} }
}
Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " + Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " +
(mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now", (mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now",
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();

View File

@@ -244,6 +244,7 @@ public class SystemServicesProxy {
Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId); Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId);
if (thumbnail != null) { if (thumbnail != null) {
thumbnail.setHasAlpha(false);
// We use a dumb heuristic for now, if the thumbnail is purely transparent in the top // 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 // left pixel, then assume the whole thumbnail is transparent. Generally, proper
// screenshots are always composed onto a bitmap that has no alpha. // 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.graphics.drawable.Drawable;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.os.UserHandle;
import android.util.Log; import android.util.Log;
import com.android.systemui.R; 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.RecentsConfiguration;
import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.SystemServicesProxy;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue; 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 // Load the thumbnail if it is stale or we haven't cached one yet
if (cachedThumbnail == null) { if (cachedThumbnail == null) {
cachedThumbnail = ssp.getTaskThumbnail(t.key.id); cachedThumbnail = ssp.getTaskThumbnail(t.key.id);
if (cachedThumbnail != null) { if (cachedThumbnail == null) {
cachedThumbnail.setHasAlpha(false);
} else {
cachedThumbnail = mDefaultThumbnail; cachedThumbnail = mDefaultThumbnail;
} }
mThumbnailCache.put(t.key, cachedThumbnail); mThumbnailCache.put(t.key, cachedThumbnail);
@@ -260,7 +253,7 @@ public class RecentsTaskLoader {
private static final String TAG = "RecentsTaskLoader"; private static final String TAG = "RecentsTaskLoader";
static RecentsTaskLoader sInstance; static RecentsTaskLoader sInstance;
public static final int ALL_TASKS = -1; static int INVALID_TASK_ID = -1;
SystemServicesProxy mSystemServicesProxy; SystemServicesProxy mSystemServicesProxy;
DrawableLruCache mApplicationIconCache; DrawableLruCache mApplicationIconCache;
@@ -273,6 +266,7 @@ public class RecentsTaskLoader {
int mMaxThumbnailCacheSize; int mMaxThumbnailCacheSize;
int mMaxIconCacheSize; int mMaxIconCacheSize;
int mNumVisibleTasksLoaded;
BitmapDrawable mDefaultApplicationIcon; BitmapDrawable mDefaultApplicationIcon;
Bitmap mDefaultThumbnail; Bitmap mDefaultThumbnail;
@@ -325,55 +319,6 @@ public class RecentsTaskLoader {
return mSystemServicesProxy; 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. */ /** Returns the activity label using as many cached values as we can. */
public String getAndUpdateActivityLabel(Task.TaskKey taskKey, public String getAndUpdateActivityLabel(Task.TaskKey taskKey,
ActivityManager.TaskDescription td, SystemServicesProxy ssp, ActivityManager.TaskDescription td, SystemServicesProxy ssp,
@@ -402,6 +347,63 @@ public class RecentsTaskLoader {
return label; 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. */ /** Returns the activity's primary color. */
public int getActivityPrimaryColor(ActivityManager.TaskDescription td, public int getActivityPrimaryColor(ActivityManager.TaskDescription td,
RecentsConfiguration config) { RecentsConfiguration config) {
@@ -411,127 +413,33 @@ public class RecentsTaskLoader {
return config.taskBarViewDefaultBackgroundColor; return config.taskBarViewDefaultBackgroundColor;
} }
/** Reload the set of recent tasks */ /** Returns the size of the app icon cache. */
public SpaceNode reload(Context context, int preloadCount, boolean isTopTaskHome) { public int getApplicationIconCacheSize() {
ArrayList<Task.TaskKey> taskKeys = new ArrayList<Task.TaskKey>(); return mMaxIconCacheSize;
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;
} }
/** Preloads the set of recent tasks (not including thumbnails). */ public RecentsTaskLoadPlan createLoadPlan(Context context) {
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) {
RecentsConfiguration config = RecentsConfiguration.getInstance(); RecentsConfiguration config = RecentsConfiguration.getInstance();
List<ActivityManager.RecentTaskInfo> tasks = getRecentTasks(ssp, RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy);
(loadTaskCount == ALL_TASKS ? config.maxNumTasksToLoad : loadTaskCount), return plan;
isTopTaskHome);
HashMap<Task.ComponentNameKey, ActivityInfoHandle> activityInfoCache =
new HashMap<Task.ComponentNameKey, ActivityInfoHandle>();
ArrayList<Task> tasksToAdd = new ArrayList<Task>();
TaskStack stack = new TaskStack();
int taskCount = tasks.size();
for (int i = 0; i < taskCount; i++) {
ActivityManager.RecentTaskInfo t = tasks.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 hasCachedActivityInfo = false;
if (activityInfoCache.containsKey(cnKey)) {
infoHandle = activityInfoCache.get(cnKey);
hasCachedActivityInfo = true;
} else {
infoHandle = new ActivityInfoHandle();
} }
// Determine whether to preload this task public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) {
boolean preloadTask = false; plan.preloadPlan(this, isTopTaskHome);
if (preloadTaskId > 0) {
preloadTask = (t.id == preloadTaskId);
} else if (preloadTaskCount > 0) {
preloadTask = (i >= (taskCount - preloadTaskCount));
} }
// Load the label, icon, and color public void loadTasks(Context context, RecentsTaskLoadPlan plan,
String activityLabel = getAndUpdateActivityLabel(taskKey, t.taskDescription, RecentsTaskLoadPlan.Options opts) {
ssp, infoHandle); if (opts == null) {
Drawable activityIcon = getAndUpdateActivityIcon(taskKey, t.taskDescription, throw new RuntimeException("Requires load options");
ssp, res, infoHandle, preloadTask); }
int activityColor = getActivityPrimaryColor(t.taskDescription, config); plan.executePlan(opts, this);
if (opts.numVisibleTasks > 0) {
// Update the activity info cache mNumVisibleTasksLoaded = opts.numVisibleTasks;
if (!hasCachedActivityInfo && infoHandle.info != null) {
activityInfoCache.put(cnKey, infoHandle);
} }
Bitmap icon = t.taskDescription != null // Start the loader
? t.taskDescription.getInMemoryIcon() mLoader.start(context);
: 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);
}
stack.setTasks(tasksToAdd);
stack.createAffiliatedGroupings(config);
return stack;
} }
/** Acquires the task resource data directly from the pool. */ /** Acquires the task resource data directly from the pool. */
@@ -591,24 +499,22 @@ public class RecentsTaskLoader {
case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN: case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
// Stop the loader immediately when the UI is no longer visible // Stop the loader immediately when the UI is no longer visible
stopLoader(); stopLoader();
mThumbnailCache.trimToSize(Math.max( mThumbnailCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
mMaxThumbnailCacheSize / 2)); mMaxThumbnailCacheSize / 2));
mApplicationIconCache.trimToSize(Math.max( mApplicationIconCache.trimToSize(Math.max(mNumVisibleTasksLoaded,
Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount,
mMaxIconCacheSize / 2)); mMaxIconCacheSize / 2));
break; break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE: case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND: case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
// We are leaving recents, so trim the data a bit // We are leaving recents, so trim the data a bit
mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 2); mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 2));
mApplicationIconCache.trimToSize(mMaxIconCacheSize / 2); mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 2));
break; break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW: case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
case ComponentCallbacks2.TRIM_MEMORY_MODERATE: case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
// We are going to be low on memory // We are going to be low on memory
mThumbnailCache.trimToSize(mMaxThumbnailCacheSize / 4); mThumbnailCache.trimToSize(Math.max(1, mMaxThumbnailCacheSize / 4));
mApplicationIconCache.trimToSize(mMaxIconCacheSize / 4); mApplicationIconCache.trimToSize(Math.max(1, mMaxIconCacheSize / 4));
break; break;
case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL: case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: 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 */ /** Requests all task stacks to start their exit-recents animation */
public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { 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(); int childCount = getChildCount();
for (int i = 0; i < childCount; i++) { for (int i = 0; i < childCount; i++) {
View child = getChildAt(i); View child = getChildAt(i);
@@ -224,6 +227,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
stackView.startExitToHomeAnimation(ctx); stackView.startExitToHomeAnimation(ctx);
} }
} }
ctx.postAnimationTrigger.decrement();
// Notify of the exit animation // Notify of the exit animation
mCb.onExitToHomeAnimationTriggered(); 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 // Launch the app right away if there is no task view, otherwise, animate the icon out first
if (tv == null) { if (tv == null) {
post(launchRunnable); launchRunnable.run();
} else { } else {
if (!task.group.isFrontMostTask(task)) { if (!task.group.isFrontMostTask(task)) {
// For affiliated tasks that are behind other tasks, we must animate the front cards // 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 { } else {
// Otherwise, we can start the task transition immediately // Otherwise, we can start the task transition immediately
stackView.startLaunchTaskAnimation(tv, null, lockToTask); 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); 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 * This is called with the full window width and height to allow stack view children to
* perform the full screen transition down. * perform the full screen transition down.

View File

@@ -35,6 +35,18 @@ public class TaskStackViewLayoutAlgorithm {
// These are all going to change // These are all going to change
static final float StackPeekMinScale = 0.8f; // The min scale of the last card in the peek area 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; RecentsConfiguration mConfig;
// The various rects that define the stack view // The various rects that define the stack view
@@ -117,7 +129,8 @@ public class TaskStackViewLayoutAlgorithm {
float pTaskHeightOffset = pAtBottomOfStackRect - float pTaskHeightOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight); screenYToCurveProgress(mStackVisibleRect.bottom - taskHeight);
float pNavBarOffset = pAtBottomOfStackRect - float pNavBarOffset = pAtBottomOfStackRect -
screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom - mStackRect.bottom)); screenYToCurveProgress(mStackVisibleRect.bottom - (mStackVisibleRect.bottom -
mStackRect.bottom));
// Update the task offsets // Update the task offsets
float pAtBackMostCardTop = 0.5f; float pAtBackMostCardTop = 0.5f;
@@ -130,8 +143,8 @@ public class TaskStackViewLayoutAlgorithm {
if (i < (taskCount - 1)) { if (i < (taskCount - 1)) {
// Increment the peek height // Increment the peek height
float pPeek = task.group.isFrontMostTask(task) ? pBetweenAffiliateOffset : float pPeek = task.group.isFrontMostTask(task) ?
pWithinAffiliateOffset; pBetweenAffiliateOffset : pWithinAffiliateOffset;
pAtSecondFrontMostCardTop = pAtFrontMostCardTop; pAtSecondFrontMostCardTop = pAtFrontMostCardTop;
pAtFrontMostCardTop += pPeek; pAtFrontMostCardTop += pPeek;
} }
@@ -153,19 +166,72 @@ public class TaskStackViewLayoutAlgorithm {
mInitialScrollP = Math.max(0, mInitialScrollP); 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 */ /** Update/get the transform */
public TaskViewTransform getStackTransform(Task task, float stackScroll, TaskViewTransform transformOut, public TaskViewTransform getStackTransform(Task task, float stackScroll,
TaskViewTransform prevTransform) { TaskViewTransform transformOut, TaskViewTransform prevTransform) {
// Return early if we have an invalid index // Return early if we have an invalid index
if (task == null || !mTaskProgressMap.containsKey(task.key)) { if (task == null || !mTaskProgressMap.containsKey(task.key)) {
transformOut.reset(); transformOut.reset();
return transformOut; return transformOut;
} }
return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut, prevTransform); return getStackTransform(mTaskProgressMap.get(task.key), stackScroll, transformOut,
prevTransform);
} }
/** Update/get the transform */ /** 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 pTaskRelative = taskProgress - stackScroll;
float pBounded = Math.max(0, Math.min(pTaskRelative, 1f)); 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 // If the task top is outside of the bounds below the screen, then immediately reset it