diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 6c8bf0f96bffb..dc802595d0b96 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -616,15 +616,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView ViewAnimation.TaskViewEnterContext ctx = new ViewAnimation.TaskViewEnterContext(t); ctx.postAnimationTrigger.increment(); if (mSearchWidgetInfo != null) { - ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { - @Override - public void run() { - // Start listening for widget package changes if there is one bound - if (!Constants.DebugFlags.App.DisableSearchBar && mAppWidgetHost != null) { - mAppWidgetHost.startListening(); + if (!Constants.DebugFlags.App.DisableSearchBar) { + ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { + @Override + public void run() { + // Start listening for widget package changes if there is one bound + if (mAppWidgetHost != null) { + mAppWidgetHost.startListening(); + } } - } - }); + }); + } } ctx.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { @Override diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index 2042668c084e2..7f9bdbdfb04c3 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java @@ -53,7 +53,7 @@ import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskGrouping; import com.android.systemui.recents.model.TaskStack; import com.android.systemui.recents.views.TaskStackView; -import com.android.systemui.recents.views.TaskStackViewLayoutAlgorithm; +import com.android.systemui.recents.views.TaskStackLayoutAlgorithm; import com.android.systemui.recents.views.TaskViewHeader; import com.android.systemui.recents.views.TaskViewTransform; import com.android.systemui.statusbar.phone.PhoneStatusBar; @@ -465,10 +465,10 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub mSearchBarBounds, mTaskStackBounds); // Rebind the header bar and draw it for the transition - TaskStackViewLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); + TaskStackLayoutAlgorithm algo = mDummyStackView.getStackAlgorithm(); Rect taskStackBounds = new Rect(mTaskStackBounds); algo.setSystemInsets(systemInsets); - algo.computeRects(taskStackBounds); + algo.initialize(taskStackBounds); Rect taskViewBounds = algo.getUntransformedTaskViewBounds(); if (!taskViewBounds.equals(mLastTaskViewBounds)) { mLastTaskViewBounds.set(taskViewBounds); @@ -666,7 +666,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub // Prepare the dummy stack for the transition mDummyStackView.updateMinMaxScrollForStack(stack); - TaskStackViewLayoutAlgorithm.VisibilityReport stackVr = + TaskStackLayoutAlgorithm.VisibilityReport stackVr = mDummyStackView.computeStackVisibilityReport(); boolean hasRecentTasks = stack.getTaskCount() > 0; boolean useThumbnailTransition = (topTask != null) && !isTopTaskHome && hasRecentTasks; @@ -713,7 +713,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub */ private void startRecentsActivity(ActivityManager.RunningTaskInfo topTask, ActivityOptions opts, boolean fromHome, boolean fromSearchHome, boolean fromThumbnail, - TaskStackViewLayoutAlgorithm.VisibilityReport vr) { + TaskStackLayoutAlgorithm.VisibilityReport vr) { mStartAnimationTriggered = false; // Update the configuration based on the launch options diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java index 6fc4e20a9810b..6c83b87f33915 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java @@ -137,12 +137,19 @@ public class RecentsTaskLoadPlan { task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false); if (DEBUG) Log.d(TAG, "\tthumbnail: " + taskKey + ", " + task.thumbnail); - stackTasks.add(task); + if (task.isFreeformTask()) { + freeformTasks.add(task); + } else { + stackTasks.add(task); + } } // Initialize the stacks + ArrayList allTasks = new ArrayList<>(); + allTasks.addAll(stackTasks); + allTasks.addAll(freeformTasks); mStack = new TaskStack(); - mStack.setTasks(stackTasks); + mStack.setTasks(allTasks); mStack.createAffiliatedGroupings(mContext); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index b3937c3a6ecf6..a96fe98bd8177 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -399,6 +399,21 @@ public class TaskStack { return mTaskList.size(); } + /** + * Returns the task in this stack which is the launch target. + */ + public Task getLaunchTarget() { + ArrayList tasks = mTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.isLaunchTarget) { + return task; + } + } + return null; + } + /** Returns the index of this task in this current task stack */ public int indexOfTask(Task t) { return mTaskList.indexOf(t); @@ -417,6 +432,21 @@ public class TaskStack { return null; } + /** + * Returns whether this stack has freeform tasks. + */ + public boolean hasFreeformTasks() { + ArrayList tasks = mTaskList.getTasks(); + int taskCount = tasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = tasks.get(i); + if (task.isFreeformTask()) { + return true; + } + } + return false; + } + /******** Filtering ********/ /** Filters the stack into tasks similar to the one specified */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java new file mode 100644 index 0000000000000..ce993c537e765 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/FreeformWorkspaceLayoutAlgorithm.java @@ -0,0 +1,108 @@ +/* + * 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.views; + +import android.util.Log; +import com.android.systemui.recents.model.Task; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * The layout logic for the contents of the freeform workspace. + */ +public class FreeformWorkspaceLayoutAlgorithm { + + private static final String TAG = "FreeformWorkspaceLayoutAlgorithm"; + private static final boolean DEBUG = false; + + // The number of cells in the freeform workspace + private int mFreeformCellXCount; + private int mFreeformCellYCount; + // The width and height of the cells in the freeform workspace + private int mFreeformCellWidth; + private int mFreeformCellHeight; + + // Optimization, allows for quick lookup of task -> index + private HashMap mTaskIndexMap = new HashMap<>(); + + /** + * Updates the layout for each of the freeform workspace tasks. This is called after the stack + * layout is updated. + */ + public void update(ArrayList freeformTasks, TaskStackLayoutAlgorithm stackLayout) { + int numFreeformTasks = stackLayout.mNumFreeformTasks; + if (!freeformTasks.isEmpty()) { + // Calculate the cell width/height depending on the number of freeform tasks + mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(numFreeformTasks))); + mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) numFreeformTasks / mFreeformCellXCount)); + mFreeformCellWidth = stackLayout.mFreeformRect.width() / mFreeformCellXCount; + // For now, make the cells square + mFreeformCellHeight = mFreeformCellWidth; + + // Put each of the tasks in the progress map at a fixed index (does not need to actually + // map to a scroll position, just by index) + int taskCount = freeformTasks.size(); + for (int i = 0; i < taskCount; i++) { + Task task = freeformTasks.get(i); + mTaskIndexMap.put(task.key, i); + } + + if (DEBUG) { + Log.d(TAG, "mFreeformCellXCount: " + mFreeformCellXCount); + Log.d(TAG, "mFreeformCellYCount: " + mFreeformCellYCount); + Log.d(TAG, "mFreeformCellWidth: " + mFreeformCellWidth); + Log.d(TAG, "mFreeformCellHeight: " + mFreeformCellHeight); + } + } + } + + /** + * Returns whether the transform is available for the given task. + */ + public boolean isTransformAvailable(Task task, float stackScroll, + TaskStackLayoutAlgorithm stackLayout) { + if (stackLayout.mNumFreeformTasks == 0 || task == null || + !mTaskIndexMap.containsKey(task.key)) { + return false; + } + return stackScroll > stackLayout.mStackEndScrollP; + } + + /** + * Returns the transform for the given task. Any rect returned will be offset by the actual + * transform for the freeform workspace. + */ + public TaskViewTransform getTransform(Task task, float stackScroll, + TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) { + if (Float.compare(stackScroll, stackLayout.mStackEndScrollP) > 0) { + // This is a freeform task, so lay it out in the freeform workspace + int taskIndex = mTaskIndexMap.get(task.key); + int x = taskIndex % mFreeformCellXCount; + int y = taskIndex / mFreeformCellXCount; + float scale = (float) mFreeformCellWidth / stackLayout.mTaskRect.width(); + int scaleXOffset = (int) (((1f - scale) * stackLayout.mTaskRect.width()) / 2); + int scaleYOffset = (int) (((1f - scale) * stackLayout.mTaskRect.height()) / 2); + transformOut.scale = scale * 0.9f; + transformOut.translationX = x * mFreeformCellWidth - scaleXOffset; + transformOut.translationY = y * mFreeformCellHeight - scaleYOffset; + transformOut.visible = true; + return transformOut; + } + return null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java similarity index 68% rename from packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java rename to packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index c74c65456bdb0..ff02e03cd3358 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -26,6 +26,7 @@ import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.misc.ParametricCurve; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; import java.util.HashMap; @@ -33,9 +34,8 @@ import java.util.HashMap; /** * The layout logic for a TaskStackView. - * */ -public class TaskStackViewLayoutAlgorithm { +public class TaskStackLayoutAlgorithm { private static final String TAG = "TaskStackViewLayoutAlgorithm"; private static final boolean DEBUG = false; @@ -46,6 +46,10 @@ public class TaskStackViewLayoutAlgorithm { private static final float SINGLE_TASK_SCALE = 0.95f; // The percentage of height of task to show between tasks private static final float VISIBLE_TASK_HEIGHT_BETWEEN_TASKS = 0.5f; + // The percentage between the maxStackScroll and the maxScroll where a given scroll will still + // snap back to the maxStackScroll instead of to the maxScroll (which shows the freeform + // workspace) + private static final float SNAP_TO_MAX_STACK_SCROLL_FACTOR = 0.3f; // A report of the visibility state of the stack public class VisibilityReport { @@ -64,11 +68,9 @@ public class TaskStackViewLayoutAlgorithm { // This is the view bounds inset exactly by the search bar, but without the bottom inset // see RecentsConfiguration.getTaskStackBounds() public Rect mStackRect = new Rect(); - // This is the task view bounds for layout (untransformed), the rect is top-aligned to the top // of the stack rect public Rect mTaskRect = new Rect(); - // The bounds of the freeform workspace, the rect is top-aligned to the top of the stack rect public Rect mFreeformRect = new Rect(); // This is the current system insets @@ -79,10 +81,14 @@ public class TaskStackViewLayoutAlgorithm { // The largest scroll progress, at this value, the front most task will be visible above the // navigation bar float mMaxScrollP; - // The scroll progress at which the stack scroll ends and the overscroll begins. This serves - // as the point at which we can show the freeform space. - float mMaxStackScrollP; - // The initial progress that the scroller is set + // The scroll progress at which bottom of the first task of the stack is aligned with the bottom + // of the stack + float mStackEndScrollP; + // The scroll progress that we actually want to scroll the user to when they want to go to the + // end of the stack (it accounts for the nav bar, so that the bottom of the task is offset from + // the bottom of the stack) + float mPreferredStackEndScrollP; + // The initial progress that the scroller is set when you first enter recents float mInitialScrollP; // The task progress for the front-most task in the stack float mFrontMostTaskP; @@ -96,20 +102,18 @@ public class TaskStackViewLayoutAlgorithm { float mTaskHeightPOffset; // The relative progress to ensure that the half task height is respected float mTaskHalfHeightPOffset; + // The front-most task bottom offset + int mStackBottomOffset; // The relative progress to ensure that the offset from the bottom of the stack to the bottom // of the task is respected - float mTaskBottomPOffset; - // The relative progress to ensure that the freeform workspace height is respected + float mStackBottomPOffset; + // The freeform workspace gap + int mFreeformWorkspaceGapOffset; + float mFreeformWorkspaceGapPOffset; + // The relative progress to ensure that the freeform workspace height + gap + stack bottom + // padding is respected + int mFreeformWorkspaceOffset; float mFreeformWorkspacePOffset; - // The front-most task bottom offset - int mTaskBottomOffset; - - // The number of cells in the freeform workspace - int mFreeformCellXCount; - int mFreeformCellYCount; - // The width and height of the cells in the freeform workspace - int mFreeformCellWidth; - int mFreeformCellHeight; // The last computed task counts int mNumStackTasks; @@ -121,14 +125,21 @@ public class TaskStackViewLayoutAlgorithm { // Optimization, allows for quick lookup of task -> progress HashMap mTaskProgressMap = new HashMap<>(); + // The freeform workspace layout + FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm; + + // Temporary task view transform + TaskViewTransform mTmpTransform = new TaskViewTransform(); + // Log function static ParametricCurve sCurve; - public TaskStackViewLayoutAlgorithm(Context context) { + public TaskStackLayoutAlgorithm(Context context) { Resources res = context.getResources(); mMinTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_min); mMaxTranslationZ = res.getDimensionPixelSize(R.dimen.recents_task_view_z_max); mContext = context; + mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(); if (sCurve == null) { sCurve = new ParametricCurve(new ParametricCurve.CurveFunction() { // The large the XScale, the longer the flat area of the curve @@ -151,6 +162,11 @@ public class TaskStackViewLayoutAlgorithm { }, new ParametricCurve.ParametricCurveFunction() { @Override public float f(float p) { + // Don't scale when there are freeform tasks + if (mNumFreeformTasks > 0) { + return 1f; + } + if (p < 0) return STACK_PEEK_MIN_SCALE; if (p > 1) return 1f; float scaleRange = (1f - STACK_PEEK_MIN_SCALE); @@ -174,7 +190,7 @@ public class TaskStackViewLayoutAlgorithm { /** * Computes the stack and task rects. */ - public void computeRects(Rect taskStackBounds) { + public void initialize(Rect taskStackBounds) { RecentsConfiguration config = Recents.getConfiguration(); int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width()); int heightPadding = mContext.getResources().getDimensionPixelSize( @@ -183,14 +199,17 @@ public class TaskStackViewLayoutAlgorithm { // Compute the stack rect, inset from the given task stack bounds mStackRect.set(taskStackBounds.left + widthPadding, taskStackBounds.top + heightPadding, taskStackBounds.right - widthPadding, taskStackBounds.bottom); - mTaskBottomOffset = mSystemInsets.bottom + heightPadding; + mStackBottomOffset = mSystemInsets.bottom + heightPadding; // Compute the task rect, align it to the top-center square in the stack rect - int size = Math.min(mStackRect.width(), mStackRect.height() - mTaskBottomOffset); + int size = Math.min(mStackRect.width(), mStackRect.height() - mStackBottomOffset); int xOffset = (mStackRect.width() - size) / 2; mTaskRect.set(mStackRect.left + xOffset, mStackRect.top, mStackRect.right - xOffset, mStackRect.top + size); - mFreeformRect.set(mTaskRect); + + // Compute the freeform rect, align it to the top-left of the stack rect + mFreeformRect.set(mStackRect); + mFreeformRect.bottom = taskStackBounds.bottom - mStackBottomOffset; // Compute the progress offsets int withinAffiliationOffset = mContext.getResources().getDimensionPixelSize( @@ -204,12 +223,17 @@ public class TaskStackViewLayoutAlgorithm { mStackRect); mTaskHalfHeightPOffset = sCurve.computePOffsetForScaledHeight(mTaskRect.height() / 2, mStackRect); - mTaskBottomPOffset = sCurve.computePOffsetForHeight(mTaskBottomOffset, mStackRect); - mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformRect.height(), + mStackBottomPOffset = sCurve.computePOffsetForHeight(mStackBottomOffset, mStackRect); + mFreeformWorkspaceGapOffset = mStackBottomOffset; + mFreeformWorkspaceGapPOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceGapOffset, + mStackRect); + mFreeformWorkspaceOffset = mFreeformWorkspaceGapOffset + mFreeformRect.height() + + mStackBottomOffset; + mFreeformWorkspacePOffset = sCurve.computePOffsetForHeight(mFreeformWorkspaceOffset, mStackRect); if (DEBUG) { - Log.d(TAG, "computeRects"); + Log.d(TAG, "initialize"); Log.d(TAG, "\tarclength: " + sCurve.getArcLength()); Log.d(TAG, "\tmStackRect: " + mStackRect); Log.d(TAG, "\tmTaskRect: " + mTaskRect); @@ -219,12 +243,14 @@ public class TaskStackViewLayoutAlgorithm { Log.d(TAG, "\tpBetweenAffiliateOffset: " + mBetweenAffiliationPOffset); Log.d(TAG, "\tmTaskHeightPOffset: " + mTaskHeightPOffset); Log.d(TAG, "\tmTaskHalfHeightPOffset: " + mTaskHalfHeightPOffset); - Log.d(TAG, "\tmTaskBottomPOffset: " + mTaskBottomPOffset); + Log.d(TAG, "\tmStackBottomPOffset: " + mStackBottomPOffset); + Log.d(TAG, "\tmFreeformWorkspacePOffset: " + mFreeformWorkspacePOffset); + Log.d(TAG, "\tmFreeformWorkspaceGapPOffset: " + mFreeformWorkspaceGapPOffset); Log.d(TAG, "\ty at p=0: " + sCurve.pToX(0f, mStackRect)); Log.d(TAG, "\ty at p=1: " + sCurve.pToX(1f, mStackRect)); - for (int height = 0; height <= 1000; height += 50) { + for (int height = 0; height <= 2000; height += 50) { float p = sCurve.computePOffsetForScaledHeight(height, mStackRect); float p2 = sCurve.computePOffsetForHeight(height, mStackRect); Log.d(TAG, "offset: " + height + ", " + @@ -236,20 +262,22 @@ public class TaskStackViewLayoutAlgorithm { } /** - * Computes the minimum and maximum scroll progress values. This method may be called before - * the RecentsConfiguration is set, so we need to pass in the alt-tab state. + * Computes the minimum and maximum scroll progress values and the progress values for each task + * in the stack. */ - void computeMinMaxScroll(ArrayList tasks) { + void update(TaskStack stack) { if (DEBUG) { - Log.d(TAG, "computeMinMaxScroll"); + Log.d(TAG, "update"); } // Clear the progress map mTaskProgressMap.clear(); // Return early if we have no tasks + ArrayList tasks = stack.getTasks(); if (tasks.isEmpty()) { - mMinScrollP = mMaxScrollP = mMaxStackScrollP = 0; + mFrontMostTaskP = 0; + mMinScrollP = mMaxScrollP = mStackEndScrollP = mPreferredStackEndScrollP = 0; mNumStackTasks = mNumFreeformTasks = 0; return; } @@ -268,9 +296,6 @@ public class TaskStackViewLayoutAlgorithm { mNumStackTasks = stackTasks.size(); mNumFreeformTasks = freeformTasks.size(); - // TODO: In the case where there is only freeform tasks, then the scrolls should be set to - // zero - if (!stackTasks.isEmpty()) { // Update the for each task from back to front. float pAtBackMostTaskTop = 0; @@ -289,52 +314,47 @@ public class TaskStackViewLayoutAlgorithm { } mFrontMostTaskP = pAtFrontMostTaskTop; - // Set the max scroll progress to the point at which the top of the front-most task - // is aligned to the bottom of the stack (offset by nav bar, padding, and task height) - mMaxStackScrollP = getBottomAlignedScrollProgress(pAtFrontMostTaskTop, - mTaskBottomPOffset + mTaskHeightPOffset); + // Set the stack end scroll progress to the point at which the bottom of the front-most + // task is aligned to the bottom of the stack + mStackEndScrollP = alignToStackBottom(pAtFrontMostTaskTop, + mTaskHeightPOffset); + // Set the preferred stack end scroll progress to the point where the bottom of the + // front-most task is offset by the navbar and padding from the bottom of the stack + mPreferredStackEndScrollP = mStackEndScrollP + mStackBottomPOffset; // Basically align the back-most task such that its progress is the same as the top of // the front most task at the max stack scroll - mMinScrollP = getBottomAlignedScrollProgress(pAtBackMostTaskTop, - mTaskBottomPOffset + mTaskHeightPOffset); + mMinScrollP = alignToStackBottom(pAtBackMostTaskTop, + mStackBottomPOffset + mTaskHeightPOffset); + } else { + // TODO: In the case where there is only freeform tasks, then the scrolls should be + // set to zero } if (!freeformTasks.isEmpty()) { - // Calculate the cell width/height depending on the number of freeform tasks - mFreeformCellXCount = Math.max(2, (int) Math.ceil(Math.sqrt(mNumFreeformTasks))); - mFreeformCellYCount = Math.max(2, (int) Math.ceil((float) mNumFreeformTasks / mFreeformCellXCount)); - mFreeformCellWidth = mFreeformRect.width() / mFreeformCellXCount; - mFreeformCellHeight = mFreeformRect.height() / mFreeformCellYCount; - - // Put each of the tasks in the progress map at a fixed index (does not need to actually - // map to a scroll position, just by index) - int taskCount = freeformTasks.size(); - for (int i = 0; i < taskCount; i++) { - Task task = freeformTasks.get(i); - mTaskProgressMap.put(task.key, (mFrontMostTaskP + 1) + i); - } - // The max scroll includes the freeform workspace offset. As the scroll progress exceeds - // mMaxStackScrollP up to mMaxScrollP, the stack will translate upwards and the freeform + // mStackEndScrollP up to mMaxScrollP, the stack will translate upwards and the freeform // workspace will be visible - mMaxScrollP = mMaxStackScrollP + mFreeformWorkspacePOffset; - mInitialScrollP = mMaxScrollP; + mFreeformLayoutAlgorithm.update(freeformTasks, this); + mMaxScrollP = mStackEndScrollP + mFreeformWorkspacePOffset; + mInitialScrollP = isInitialStateFreeform(stack) ? + mMaxScrollP : mPreferredStackEndScrollP; } else { - mMaxScrollP = mMaxStackScrollP; - mInitialScrollP = Math.max(mMinScrollP, mMaxStackScrollP - mTaskHalfHeightPOffset); + mMaxScrollP = mPreferredStackEndScrollP; + mInitialScrollP = Math.max(mMinScrollP, mMaxScrollP - mTaskHalfHeightPOffset); } + if (DEBUG) { Log.d(TAG, "mNumStackTasks: " + mNumStackTasks); Log.d(TAG, "mNumFreeformTasks: " + mNumFreeformTasks); Log.d(TAG, "mMinScrollP: " + mMinScrollP); - Log.d(TAG, "mMaxStackScrollP: " + mMaxStackScrollP); + Log.d(TAG, "mStackEndScrollP: " + mStackEndScrollP); Log.d(TAG, "mMaxScrollP: " + mMaxScrollP); } } /** * Computes the maximum number of visible tasks and thumbnails. Requires that - * computeMinMaxScroll() is called first. + * update() is called first. */ public VisibilityReport computeStackVisibilityReport(ArrayList tasks) { // Ensure minimum visibility count @@ -380,7 +400,7 @@ public class TaskStackViewLayoutAlgorithm { prevScreenY = screenY; } else { // Once we hit the next front most task that does not have a visible thumbnail, - // walk through remaining visible set + // w alk through remaining visible set for (int j = i; j >= 0; j--) { numVisibleTasks++; progress = mTaskProgressMap.get(tasks.get(j).key) - mInitialScrollP; @@ -404,50 +424,42 @@ public class TaskStackViewLayoutAlgorithm { */ 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(); + if (mFreeformLayoutAlgorithm.isTransformAvailable(task, stackScroll, this)) { + mFreeformLayoutAlgorithm.getTransform(task, stackScroll, transformOut, this); + if (transformOut.visible) { + getFreeformWorkspaceBounds(stackScroll, mTmpTransform); + transformOut.translationY += mTmpTransform.translationY; + transformOut.translationZ = mMaxTranslationZ; + transformOut.rect.set(mTaskRect); + transformOut.rect.offset(0, transformOut.translationY); + Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); + transformOut.p = 0; + } return transformOut; + } else { + // 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) { - float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset; - int overscrollYOffset = 0; - if (mNumFreeformTasks > 0) { - overscrollYOffset = (int) (Math.max(0, stackOverscroll) * mFreeformRect.height()); - } - if ((mNumFreeformTasks > 0) && (stackScroll > mMaxStackScrollP) && - (taskProgress > mFrontMostTaskP)) { - // This is a freeform task, so lay it out in the freeform workspace - int taskIndex = Math.round(taskProgress - (mFrontMostTaskP + 1)); - int x = taskIndex % mFreeformCellXCount; - int y = taskIndex / mFreeformCellXCount; - int frontTaskBottom = mStackRect.height() - mTaskBottomOffset; - float scale = (float) mFreeformCellWidth / mTaskRect.width(); - int scaleXOffset = (int) (((1f - scale) * mTaskRect.width()) / 2); - int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2); - transformOut.scale = scale; - transformOut.translationX = x * mFreeformCellWidth - scaleXOffset; - transformOut.translationY = frontTaskBottom - overscrollYOffset + - (y * mFreeformCellHeight) - scaleYOffset; - transformOut.visible = true; - return transformOut; - - } else if (mNumStackTasks == 1) { + if (mNumStackTasks == 1) { // Center the task in the stack, changing the scale will not follow the curve, but just // modulate some values directly float pTaskRelative = mMinScrollP - stackScroll; float scale = SINGLE_TASK_SCALE; - int topOffset = (mStackRect.height() - mTaskBottomOffset - mTaskRect.height()) / 2; + int topOffset = (mStackRect.height() - mTaskRect.height()) / 2; transformOut.scale = scale; - transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())) - - overscrollYOffset; + transformOut.translationX = 0; + transformOut.translationY = (int) (topOffset + (pTaskRelative * mStackRect.height())); transformOut.translationZ = mMaxTranslationZ; transformOut.rect.set(mTaskRect); transformOut.rect.offset(0, transformOut.translationY); @@ -457,11 +469,20 @@ public class TaskStackViewLayoutAlgorithm { return transformOut; } else { - float pTaskRelative = taskProgress - stackScroll; - if (mNumFreeformTasks > 0) { - pTaskRelative = Math.min(mMaxStackScrollP, pTaskRelative); + // Once we scroll past the preferred stack end scroll, then we should start translating + // the cards in screen space and lock their final state at the end stack progress + int overscrollYOffset = 0; + if (mNumFreeformTasks > 0 && stackScroll > mStackEndScrollP) { + float stackOverscroll = (stackScroll - mPreferredStackEndScrollP) / + (mFreeformWorkspacePOffset - mFreeformWorkspaceGapPOffset); + overscrollYOffset = (int) (Math.max(0, stackOverscroll) * + (mFreeformWorkspaceOffset - mFreeformWorkspaceGapPOffset)); + stackScroll = Math.min(mPreferredStackEndScrollP, stackScroll); } + + 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 if (pTaskRelative > 1f) { transformOut.reset(); @@ -480,6 +501,7 @@ public class TaskStackViewLayoutAlgorithm { float scale = sCurve.pToScale(pBounded); int scaleYOffset = (int) (((1f - scale) * mTaskRect.height()) / 2); transformOut.scale = scale; + transformOut.translationX = 0; transformOut.translationY = sCurve.pToX(pBounded, mStackRect) - mStackRect.top - scaleYOffset - overscrollYOffset; transformOut.translationZ = Math.max(mMinTranslationZ, @@ -489,10 +511,30 @@ public class TaskStackViewLayoutAlgorithm { Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); transformOut.visible = true; transformOut.p = pTaskRelative; + if (DEBUG) { + Log.d(TAG, "getStackTransform (normal): " + taskProgress + ", " + stackScroll); + Log.d(TAG, "\t" + transformOut); + } + return transformOut; } } + /** + * Returns whether this stack should be initialized to show the freeform workspace or not. + */ + public boolean isInitialStateFreeform(TaskStack stack) { + Task launchTarget = stack.getLaunchTarget(); + if (launchTarget != null) { + return launchTarget.isFreeformTask(); + } + Task frontTask = stack.getFrontMostTask(); + if (frontTask != null) { + return frontTask.isFreeformTask(); + } + return false; + } + /** * Update/get the transform */ @@ -503,17 +545,36 @@ public class TaskStackViewLayoutAlgorithm { return transformOut; } - if (stackScroll > mMaxStackScrollP) { - float stackOverscroll = (stackScroll - mMaxStackScrollP) / mFreeformWorkspacePOffset; - int overscrollYOffset = (int) (stackOverscroll * mFreeformRect.height()); - int frontTaskBottom = mStackRect.height() - mTaskBottomOffset; + if (stackScroll > mStackEndScrollP) { + // mStackEndScroll is the point at which the first stack task is bottom aligned with the + // stack, so we offset from on the stack rect height. + float stackOverscroll = (Math.max(0, stackScroll - mStackEndScrollP)) / + mFreeformWorkspacePOffset; + int overscrollYOffset = (int) (stackOverscroll * mFreeformWorkspaceOffset); + transformOut.scale = 1f; + transformOut.alpha = 1f; + transformOut.translationY = mStackRect.height() + mFreeformWorkspaceGapOffset - + overscrollYOffset; + transformOut.rect.set(mFreeformRect); + transformOut.rect.offset(0, transformOut.translationY); + Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale); transformOut.visible = true; - transformOut.alpha = - transformOut.translationY = frontTaskBottom - overscrollYOffset; } return transformOut; } + /** + * Returns the preferred maximum scroll position for a stack at the given {@param scroll}. + */ + public float getPreferredMaxScrollPosition(float scroll) { + float maxStackScrollBounds = mStackEndScrollP + SNAP_TO_MAX_STACK_SCROLL_FACTOR * + (mMaxScrollP - mStackEndScrollP); + if (scroll < maxStackScrollBounds) { + return mPreferredStackEndScrollP; + } + return mMaxScrollP; + } + /** * Returns the untransformed task view bounds. */ @@ -549,7 +610,12 @@ public class TaskStackViewLayoutAlgorithm { return -y; } - private float getBottomAlignedScrollProgress(float p, float pOffsetFromBottom) { + private float alignToStackTop(float p) { + // At scroll progress == p, then p is at the top of the stack + return p; + } + + private float alignToStackBottom(float p, float pOffsetFromBottom) { // At scroll progress == p, then p is at the top of the stack // At scroll progress == p + 1, then p is at the bottom of the stack return p - (1 - pOffsetFromBottom); 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 dfa36d81affbd..a7bfc40ca7a84 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -22,12 +22,12 @@ import android.content.Context; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; @@ -79,11 +79,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } TaskStack mStack; - TaskStackViewLayoutAlgorithm mLayoutAlgorithm; + TaskStackLayoutAlgorithm mLayoutAlgorithm; TaskStackViewFilterAlgorithm mFilterAlgorithm; TaskStackViewScroller mStackScroller; TaskStackViewTouchHandler mTouchHandler; TaskStackViewCallbacks mCb; + ColorDrawable mFreeformWorkspaceBackground; ViewPool mViewPool; ArrayList mCurrentTaskTransforms = new ArrayList<>(); DozeTrigger mUIDozeTrigger; @@ -101,6 +102,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Rect mTmpRect = new Rect(); RectF mTmpTaskRect = new RectF(); TaskViewTransform mTmpTransform = new TaskViewTransform(); + TaskViewTransform mTmpStackBackTransform = new TaskViewTransform(); + TaskViewTransform mTmpStackFrontTransform = new TaskViewTransform(); HashMap mTmpTaskViewMap = new HashMap<>(); ArrayList mTaskViews = new ArrayList<>(); List mImmutableTaskViews = new ArrayList<>(); @@ -122,7 +125,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal setStack(stack); mViewPool = new ViewPool<>(context, this); mInflater = LayoutInflater.from(context); - mLayoutAlgorithm = new TaskStackViewLayoutAlgorithm(context); + mLayoutAlgorithm = new TaskStackLayoutAlgorithm(context); mFilterAlgorithm = new TaskStackViewFilterAlgorithm(this, mViewPool); mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm); mStackScroller.setCallbacks(this); @@ -143,6 +146,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } }); setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + + mFreeformWorkspaceBackground = new ColorDrawable(0x33000000); } /** Sets the callbacks */ @@ -270,7 +275,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** Returns the stack algorithm for this task stack. */ - public TaskStackViewLayoutAlgorithm getStackAlgorithm() { + public TaskStackLayoutAlgorithm getStackAlgorithm() { return mLayoutAlgorithm; } @@ -342,6 +347,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int[] visibleRange = mTmpVisibleRange; boolean isValidVisibleRange = updateStackTransforms(mCurrentTaskTransforms, tasks, stackScroll, visibleRange, false); + boolean hasStackBackTransform = false; + boolean hasStackFrontTransform = false; + if (DEBUG) { + Log.d(TAG, "visibleRange: " + visibleRange[0] + " to " + visibleRange[1]); + } // Return all the invisible children to the pool mTmpTaskViewMap.clear(); @@ -381,11 +391,20 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // For items in the list, put them in start animating them from the // approriate ends of the list where they are expected to appear if (Float.compare(transform.p, 0f) <= 0) { - mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpTransform, null); + if (!hasStackBackTransform) { + hasStackBackTransform = true; + mLayoutAlgorithm.getStackTransform(0f, 0f, mTmpStackBackTransform, + null); + } + tv.updateViewPropertiesToTaskTransform(mTmpStackBackTransform, 0); } else { - mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpTransform, null); + if (!hasStackFrontTransform) { + hasStackFrontTransform = true; + mLayoutAlgorithm.getStackTransform(1f, 0f, mTmpStackFrontTransform, + null); + } + tv.updateViewPropertiesToTaskTransform(mTmpStackFrontTransform, 0); } - tv.updateViewPropertiesToTaskTransform(mTmpTransform, 0); } } @@ -403,6 +422,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + // Update the freeform workspace + mLayoutAlgorithm.getFreeformWorkspaceBounds(stackScroll, mTmpTransform); + if (mTmpTransform.visible) { + mTmpTransform.rect.roundOut(mTmpRect); + mFreeformWorkspaceBackground.setAlpha(255); + mFreeformWorkspaceBackground.setBounds(mTmpRect); + } else { + mFreeformWorkspaceBackground.setAlpha(0); + } + // Reset the request-synchronize params mStackViewsAnimationDuration = 0; mStackViewsDirty = false; @@ -460,7 +489,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** Updates the min and max virtual scroll bounds */ void updateMinMaxScroll(boolean boundScrollToNewMinMax) { // Compute the min and max scroll values - mLayoutAlgorithm.computeMinMaxScroll(mStack.getTasks()); + mLayoutAlgorithm.update(mStack); // Debug logging if (boundScrollToNewMinMax) { @@ -512,7 +541,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal }; if (scrollToTask) { - // TODO: Center the newly focused task view + // TODO: Center the newly focused task view, only if not freeform float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask) - 0.5f; newScroll = mStackScroller.getBoundedStackScroll(newScroll); mStackScroller.animateScroll(mStackScroller.getStackScroll(), newScroll, @@ -633,7 +662,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** Computes the stack and task rects */ public void computeRects(Rect taskStackBounds) { // Compute the rects in the stack algorithm - mLayoutAlgorithm.computeRects(taskStackBounds); + mLayoutAlgorithm.initialize(taskStackBounds); // Update the scroll bounds updateMinMaxScroll(false); @@ -652,7 +681,7 @@ 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() { + public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks()); } @@ -710,7 +739,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - // Layout each of the children + // Layout each of the TaskViews List taskViews = getTaskViews(); int taskViewCount = taskViews.size(); for (int i = 0; i < taskViewCount; i++) { @@ -720,10 +749,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } else { mTmpRect.setEmpty(); } - tv.layout(mLayoutAlgorithm.mTaskRect.left - mTmpRect.left, - mLayoutAlgorithm.mTaskRect.top - mTmpRect.top, - mLayoutAlgorithm.mTaskRect.right + mTmpRect.right, - mLayoutAlgorithm.mTaskRect.bottom + mTmpRect.bottom); + Rect taskRect = mLayoutAlgorithm.mTaskRect; + tv.layout(taskRect.left - mTmpRect.left, taskRect.top - mTmpRect.top, + taskRect.right + mTmpRect.right, taskRect.bottom + mTmpRect.bottom); } if (mAwaitingFirstLayout) { @@ -737,17 +765,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int offscreenY = mLayoutAlgorithm.mStackRect.bottom; // Find the launch target task - Task launchTargetTask = null; + Task launchTargetTask = mStack.getLaunchTarget(); List taskViews = getTaskViews(); int taskViewCount = taskViews.size(); - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - Task task = tv.getTask(); - if (task.isLaunchTarget) { - launchTargetTask = task; - break; - } - } // Prepare the first view for its enter animation for (int i = taskViewCount - 1; i >= 0; i--) { @@ -755,7 +775,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal Task task = tv.getTask(); boolean occludesLaunchTarget = (launchTargetTask != null) && launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); - tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, offscreenY); + tv.prepareEnterRecentsAnimation(task.isLaunchTarget, occludesLaunchTarget, + offscreenY); } // If the enter animation started already and we haven't completed a layout yet, do the @@ -794,17 +815,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (mStack.getTaskCount() > 0) { // Find the launch target task - Task launchTargetTask = null; + Task launchTargetTask = mStack.getLaunchTarget(); List taskViews = getTaskViews(); int taskViewCount = taskViews.size(); - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - Task task = tv.getTask(); - if (task.isLaunchTarget) { - launchTargetTask = task; - break; - } - } // Animate all the task views into view for (int i = taskViewCount - 1; i >= 0; i--) { @@ -817,7 +830,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ctx.currentTaskOccludesLaunchTarget = (launchTargetTask != null) && launchTargetTask.group.isTaskAboveTask(task, launchTargetTask); ctx.updateListener = mRequestUpdateClippingListener; - mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), ctx.currentTaskTransform, null); + mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), + ctx.currentTaskTransform, null); tv.startEnterRecentsAnimation(ctx); } @@ -898,6 +912,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override protected void dispatchDraw(Canvas canvas) { mLayersDisabled = false; + + // Draw the freeform workspace background + if (mFreeformWorkspaceBackground.getAlpha() > 0) { + mFreeformWorkspaceBackground.draw(canvas); + } + super.dispatchDraw(canvas); } @@ -919,42 +939,44 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, Task newFrontMostTask) { - // Remove the view associated with this task, we can't rely on updateTransforms - // to work here because the task is no longer in the list - TaskView tv = getChildViewForTask(removedTask); - if (tv != null) { - mViewPool.returnViewToPool(tv); + if (!removedTask.isFreeformTask()) { + // Remove the view associated with this task, we can't rely on updateTransforms + // to work here because the task is no longer in the list + TaskView tv = getChildViewForTask(removedTask); + if (tv != null) { + mViewPool.returnViewToPool(tv); + } + + // Get the stack scroll of the task to anchor to (since we are removing something, the front + // most task will be our anchor task) + Task anchorTask = null; + float prevAnchorTaskScroll = 0; + boolean pullStackForward = stack.getTaskCount() > 0; + if (pullStackForward) { + anchorTask = mStack.getFrontMostTask(); + prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); + } + + // Update the min/max scroll and animate other task views into their new positions + updateMinMaxScroll(true); + + if (wasFrontMostTask) { + // Since the max scroll progress is offset from the bottom of the stack, just scroll + // to ensure that the new front most task is now fully visible + mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP); + } else if (pullStackForward) { + // Otherwise, offset the scroll by half the movement of the anchor task to allow the + // tasks behind the removed task to move forward, and the tasks in front to move back + float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); + mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll + - prevAnchorTaskScroll) / 2); + mStackScroller.boundScroll(); + } + + // Animate all the tasks into place + requestSynchronizeStackViewsWithModel(200); } - // Get the stack scroll of the task to anchor to (since we are removing something, the front - // most task will be our anchor task) - Task anchorTask = null; - float prevAnchorTaskScroll = 0; - boolean pullStackForward = stack.getTaskCount() > 0; - if (pullStackForward) { - anchorTask = mStack.getFrontMostTask(); - prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); - } - - // Update the min/max scroll and animate other task views into their new positions - updateMinMaxScroll(true); - - if (wasFrontMostTask) { - // Since the max scroll progress is offset from the bottom of the stack, just scroll - // to ensure that the new front most task is now fully visible - mStackScroller.setStackScroll(mLayoutAlgorithm.mMaxScrollP); - } else if (pullStackForward) { - // Otherwise, offset the scroll by half the movement of the anchor task to allow the - // tasks behind the removed task to move forward, and the tasks in front to move back - float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); - mStackScroller.setStackScroll(mStackScroller.getStackScroll() + (anchorTaskScroll - - prevAnchorTaskScroll) / 2); - mStackScroller.boundScroll(); - } - - // Animate all the tasks into place - requestSynchronizeStackViewsWithModel(200); - // Update the new front most task if (newFrontMostTask != null) { TaskView frontTv = getChildViewForTask(newFrontMostTask); @@ -1103,7 +1125,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int insertIndex = -1; int taskIndex = mStack.indexOfTask(task); if (taskIndex != -1) { - List taskViews = getTaskViews(); int taskViewCount = taskViews.size(); for (int i = 0; i < taskViewCount; i++) { @@ -1252,7 +1273,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // the next task RecentsConfiguration config = Recents.getConfiguration(); RecentsActivityLaunchState launchState = config.getLaunchState(); - setFocusedTask(taskIndex - 1, true /* scrollToTask */, launchState.launchedWithAltTab); + setFocusedTask(taskIndex - 1, + !mStack.getTasks().get(taskIndex - 1).isFreeformTask() /* scrollToTask */, + launchState.launchedWithAltTab); } } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java index 15fcab4c67cd6..6b92aeda15621 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -39,7 +39,7 @@ public class TaskStackViewScroller { } Context mContext; - TaskStackViewLayoutAlgorithm mLayoutAlgorithm; + TaskStackLayoutAlgorithm mLayoutAlgorithm; TaskStackViewScrollerCallbacks mCb; float mStackScrollP; @@ -52,7 +52,7 @@ public class TaskStackViewScroller { Interpolator mLinearOutSlowInInterpolator; - public TaskStackViewScroller(Context context, TaskStackViewLayoutAlgorithm layoutAlgorithm) { + public TaskStackViewScroller(Context context, TaskStackLayoutAlgorithm layoutAlgorithm) { mContext = context; mScroller = new OverScroller(context); mLayoutAlgorithm = layoutAlgorithm; @@ -121,7 +121,8 @@ public class TaskStackViewScroller { /** Returns the bounded stack scroll */ float getBoundedStackScroll(float scroll) { - return Math.max(mLayoutAlgorithm.mMinScrollP, Math.min(mLayoutAlgorithm.mMaxScrollP, scroll)); + return Math.max(mLayoutAlgorithm.mMinScrollP, + Math.min(mLayoutAlgorithm.getPreferredMaxScrollPosition(scroll), scroll)); } /** Returns the amount that the absolute value of how much the scroll is out of bounds. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java index 08889c53161cc..9e6fb7bae4b98 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -16,8 +16,10 @@ package com.android.systemui.recents.views; +import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; +import android.util.Log; import android.view.InputDevice; import android.view.MotionEvent; import android.view.VelocityTracker; @@ -30,6 +32,8 @@ import com.android.systemui.recents.Constants; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.HideRecentsEvent; import com.android.systemui.recents.events.ui.DismissTaskViewEvent; +import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.statusbar.FlingAnimationUtils; import java.util.List; @@ -37,7 +41,7 @@ import java.util.List; class TaskStackViewTouchHandler implements SwipeHelper.Callback { private static final String TAG = "TaskStackViewTouchHandler"; - private static final boolean DEBUG = true; + private static final boolean DEBUG = false; private static int INACTIVE_POINTER_ID = -1; @@ -45,6 +49,8 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { TaskStackView mSv; TaskStackViewScroller mScroller; VelocityTracker mVelocityTracker; + FlingAnimationUtils mFlingAnimUtils; + ValueAnimator mScrollFlingAnimator; boolean mIsScrolling; float mDownScrollP; @@ -74,6 +80,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mWindowTouchSlop = configuration.getScaledWindowTouchSlop(); mSv = sv; mScroller = scroller; + mFlingAnimUtils = new FlingAnimationUtils(context, 0.2f); float densityScale = res.getDisplayMetrics().density; mOverscrollSize = res.getDimensionPixelSize(R.dimen.recents_stack_overscroll); @@ -140,7 +147,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return false; } - TaskStackViewLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm; + final TaskStackLayoutAlgorithm layoutAlgorithm = mSv.mLayoutAlgorithm; int action = ev.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { @@ -154,6 +161,7 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { // Stop the current scroll if it is still flinging mScroller.stopScroller(); mScroller.stopBoundScrollAnimation(); + Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator); // Initialize the velocity tracker initOrResetVelocityTracker(); @@ -189,6 +197,9 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y); float curScrollP = mDownScrollP + deltaP; mScroller.setStackScroll(curScrollP); + if (DEBUG) { + Log.d(TAG, "scroll: " + curScrollP); + } } mVelocityTracker.addMovement(ev); @@ -211,21 +222,53 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { int activePointerIndex = ev.findPointerIndex(mActivePointerId); int y = (int) ev.getY(activePointerIndex); int velocity = (int) mVelocityTracker.getYVelocity(mActivePointerId); + float curScrollP = mScroller.getStackScroll(); if (mIsScrolling) { - if (mScroller.isScrollOutOfBounds()) { - // Animate the scroll back into bounds + boolean hasFreeformTasks = mSv.mStack.hasFreeformTasks(); + if (hasFreeformTasks && velocity > 0 && + curScrollP > layoutAlgorithm.mStackEndScrollP) { + // Snap to workspace + float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + layoutAlgorithm.mPreferredStackEndScrollP); + mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY); + mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, + (Integer) animation.getAnimatedValue()); + float scroll = mDownScrollP + deltaP; + mScroller.setStackScroll(scroll); + } + }); + mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity); + mScrollFlingAnimator.start(); + } else if (hasFreeformTasks && velocity < 0 && + curScrollP > (layoutAlgorithm.mStackEndScrollP - + layoutAlgorithm.mTaskHalfHeightPOffset)) { + // Snap to stack + float finalY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + layoutAlgorithm.mMaxScrollP); + mScrollFlingAnimator = ValueAnimator.ofInt(y, (int) finalY); + mScrollFlingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, + (Integer) animation.getAnimatedValue()); + float scroll = mDownScrollP + deltaP; + mScroller.setStackScroll(scroll); + } + }); + mFlingAnimUtils.apply(mScrollFlingAnimator, y, finalY, velocity); + mScrollFlingAnimator.start(); + } else if (mScroller.isScrollOutOfBounds()) { mScroller.animateBoundScroll(); } else if (Math.abs(velocity) > mMinimumVelocity) { - float deltaP = layoutAlgorithm.getDeltaPForY(mDownY, y); - float curScrollP = mDownScrollP + deltaP; - float downToCurY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, - curScrollP); - float downToMinY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, - layoutAlgorithm.mMaxScrollP); - float downToMaxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + float minY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, + layoutAlgorithm.mPreferredStackEndScrollP); + float maxY = mDownY + layoutAlgorithm.getYForDeltaP(mDownScrollP, layoutAlgorithm.mMinScrollP); - mScroller.fling(mDownScrollP, mDownY, (int) downToCurY, velocity, - (int) downToMinY, (int) downToMaxY, mOverscrollSize); + mScroller.fling(mDownScrollP, mDownY, y, velocity, (int) minY, (int) maxY, + mOverscrollSize); mSv.invalidate(); } } else if (mActiveTaskView == null) { @@ -239,10 +282,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { break; } case MotionEvent.ACTION_CANCEL: { - if (mScroller.isScrollOutOfBounds()) { - // Animate the scroll back into bounds - mScroller.animateBoundScroll(); - } mActivePointerId = INACTIVE_POINTER_ID; mIsScrolling = false; recycleVelocityTracker(); @@ -308,9 +347,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { @Override public boolean canChildBeDismissed(View v) { - if (v instanceof TaskView) { - return !((TaskView) v).getTask().isFreeformTask(); - } return true; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index aaacc6cf99b55..57cb599162c58 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -552,7 +552,11 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, * view. */ boolean shouldClipViewInStack() { - return mClipViewInStack && (getVisibility() == View.VISIBLE); + // Never clip for freeform tasks or if invisible + if (mTask.isFreeformTask() || getVisibility() != View.VISIBLE) { + return false; + } + return mClipViewInStack; } /** Sets whether this view should be clipped, or clipped against. */ diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index f6353f83da522..649199ec11c18 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -255,6 +255,10 @@ public class TaskViewHeader extends FrameLayout mApplicationIcon.setImageDrawable(null); mApplicationIcon.setOnClickListener(null); mMoveTaskButton.setOnClickListener(null); + + // Stop any focus animations + Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator); + mBackground.jumpToCurrentState(); } /** Updates the resize task bar button. */ @@ -370,8 +374,9 @@ public class TaskViewHeader extends FrameLayout boolean isRunning = false; if (mFocusAnimator != null) { isRunning = mFocusAnimator.isRunning(); - Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator); } + Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator); + mBackground.jumpToCurrentState(); if (focused) { // If we are not animating the visible state, just return