diff options
author | Winson Chung <winsonc@google.com> | 2014-11-12 03:15:25 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2014-11-12 03:15:25 +0000 |
commit | 9922539facdc52124f9329969c74dcc7b39c8161 (patch) | |
tree | 6253acdcfaa9a8c455805d2578f126ed1ea9fa89 | |
parent | e73cab5f1226f7434fa8bbf341e91891916683c5 (diff) | |
parent | 6a601c9ee8cee46680417dc450e9bb64c86a1b09 (diff) | |
download | frameworks_base-9922539facdc52124f9329969c74dcc7b39c8161.zip frameworks_base-9922539facdc52124f9329969c74dcc7b39c8161.tar.gz frameworks_base-9922539facdc52124f9329969c74dcc7b39c8161.tar.bz2 |
Merge "Preload only visible thumbnails and task icons. (Bug 17672056, Bug 18291345)" into lmp-mr1-dev automerge: 147de3a
automerge: 6a601c9
* commit '6a601c9ee8cee46680417dc450e9bb64c86a1b09':
Preload only visible thumbnails and task icons. (Bug 17672056, Bug 18291345)
10 files changed, 496 insertions, 239 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9b6737a..afdfcd4 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 2bfdb69..b5a6dac 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -41,6 +41,7 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskGrouping; @@ -64,6 +65,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta final public static String EXTRA_TRIGGERED_FROM_ALT_TAB = "recents.triggeredFromAltTab"; final public static String EXTRA_TRIGGERED_FROM_HOME_KEY = "recents.triggeredFromHomeKey"; final public static String EXTRA_REUSE_TASK_STACK_VIEWS = "recents.reuseTaskStackViews"; + final public static String EXTRA_NUM_VISIBLE_TASKS = "recents.numVisibleTasks"; + final public static String EXTRA_NUM_VISIBLE_THUMBNAILS = "recents.numVisibleThumbnails"; final public static String ACTION_START_ENTER_ANIMATION = "action_start_enter_animation"; final public static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity"; @@ -76,6 +79,7 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta final static String sRecentsActivity = "com.android.systemui.recents.RecentsActivity"; static RecentsComponent.Callbacks sRecentsComponentCallbacks; + static RecentsTaskLoadPlan sInstanceLoadPlan; Context mContext; LayoutInflater mInflater; @@ -134,8 +138,15 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } - // When we start, preload the metadata associated with the previous tasks - RecentsTaskLoader.getInstance().preload(mContext, RecentsTaskLoader.ALL_TASKS); + // When we start, preload the metadata and icons associated with the recent tasks. + // We can use a new plan since the caches will be the same. + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); + loader.preloadTasks(plan, true /* isTopTaskHome */); + RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); + launchOpts.numVisibleTasks = loader.getApplicationIconCacheSize(); + launchOpts.loadThumbnails = false; + loader.loadTasks(mContext, plan, launchOpts); } public void onBootCompleted() { @@ -183,9 +194,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } public void onPreloadRecents() { - // When we start, preload the metadata associated with the previous tasks - RecentsTaskLoader.getInstance().preload(mContext, - Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); + // Preload only the raw task list into a new load plan (which will be consumed by the + // RecentsActivity) + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + sInstanceLoadPlan = loader.createLoadPlan(mContext); + sInstanceLoadPlan.preloadRawTasks(true); } public void onCancelPreloadingRecents() { @@ -194,8 +207,10 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta void showRelativeAffiliatedTask(boolean showNextTask) { RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); - TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(), - -1, -1, RecentsTaskLoader.ALL_TASKS, false, true, null, null); + RecentsTaskLoadPlan plan = loader.createLoadPlan(mContext); + loader.preloadTasks(plan, true /* isTopTaskHome */); + TaskStack stack = plan.getTaskStack(); + // Return early if there are no tasks if (stack.getTaskCount() == 0) return; @@ -411,11 +426,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta * Creates the activity options for an app->recents transition. */ ActivityOptions getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo topTask, - boolean isTopTaskHome) { + TaskStack stack, TaskStackView stackView) { // Update the destination rect Task toTask = new Task(); - TaskViewTransform toTransform = getThumbnailTransitionTransform(topTask.id, isTopTaskHome, - toTask); + TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView, + topTask.id, toTask); if (toTransform != null && toTask.key != null) { Rect toTaskRect = toTransform.rect; int toHeaderWidth = (int) (mHeaderBar.getMeasuredWidth() * toTransform.scale); @@ -443,16 +458,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } /** Returns the transition rect for the given task id. */ - TaskViewTransform getThumbnailTransitionTransform(int runningTaskId, boolean isTopTaskHome, - Task runningTaskOut) { - // Get the stack of tasks that we are animating into - RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); - TaskStack stack = loader.getTaskStack(mSystemServicesProxy, mContext.getResources(), - runningTaskId, -1, RecentsTaskLoader.ALL_TASKS, false, isTopTaskHome, null, null); - if (stack.getTaskCount() == 0) { - return null; - } - + TaskViewTransform getThumbnailTransitionTransform(TaskStack stack, TaskStackView stackView, + int runningTaskId, Task runningTaskOut) { // Find the running task in the TaskStack Task task = null; ArrayList<Task> tasks = stack.getTasks(); @@ -474,30 +481,42 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } // Get the transform for the running task - mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); - mDummyStackView.getScroller().setStackScrollToInitialState(); - mTmpTransform = mDummyStackView.getStackAlgorithm().getStackTransform(task, - mDummyStackView.getScroller().getStackScroll(), mTmpTransform, null); + stackView.getScroller().setStackScrollToInitialState(); + mTmpTransform = stackView.getStackAlgorithm().getStackTransform(task, + stackView.getScroller().getStackScroll(), mTmpTransform, null); return mTmpTransform; } /** Starts the recents activity */ void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, boolean isTopTaskHome) { - // If Recents is not the front-most activity and we should animate into it. If - // the activity at the root of the top task stack in the home stack, then we just do a - // simple transition. Otherwise, we animate to the rects defined by the Recents service, - // which can differ depending on the number of items in the list. - SystemServicesProxy ssp = mSystemServicesProxy; - List<ActivityManager.RecentTaskInfo> recentTasks = - ssp.getRecentTasks(3, UserHandle.CURRENT.getIdentifier(), isTopTaskHome); - boolean useThumbnailTransition = !isTopTaskHome; - boolean hasRecentTasks = !recentTasks.isEmpty(); + if (sInstanceLoadPlan == null) { + // Create a new load plan if onPreloadRecents() was never triggered + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + sInstanceLoadPlan = loader.createLoadPlan(mContext); + } + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + loader.preloadTasks(sInstanceLoadPlan, isTopTaskHome); + TaskStack stack = sInstanceLoadPlan.getTaskStack(); + + // Prepare the dummy stack for the transition + mDummyStackView.updateMinMaxScrollForStack(stack, mTriggeredFromAltTab, isTopTaskHome); + TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = + mDummyStackView.computeStackVisibilityReport(); + boolean hasRecentTasks = stack.getTaskCount() > 0; + boolean useThumbnailTransition = !isTopTaskHome && hasRecentTasks; if (useThumbnailTransition) { + // Ensure that we load the running task's icon + RecentsTaskLoadPlan.Options launchOpts = new RecentsTaskLoadPlan.Options(); + launchOpts.runningTaskId = topTask.id; + launchOpts.loadThumbnails = false; + loader.loadTasks(mContext, sInstanceLoadPlan, launchOpts); + // Try starting with a thumbnail transition - ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, isTopTaskHome); + ActivityOptions opts = getThumbnailTransitionActivityOptions(topTask, stack, + mDummyStackView); if (opts != null) { - startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL); + startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_APP_THUMBNAIL, stackVr); } else { // Fall through below to the non-thumbnail transition useThumbnailTransition = false; @@ -531,11 +550,11 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta ActivityOptions opts = getHomeTransitionActivityOptions(fromSearchHome); startAlternateRecentsActivity(topTask, opts, - fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME); + fromSearchHome ? EXTRA_FROM_SEARCH_HOME : EXTRA_FROM_HOME, stackVr); } else { // Otherwise we do the normal fade from an unknown source ActivityOptions opts = getUnknownTransitionActivityOptions(); - startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME); + startAlternateRecentsActivity(topTask, opts, EXTRA_FROM_HOME, stackVr); } } mLastToggleTime = System.currentTimeMillis(); @@ -543,7 +562,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta /** Starts the recents activity */ void startAlternateRecentsActivity(ActivityManager.RunningTaskInfo topTask, - ActivityOptions opts, String extraFlag) { + ActivityOptions opts, String extraFlag, + TaskStackViewLayoutAlgorithm.VisibilityReport vr) { Intent intent = new Intent(sToggleRecentsAction); intent.setClassName(sRecentsPackage, sRecentsActivity); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK @@ -555,6 +575,8 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta intent.putExtra(EXTRA_TRIGGERED_FROM_ALT_TAB, mTriggeredFromAltTab); intent.putExtra(EXTRA_FROM_TASK_ID, (topTask != null) ? topTask.id : -1); intent.putExtra(EXTRA_REUSE_TASK_STACK_VIEWS, mCanReuseTaskStackViews); + intent.putExtra(EXTRA_NUM_VISIBLE_TASKS, vr.numVisibleTasks); + intent.putExtra(EXTRA_NUM_VISIBLE_THUMBNAILS, vr.numVisibleThumbnails); if (opts != null) { mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT); } else { @@ -575,6 +597,15 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta } } + /** + * Returns the preloaded load plan and invalidates it. + */ + public static RecentsTaskLoadPlan consumeInstanceLoadPlan() { + RecentsTaskLoadPlan plan = sInstanceLoadPlan; + sInstanceLoadPlan = null; + return plan; + } + /**** OnAnimationStartedListener Implementation ****/ @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java index 9b84d2e..4c76af7 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 de95ae8..9a7fec4 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; @@ -43,6 +40,7 @@ import com.android.systemui.recents.misc.DebugTrigger; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.RecentsTaskLoadPlan; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.SpaceNode; import com.android.systemui.recents.model.Task; @@ -168,9 +166,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView if (action.equals(Intent.ACTION_SCREEN_OFF)) { // When the screen turns off, dismiss Recents to Home dismissRecentsToHome(false); - // Start preloading some tasks in the background - RecentsTaskLoader.getInstance().preload(RecentsActivity.this, - Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount); + // Preload the metadata for all tasks in the background + RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); + RecentsTaskLoadPlan plan = loader.createLoadPlan(context); + loader.preloadTasks(plan, true /* isTopTaskHome */); } else if (action.equals(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED)) { // When the search activity changes, update the Search widget refreshSearchWidget(); @@ -193,6 +192,10 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // Update the configuration based on the launch intent boolean fromSearchHome = launchIntent.getBooleanExtra( AlternateRecentsComponent.EXTRA_FROM_SEARCH_HOME, false); + int numVisibleTasks = launchIntent.getIntExtra( + AlternateRecentsComponent.EXTRA_NUM_VISIBLE_TASKS, 0); + int numVisibleThumbnails = launchIntent.getIntExtra( + AlternateRecentsComponent.EXTRA_NUM_VISIBLE_THUMBNAILS, 0); mConfig.launchedFromHome = fromSearchHome || launchIntent.getBooleanExtra( AlternateRecentsComponent.EXTRA_FROM_HOME, false); mConfig.launchedFromAppWithThumbnail = launchIntent.getBooleanExtra( @@ -204,16 +207,29 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView mConfig.launchedReuseTaskStackViews = launchIntent.getBooleanExtra( AlternateRecentsComponent.EXTRA_REUSE_TASK_STACK_VIEWS, false); - // Load all the tasks + // If AlternateRecentsComponent has preloaded a load plan, then use that to prevent + // reconstructing the task stack RecentsTaskLoader loader = RecentsTaskLoader.getInstance(); - SpaceNode root = loader.reload(this, - Constants.Values.RecentsTaskLoader.PreloadFirstTasksCount, - mConfig.launchedFromHome); + 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); @@ -442,6 +458,8 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView /** Inflates the debug overlay if debug mode is enabled. */ void inflateDebugOverlay() { + if (!Constants.DebugFlags.App.EnableDebugMode) return; + if (mConfig.debugModeEnabled && mDebugOverlay == null) { // Inflate the overlay and seek bars mDebugOverlay = (DebugOverlayView) mDebugOverlayStub.inflate(); @@ -600,13 +618,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView settings.edit().remove(Constants.Values.App.Key_DebugModeEnabled).apply(); mConfig.debugModeEnabled = false; inflateDebugOverlay(); - mDebugOverlay.disable(); + if (mDebugOverlay != null) { + mDebugOverlay.disable(); + } } else { // Enable the debug mode settings.edit().putBoolean(Constants.Values.App.Key_DebugModeEnabled, true).apply(); mConfig.debugModeEnabled = true; inflateDebugOverlay(); - mDebugOverlay.enable(); + if (mDebugOverlay != null) { + mDebugOverlay.enable(); + } } Toast.makeText(this, "Debug mode (" + Constants.Values.App.DebugModeVersion + ") " + (mConfig.debugModeEnabled ? "Enabled" : "Disabled") + ", please restart Recents now", 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 51b3fb5..9a4bd08 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -244,6 +244,7 @@ public class SystemServicesProxy { Bitmap thumbnail = SystemServicesProxy.getThumbnail(mAm, taskId); if (thumbnail != null) { + thumbnail.setHasAlpha(false); // We use a dumb heuristic for now, if the thumbnail is purely transparent in the top // left pixel, then assume the whole thumbnail is transparent. Generally, proper // screenshots are always composed onto a bitmap that has no alpha. 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 0000000..41251c8 --- /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 390507f..6fd738d 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 6093584..77a050a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -216,6 +216,9 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV /** Requests all task stacks to start their exit-recents animation */ public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) { + // We have to increment/decrement the post animation trigger in case there are no children + // to ensure that it runs + ctx.postAnimationTrigger.increment(); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); @@ -224,6 +227,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV stackView.startExitToHomeAnimation(ctx); } } + ctx.postAnimationTrigger.decrement(); // Notify of the exit animation mCb.onExitToHomeAnimationTriggered(); @@ -503,7 +507,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Launch the app right away if there is no task view, otherwise, animate the icon out first if (tv == null) { - post(launchRunnable); + launchRunnable.run(); } else { if (!task.group.isFrontMostTask(task)) { // For affiliated tasks that are behind other tasks, we must animate the front cards @@ -512,7 +516,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } else { // Otherwise, we can start the task transition immediately stackView.startLaunchTaskAnimation(tv, null, lockToTask); - postDelayed(launchRunnable, 17); + launchRunnable.run(); } } } 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 bef4cd1..c3077b0 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -624,6 +624,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 c549d2b..5767e18 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 |