diff options
| author | 2014-11-07 15:02:38 -0800 | |
|---|---|---|
| committer | 2014-11-11 16:10:05 -0800 | |
| commit | a91c293be26b2deb5434eb827a800fa0c80dc92c (patch) | |
| tree | b835dbe2c327676ce2689e468c4de6925dee8260 | |
| parent | a11bb7427171418681428754051d5ee4dce851ee (diff) | |
Preload only visible thumbnails and task icons. (Bug 17672056, Bug 18291345)
This change ensures that only the number of visible thumbnails and task icons are
loaded to minimize the delay required when initializing the stack without the
cache. In addition, this change reduces the number of times that the task stack
is recomposed when launching recents (in addition to the number of calls to
getRecentTasks()).
There is also a fix to a regression where the exit trigger is not run when the
task stack view is empty.
Change-Id: I75834ff3c57c0e5dad6252b982f71c6e740071f2
10 files changed, 496 insertions, 239 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 52ef4f942a76..20490c90424f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -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 diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java index be7d3229cb78..7932fde83b6b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -40,6 +40,7 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskGrouping; @@ -63,6 +64,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab"; final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey"; final public static String EXTRA_REUSE_TASK_STACK_VIEWS = "recents.reuseTaskStackViews"; + final public static String EXTRA_NUM_VISIBLE_TASKS = "recents.numVisibleTasks"; + final public static String EXTRA_NUM_VISIBLE_THUMBNAILS = "recents.numVisibleThumbnails"; final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation"; final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity"; @@ -75,6 +78,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; static RecentsComponent.Callbacks sRecentsComponentCallbacks; + static RecentsTaskLoadPlan sInstanceLoadPlan; Context mContext; LayoutInflater mInflater; @@ -133,8 +137,15 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } - // When we start, preload the metadata associated with the previous tasks - RecentsTaskLoader.getInstance().preload(mContext, RecentsTaskLoader.ALL_TASKS); + // When we start, preload the metadata and icons associated with the recent tasks. + // We can use a new plan since the caches will be the same. + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); + loader.preloadTasks(plan, true /* isTopTaskHome */); + RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); + launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize(); + launchOpts.loadThumbnails = false; + loader.loadTasks(mContext, plan, launchOpts); } public void onBootCompleted() { @@ -182,9 +193,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } public void onPreloadRecents() { - // When we start, preload the metadata associated with the previous tasks - RecentsTaskLoader.getInstance().preload(mContext, - Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); + // Preload only the raw task list into a new load plan (which will be consumed by the + // RecentsActivity) + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + sInstanceLoadPlan = loader.createLoadPlan(mContext); + sInstanceLoadPlan.preloadRawTasks(true); } public void onCancelPreloadingRecents() { @@ -193,8 +206,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta void showRelativeAffiliatedTask(boolean showNextTask) { RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); - TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(), - -1, -1, RecentsTaskLoader.ALL_TASKS, false, true, null, null); + RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); + loader.preloadTasks(plan, true /* isTopTaskHome */); + TaskStack stack = plan.getTaskStack(); + // Return early if there are no tasks if (stack.getTaskCount() == 0) return; @@ -402,11 +417,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta * Creates the activity options for an app->recents transition. */ ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask, - boolean isTopTaskHome) { + TaskStack stack, TaskStackView stackView) { // Update the destination rect Task toTask = new Task(); - TaskViewTransform toTransform = getThumbnailTransitionTransform(topTask.id, isTopTaskHome, - toTask); + TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, + topTask.id, toTask); if (toTransform != null && toTask.key != null) { Rect toTaskRect = toTransform.rect; int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); @@ -434,16 +449,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } /** Returns the transition rect for the given task id. */ - TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome, - Task runningTaskOut) { - // Get the stack of tasks that we are animating into - RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); - TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(), - runningTaskId, -1, RecentsTaskLoader.ALL_TASKS, false, isTopTaskHome, null, null); - if (stack.getTaskCount() == 0) { - return null; - } - + TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView, + int runningTaskId, Task runningTaskOut) { // Find the running task in the TaskStack Task task = null; ArrayList<Task> tasks = stack.getTasks(); @@ -465,30 +472,42 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } // Get the transform for the running task - mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); - mDummyStackView.getScroller().setStackScrollToInitialState(); - mTmpTransform = mDummyStackView.getStackAlgorithm().getStackTransform(task, - mDummyStackView.getScroller().getStackScroll(), mTmpTransform, null); + stackView.getScroller().setStackScrollToInitialState(); + mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, + stackView.getScroller().getStackScroll(), mTmpTransform, null); return mTmpTransform; } /** Starts the recents activity */ void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) { - // If Recents is not the front-most activity and we should animate into it. If - // the activity at the root of the top task stack in the home stack, then we just do a - // simple transition. Otherwise, we animate to the rects defined by the Recents service, - // which can differ depending on the number of items in the list. - SystemServicesProxy ssp = mSystemServicesProxy; - List<ActivityManager.RecentTaskInfo> recentTasks = - ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier(), isTopTaskHome); - boolean useThumbnailTransition = !isTopTaskHome; - boolean hasRecentTasks = !recentTasks.isEmpty(); + if (sInstanceLoadPlan == null) { + // Create a new load plan if onPreloadRecents() was never triggered + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + sInstanceLoadPlan = loader.createLoadPlan(mContext); + } + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); + TaskStack stack = sInstanceLoadPlan.getTaskStack(); + + // Prepare the dummy stack for the transition + mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); + TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = + mDummyStackView.computeStackVisibilityReport(); + boolean hasRecentTasks = stack.getTaskCount() > 0; + boolean useThumbnailTransition = !isTopTaskHome && hasRecentTasks; if (useThumbnailTransition) { + // Ensure that we load the running task's icon + RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); + launchOpts.runningTaskId = topTask.id; + launchOpts.loadThumbnails = false; + loader.loadTasks(mContext, sInstanceLoadPlan, launchOpts); + // Try starting with a thumbnail transition - ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, isTopTaskHome); + ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, + mDummyStackView); if (opts != null) { - startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL); + startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL, stackVr); } else { // Fall through below to the non-thumbnail transition useThumbnailTransition = false; @@ -522,11 +541,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); startAlternateRecentsActivity(topTask, opts, - fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME); + fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME, stackVr); } else { // Otherwise we do the normal fade from an unknown source ActivityOptions opts = getUnknownTransitionActivityOptions(); - startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME); + startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME, stackVr); } } mLastToggleTime = System.currentTimeMillis(); @@ -534,7 +553,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta /** Starts the recents activity */ void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask, - ActivityOptions opts, String extraFlag) { + ActivityOptions opts, String extraFlag, + TaskStackViewLayoutAlgorithm.VisibilityReport vr) { Intent intent = new Intent(sToggleRecentsAction); intent.setClassName(sRecentsPackage, sRecentsActivity); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK @@ -546,6 +566,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab); intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1); intent.putExtra(EXTRA_REUSE_TASK_STACK_VIEWS, mCanReuseTaskStackViews); + intent.putExtra(EXTRA_NUM_VISIBLE_TASKS, vr.numVisibleTasks); + intent.putExtra(EXTRA_NUM_VISIBLE_THUMBNAILS, vr.numVisibleThumbnails); if (opts != null) { mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); } else { @@ -566,6 +588,15 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } + /** + * Returns the preloaded load plan and invalidates it. + */ + public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { + RecentsTaskLoadPlan plan = sInstanceLoadPlan; + sInstanceLoadPlan = null; + return plan; + } + /**** OnAnimationStartedListener Implementation ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 9b84d2e8aa5e..4c76af705614 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java +++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index f8d981fc2d97..d5bc29298af5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -27,10 +27,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.os.UserHandle; import android.util.Pair; import android.view.KeyEvent; @@ -42,6 +39,7 @@ import com.android.systemui.recents.misc.DebugTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.SpaceNode; import com.android.systemui.recents.model.Task; @@ -163,9 +161,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (action.equals(Intent.ACTION_SCREEN_OFF)) { // When the screen turns off, dismiss Recents to Home dismissRecentsToHome(false); - // Start preloading some tasks in the background - RecentsTaskLoader.getInstance().preload(RecentsActivity.this, - Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); + // Preload the metadata for all tasks in the background + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + RecentsTaskLoadPlan plan = loader.createLoadPlan(context); + loader.preloadTasks(plan, true /* isTopTaskHome */); } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) { // When the search activity changes, update the Search widget refreshSearchWidget(); @@ -188,6 +187,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Update the configuration based on the launch intent boolean fromSearchHome = launchIntent.getBooleanExtra( AlternateRecentsComponent.EXTRA_FROM_SEARCH_HOME, false); + int numVisibleTasks = launchIntent.getIntExtra( + AlternateRecentsComponent.EXTRA_NUM_VISIBLE_TASKS, 0); + int numVisibleThumbnails = launchIntent.getIntExtra( + AlternateRecentsComponent.EXTRA_NUM_VISIBLE_THUMBNAILS, 0); mConfig.launchedFromHome = fromSearchHome || launchIntent.getBooleanExtra( AlternateRecentsComponent.EXTRA_FROM_HOME, false); mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra( @@ -199,16 +202,29 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mConfig.launchedReuseTaskStackViews = launchIntent.getBooleanExtra( AlternateRecentsComponent.EXTRA_REUSE_TASK_STACK_VIEWS, false); - // Load all the tasks + // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent + // reconstructing the task stack RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); - SpaceNode root = loader.reload(this, - Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount, - mConfig.launchedFromHome); + RecentsTaskLoadPlan plan = AlternateRecentsComponent.consumeInstanceLoadPlan(); + if (plan == null) { + plan = loader.createLoadPlan(this); + loader.preloadTasks(plan, mConfig.launchedFromHome); + } + + // 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(); - if (!stacks.isEmpty()) { - mRecentsView.setTaskStacks(root.getStacks()); + boolean hasTasks = root.hasTasks(); + if (hasTasks) { + mRecentsView.setTaskStacks(stacks); } - mConfig.launchedWithNoRecentTasks = !root.hasTasks(); + mConfig.launchedWithNoRecentTasks = !hasTasks; // Create the home intent runnable Intent homeIntent = new Intent(Intent.ACTION_MAIN, null); @@ -434,6 +450,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView /** Inflates the debug overlay if debug mode is enabled. */ void inflateDebugOverlay() { + if (!Constants.DebugFlags.App.EnableDebugMode) return; + if (mConfig.debugModeEnabled && mDebugOverlay == null) { // Inflate the overlay and seek bars mDebugOverlay = (DebugOverlayView) mDebugOverlayStub.inflate(); @@ -592,13 +610,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply(); mConfig.debugModeEnabled = false; inflateDebugOverlay(); - mDebugOverlay.disable(); + if (mDebugOverlay != null) { + mDebugOverlay.disable(); + } } else { // Enable the debug mode settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply(); mConfig.debugModeEnabled = true; inflateDebugOverlay(); - mDebugOverlay.enable(); + if (mDebugOverlay != null) { + mDebugOverlay.enable(); + } } Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " + (mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now", diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index b66138518466..b9e066d3ebd0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -242,6 +242,7 @@ public class SystemServicesProxy { Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId); if (thumbnail != null) { + thumbnail.setHasAlpha(false); // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top // left pixel, then assume the whole thumbnail is transparent. Generally, proper // screenshots are always composed onto a bitmap that has no alpha. diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java new file mode 100644 index 000000000000..41251c841950 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -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; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java index 390507f12a56..6fd738d01377 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java @@ -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,30 +319,45 @@ 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 label using as many cached values as we can. */ + public String getAndUpdateActivityLabel(Task.TaskKey taskKey, + ActivityManager.TaskDescription td, SystemServicesProxy ssp, + ActivityInfoHandle infoHandle) { + // Return the task description label if it exists + if (td != null && td.getLabel() != null) { + return td.getLabel(); + } + // Return the cached activity label if it exists + String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey); + if (label != null) { + return label; + } + // All short paths failed, load the label from the activity info and cache it + if (infoHandle.info == null) { + infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(), + taskKey.userId); + } + if (infoHandle.info != null) { + label = ssp.getActivityLabel(infoHandle.info); + mActivityLabelCache.put(taskKey, label); + } else { + Log.w(TAG, "Missing ActivityInfo for " + taskKey.baseIntent.getComponent() + + " u=" + taskKey.userId); + } + 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 preloadTask) { + 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 we are preloading this task, continue to load the task description icon or the - // activity icon - if (preloadTask) { - + if (loadIfNotCached) { // Return and cache the task description icon if it exists Drawable tdDrawable = mLoader.getTaskDescriptionIcon(taskKey, td.getInMemoryIcon(), td.getIconFilename(), ssp, res); @@ -370,36 +379,29 @@ public class RecentsTaskLoader { } } } - // If we couldn't load any icon, return null + // We couldn't load any icon 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, - ActivityInfoHandle infoHandle) { - // Return the task description label if it exists - if (td != null && td.getLabel() != null) { - return td.getLabel(); - } - // Return the cached activity label if it exists - String label = mActivityLabelCache.getAndInvalidateIfModified(taskKey); - if (label != null) { - return label; - } - // All short paths failed, load the label from the activity info and cache it - if (infoHandle.info == null) { - infoHandle.info = ssp.getActivityInfo(taskKey.baseIntent.getComponent(), - taskKey.userId); + /** 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 (infoHandle.info != null) { - label = ssp.getActivityLabel(infoHandle.info); - mActivityLabelCache.put(taskKey, label); - } else { - Log.w(TAG, "Missing ActivityInfo for " + taskKey.baseIntent.getComponent() - + " u=" + taskKey.userId); + + if (loadIfNotCached) { + // Load the thumbnail from the system + thumbnail = ssp.getTaskThumbnail(taskKey.id); + if (thumbnail != null) { + mThumbnailCache.put(taskKey, thumbnail); + return thumbnail; + } } - return label; + // We couldn't load any thumbnail + return null; } /** Returns the activity's primary color. */ @@ -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(); - - 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 - 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); - } + RecentsTaskLoadPlan plan = new RecentsTaskLoadPlan(context, config, mSystemServicesProxy); + return plan; + } - 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); - } - } + public void preloadTasks(RecentsTaskLoadPlan plan, boolean isTopTaskHome) { + plan.preloadPlan(this, isTopTaskHome); + } - // 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"); + } + plan.executePlan(opts, this); + if (opts.numVisibleTasks > 0) { + mNumVisibleTasksLoaded = opts.numVisibleTasks; } - stack.setTasks(tasksToAdd); - stack.createAffiliatedGroupings(config); - return stack; + + // 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: diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 81ee839b67b2..98ad9bf6e9d5 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -214,6 +214,9 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Requests all task stacks to start their exit-recents animation */ public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { + // We have to increment/decrement the post animation trigger in case there are no children + // to ensure that it runs + ctx.postAnimationTrigger.increment(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); @@ -222,6 +225,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV stackView.startExitToHomeAnimation(ctx); } } + ctx.postAnimationTrigger.decrement(); // Notify of the exit animation mCb.onExitToHomeAnimationTriggered(); @@ -501,7 +505,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Launch the app right away if there is no task view, otherwise, animate the icon out first if (tv == null) { - post(launchRunnable); + launchRunnable.run(); } else { if (!task.group.isFrontMostTask(task)) { // For affiliated tasks that are behind other tasks, we must animate the front cards @@ -510,7 +514,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } else { // Otherwise, we can start the task transition immediately stackView.startLaunchTaskAnimation(tv, null, lockToTask); - postDelayed(launchRunnable, 17); + launchRunnable.run(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 9df0db611d08..dd76efc4163b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -622,6 +622,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** + * 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. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java index c549d2b45589..5767e18fbc43 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java @@ -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 |