diff options
12 files changed, 606 insertions, 210 deletions
diff --git a/packages/SystemUI/res/values-sw600dp/config.xml b/packages/SystemUI/res/values-sw600dp/config.xml index 6795da43e087..17ff19549a0d 100644 --- a/packages/SystemUI/res/values-sw600dp/config.xml +++ b/packages/SystemUI/res/values-sw600dp/config.xml @@ -35,7 +35,7 @@ <!-- Recents: The relative range of visible tasks from the current scroll position while the stack is focused. --> - <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item> + <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item> <item name="recents_layout_focused_range_max" format="float" type="integer">3</item> <!-- Recents: The relative range of visible tasks from the current scroll position diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index e98ec825f4a0..810ca14fc15c 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -194,7 +194,7 @@ <!-- Recents: The relative range of visible tasks from the current scroll position while the stack is focused. --> - <item name="recents_layout_focused_range_min" format="float" type="integer">-4</item> + <item name="recents_layout_focused_range_min" format="float" type="integer">-3</item> <item name="recents_layout_focused_range_max" format="float" type="integer">3</item> <!-- Recents: The relative range of visible tasks from the current scroll position diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java index d28da41e153f..fd4161fe3e48 100644 --- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java +++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java @@ -367,6 +367,7 @@ public class SwipeHelper implements Gefingerpoken { } anim.addListener(new AnimatorListenerAdapter() { public void onAnimationEnd(Animator animation) { + updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); mCallback.onChildDismissed(view); if (endAction != null) { endAction.run(); @@ -381,9 +382,17 @@ public class SwipeHelper implements Gefingerpoken { updateSwipeProgressFromOffset(animView, canAnimViewBeDismissed); } }); + prepareDismissAnimation(animView, anim); anim.start(); } + /** + * Called to update the dismiss animation. + */ + protected void prepareDismissAnimation(View view, Animator anim) { + // Do nothing + } + public void snapChild(final View view, float velocity) { final View animView = mCallback.getChildContentView(view); final boolean canAnimViewBeDismissed = mCallback.canChildBeDismissed(animView); @@ -401,14 +410,14 @@ public class SwipeHelper implements Gefingerpoken { mCallback.onChildSnappedBack(animView); } }); - updateSnapBackAnimation(anim); + prepareSnapBackAnimation(animView, anim); anim.start(); } /** * Called to update the snap back animation. */ - protected void updateSnapBackAnimation(Animator anim) { + protected void prepareSnapBackAnimation(View view, Animator anim) { // Do nothing } diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java index 80597bc0bc14..fe23fa07c1e6 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java @@ -36,6 +36,7 @@ import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; +import com.android.systemui.recents.views.TaskViewAnimation; import java.util.ArrayList; import java.util.Calendar; @@ -268,8 +269,8 @@ public class RecentsHistoryAdapter extends RecyclerView.Adapter<RecentsHistoryAd public void onTaskRemoved(Task task, int position) { // Since this is removed from the history, we need to update the stack as well to ensure - // that the model is correct - mStack.removeTask(task); + // that the model is correct. Since the stack is hidden, we can update it immediately. + mStack.removeTask(task, TaskViewAnimation.IMMEDIATE); removeTaskRow(position); if (mRows.isEmpty()) { dismissHistory(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java new file mode 100644 index 000000000000..72511de9ec80 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 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.misc; + +import android.animation.TypeEvaluator; +import android.graphics.RectF; + +/** + * This evaluator can be used to perform type interpolation between <code>RectF</code> values. + */ +public class RectFEvaluator implements TypeEvaluator<RectF> { + + private RectF mRect = new RectF(); + + /** + * This function returns the result of linearly interpolating the start and + * end Rect values, with <code>fraction</code> representing the proportion + * between the start and end values. The calculation is a simple parametric + * calculation on each of the separate components in the Rect objects + * (left, top, right, and bottom). + * + * <p>The object returned will be the <code>reuseRect</code> passed into the constructor.</p> + * + * @param fraction The fraction from the starting to the ending values + * @param startValue The start Rect + * @param endValue The end Rect + * @return A linear interpolation between the start and end values, given the + * <code>fraction</code> parameter. + */ + @Override + public RectF evaluate(float fraction, RectF startValue, RectF endValue) { + float left = startValue.left + ((endValue.left - startValue.left) * fraction); + float top = startValue.top + ((endValue.top - startValue.top) * fraction); + float right = startValue.right + ((endValue.right - startValue.right) * fraction); + float bottom = startValue.bottom + ((endValue.bottom - startValue.bottom) * fraction); + mRect.set(left, top, right, bottom); + return mRect; + } +} 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 327cdf87994d..ad723e23e671 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -42,6 +42,7 @@ import com.android.systemui.recents.misc.NamedCounter; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; import com.android.systemui.recents.views.DropTarget; +import com.android.systemui.recents.views.TaskViewAnimation; import java.util.ArrayList; import java.util.Collections; @@ -226,12 +227,12 @@ public class TaskStack { * Notifies when a task has been removed from the stack. */ void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, - Task newFrontMostTask); + Task newFrontMostTask, TaskViewAnimation animation); /** * Notifies when a task has been removed from the history. */ - void onHistoryTaskRemoved(TaskStack stack, Task removedTask); + void onHistoryTaskRemoved(TaskStack stack, Task removedTask, TaskViewAnimation animation); } /** @@ -520,21 +521,24 @@ public class TaskStack { } } - /** Removes a task */ - public void removeTask(Task t) { + /** + * Removes a task from the stack, with an additional {@param animation} hint to the callbacks on + * how they should update themselves. + */ + public void removeTask(Task t, TaskViewAnimation animation) { if (mStackTaskList.contains(t)) { boolean wasFrontMostTask = (getStackFrontMostTask() == t); removeTaskImpl(mStackTaskList, t); Task newFrontMostTask = getStackFrontMostTask(); if (mCb != null) { // Notify that a task has been removed - mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask); + mCb.onStackTaskRemoved(this, t, wasFrontMostTask, newFrontMostTask, animation); } } else if (mHistoryTaskList.contains(t)) { removeTaskImpl(mHistoryTaskList, t); if (mCb != null) { // Notify that a task has been removed - mCb.onHistoryTaskRemoved(this, t); + mCb.onHistoryTaskRemoved(this, t, animation); } } mRawTaskList.remove(t); @@ -564,7 +568,8 @@ public class TaskStack { Task task = mRawTaskList.get(i); if (!newTasksMap.containsKey(task.key)) { if (notifyStackChanges) { - mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null); + mCb.onStackTaskRemoved(this, task, i == (taskCount - 1), null, + TaskViewAnimation.IMMEDIATE); } } task.setGroup(null); 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 e727652f5582..e448101577de 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -513,7 +513,7 @@ public class RecentsView extends FrameLayout { taskViewRect.right, taskViewRect.bottom); // Remove the task view after it is docked - mTaskStackView.updateLayout(false /* boundScroll */); + mTaskStackView.updateLayoutAlgorithm(false /* boundScroll */); stackLayout.getStackTransform(event.task, stackScroller.getStackScroll(), tmpTransform, null); tmpTransform.alpha = 0; @@ -529,11 +529,14 @@ public class RecentsView extends FrameLayout { ssp.startTaskInDockedMode(getContext(), event.taskView, event.task.key.id, dockState.createMode); - mTaskStackView.getStack().removeTask(event.task); + // Animate the stack accordingly + TaskViewAnimation stackAnim = new TaskViewAnimation( + TaskStackView.DEFAULT_SYNC_STACK_DURATION, + mFastOutSlowInInterpolator); + mTaskStackView.getStack().removeTask(event.task, stackAnim); } })); - MetricsLogger.action(mContext, MetricsLogger.ACTION_WINDOW_DOCK_DRAG_DROP); } else { diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java index 68ff63c1507c..5bd5a8159803 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java @@ -373,7 +373,7 @@ public class TaskStackLayoutAlgorithm { * Computes the minimum and maximum scroll progress values and the progress values for each task * in the stack. */ - void update(TaskStack stack, ArraySet<Task> ignoreTasksSet) { + void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet) { SystemServicesProxy ssp = Recents.getSystemServices(); // Clear the progress map @@ -393,7 +393,7 @@ public class TaskStackLayoutAlgorithm { ArrayList<Task> stackTasks = new ArrayList<>(); for (int i = 0; i < tasks.size(); i++) { Task task = tasks.get(i); - if (ignoreTasksSet.contains(task)) { + if (ignoreTasksSet.contains(task.key)) { continue; } if (task.isFreeformTask()) { @@ -553,7 +553,8 @@ public class TaskStackLayoutAlgorithm { boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task); if (isFrontMostTaskInGroup) { - getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null); + getStackTransform(taskProgress, mInitialScrollP, tmpTransform, null, + false /* ignoreSingleTaskCase */); float screenY = tmpTransform.rect.top; boolean hasVisibleThumbnail = (prevScreenY - screenY) > taskBarHeight; if (hasVisibleThumbnail) { @@ -596,13 +597,21 @@ public class TaskStackLayoutAlgorithm { return transformOut; } return getStackTransform(mTaskIndexMap.get(task.key), stackScroll, transformOut, - frontTransform); + frontTransform, false /* ignoreSingleTaskCase */); } } - /** Update/get the transform */ + /** + * Update/get the transform. + * + * @param ignoreSingleTaskCase When set, will ensure that the transform computed does not take + * into account the special single-task case. This is only used + * internally to ensure that we can calculate the transform for any + * position in the stack. + */ public TaskViewTransform getStackTransform(float taskProgress, float stackScroll, - TaskViewTransform transformOut, TaskViewTransform frontTransform) { + TaskViewTransform transformOut, TaskViewTransform frontTransform, + boolean ignoreSingleTaskCase) { SystemServicesProxy ssp = Recents.getSystemServices(); // Compute the focused and unfocused offset @@ -632,7 +641,7 @@ public class TaskStackLayoutAlgorithm { int y; float z; float relP; - if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) { + if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1 && !ignoreSingleTaskCase) { // When there is exactly one task, then decouple the task from the stack and just move // in screen space p = (mMinScrollP - stackScroll) / mNumStackTasks; @@ -762,8 +771,8 @@ public class TaskStackLayoutAlgorithm { mFocusState * (mFocusedRange.relativeMin - mUnfocusedRange.relativeMin); float max = mUnfocusedRange.relativeMax + mFocusState * (mFocusedRange.relativeMax - mUnfocusedRange.relativeMax); - getStackTransform(min, 0f, mBackOfStackTransform, null); - getStackTransform(max, 0f, mFrontOfStackTransform, null); + getStackTransform(min, 0f, mBackOfStackTransform, null, true /* ignoreSingleTaskCase */); + getStackTransform(max, 0f, mFrontOfStackTransform, null, true /* ignoreSingleTaskCase */); mBackOfStackTransform.visible = true; mFrontOfStackTransform.visible = true; } 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 7583a19308f2..3b78d71c3a5c 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -30,6 +30,7 @@ import android.os.Parcelable; import android.provider.Settings; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.MutableBoolean; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -77,7 +78,6 @@ import com.android.systemui.recents.model.Task; import com.android.systemui.recents.model.TaskStack; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID; @@ -100,10 +100,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal private static final float SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f; private static final float HIDE_HISTORY_BUTTON_SCROLL_THRESHOLD = 0.3f; - private static final int DEFAULT_SYNC_STACK_DURATION = 200; + public static final int DEFAULT_SYNC_STACK_DURATION = 200; private static final int DRAG_SCALE_DURATION = 175; private static final float DRAG_SCALE_FACTOR = 1.05f; + private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>(); + TaskStack mStack; TaskStackLayoutAlgorithm mLayoutAlgorithm; TaskStackViewScroller mStackScroller; @@ -116,7 +118,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal ArrayList<TaskView> mTaskViews = new ArrayList<>(); ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); - TaskViewAnimation mDeferredTaskViewUpdateAnimation = null; + ArraySet<Task.TaskKey> mIgnoreTasks = new ArraySet<>(); + TaskViewAnimation mDeferredTaskViewLayoutAnimation = null; DozeTrigger mUIDozeTrigger; Task mFocusedTask; @@ -137,7 +140,6 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int[] mTmpVisibleRange = new int[2]; Rect mTmpRect = new Rect(); ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>(); - ArraySet<Task> mTmpTaskSet = new ArraySet<>(); List<TaskView> mTmpTaskViews = new ArrayList<>(); TaskViewTransform mTmpTransform = new TaskViewTransform(); LayoutInflater mInflater; @@ -345,30 +347,82 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** - * Gets the stack transforms of a list of tasks, and returns the visible range of tasks. + * Adds a task to the ignored set. + */ + void addIgnoreTask(Task task) { + mIgnoreTasks.add(task.key); + } + + /** + * Removes a task from the ignored set. + */ + void removeIgnoreTask(Task task) { + mIgnoreTasks.remove(task.key); + } + + /** + * Returns whether the specified {@param task} is ignored. + */ + boolean isIgnoredTask(Task task) { + return mIgnoreTasks.contains(task.key); + } + + /** + * Computes the task transforms at the current stack scroll for all visible tasks. If a valid + * target stack scroll is provided (ie. is different than {@param curStackScroll}), then the + * visible range includes all tasks at the target stack scroll. This is useful for ensure that + * all views necessary for a transition or animation will be visible at the start. + * * This call ignores freeform tasks. + * + * @param taskTransforms The set of task view transforms to reuse, this list will be sized to + * match the size of {@param tasks} + * @param tasks The set of tasks for which to generate transforms + * @param curStackScroll The current stack scroll + * @param targetStackScroll The stack scroll that we anticipate we are going to be scrolling to. + * The range of the union of the visible views at the current and + * target stack scrolls will be returned. + * @param ignoreTasksSet The set of tasks to skip for purposes of calculaing the visible range. + * Transforms will still be calculated for the ignore tasks. */ - private boolean updateStackTransforms(ArrayList<TaskViewTransform> taskTransforms, - ArrayList<Task> tasks, float stackScroll, - int[] visibleRangeOut, ArraySet<Task> ignoreTasksSet) { - int taskTransformCount = taskTransforms.size(); + boolean computeVisibleTaskTransforms(ArrayList<TaskViewTransform> taskTransforms, + ArrayList<Task> tasks, float curStackScroll, float targetStackScroll, + int[] visibleRangeOut, ArraySet<Task.TaskKey> ignoreTasksSet) { int taskCount = tasks.size(); int frontMostVisibleIndex = -1; int backMostVisibleIndex = -1; + boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0; // We can reuse the task transforms where possible to reduce object allocation Utilities.matchTaskListSize(tasks, taskTransforms); // Update the stack transforms TaskViewTransform frontTransform = null; + TaskViewTransform frontTransformAtTarget = null; + TaskViewTransform transform = null; + TaskViewTransform transformAtTarget = null; for (int i = taskCount - 1; i >= 0; i--) { Task task = tasks.get(i); - if (ignoreTasksSet.contains(task)) { - continue; - } - TaskViewTransform transform = mLayoutAlgorithm.getStackTransform(task, stackScroll, + // Calculate the current and (if necessary) the target transform for the task + transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll, taskTransforms.get(i), frontTransform); + if (useTargetStackScroll && !transform.visible) { + // If we have a target stack scroll and the task is not currently visible, then we + // just update the transform at the new scroll + // TODO: Optimize this + transformAtTarget = mLayoutAlgorithm.getStackTransform(task, + targetStackScroll, new TaskViewTransform(), frontTransformAtTarget); + if (transformAtTarget.visible) { + transform.copyFrom(transformAtTarget); + } + } + + // For ignore tasks, only calculate the stack transform and skip the calculation of the + // visible stack indices + if (ignoreTasksSet.contains(task.key)) { + continue; + } // For freeform tasks, only calculate the stack transform and skip the calculation of // the visible stack indices @@ -392,7 +446,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal break; } } + frontTransform = transform; + frontTransformAtTarget = transformAtTarget; } if (visibleRangeOut != null) { visibleRangeOut[0] = frontMostVisibleIndex; @@ -402,33 +458,48 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** - * Updates the children {@link TaskView}s to match the tasks in the current {@link TaskStack}. - * This call does not update the {@link TaskView}s to their position in the layout except when - * they are initially picked up from the pool, when they will be placed in a suitable initial - * position. + * Binds the visible {@link TaskView}s at the given target scroll. + * + * @see #bindVisibleTaskViews(float, ArraySet<Task.TaskKey>) */ - private void bindTaskViewsWithStack(ArraySet<Task> ignoreTasksSet) { - final float stackScroll = mStackScroller.getStackScroll(); + void bindVisibleTaskViews(float targetStackScroll) { + bindVisibleTaskViews(targetStackScroll, mIgnoreTasks); + } + + /** + * Synchronizes the set of children {@link TaskView}s to match the visible set of tasks in the + * current {@link TaskStack}. This call does not continue on to update their position to the + * computed {@link TaskViewTransform}s of the visible range, but only ensures that they will + * be added/removed from the view hierarchy and placed in the correct Z order and initial + * position (if not currently on screen). + * + * @param targetStackScroll If provided, will ensure that the set of visible {@link TaskView}s + * includes those visible at the current stack scroll, and all at the + * target stack scroll. + * @param ignoreTasksSet The set of tasks to ignore in this rebinding of the visible + * {@link TaskView}s + */ + void bindVisibleTaskViews(float targetStackScroll, ArraySet<Task.TaskKey> ignoreTasksSet) { final int[] visibleStackRange = mTmpVisibleRange; // Get all the task transforms final ArrayList<Task> tasks = mStack.getStackTasks(); - final boolean isValidVisibleStackRange = updateStackTransforms(mCurrentTaskTransforms, - tasks, stackScroll, visibleStackRange, ignoreTasksSet); + final boolean isValidVisibleRange = computeVisibleTaskTransforms(mCurrentTaskTransforms, + tasks, mStackScroller.getStackScroll(), targetStackScroll, visibleStackRange, + ignoreTasksSet); // Return all the invisible children to the pool - final List<TaskView> taskViews = getTaskViews(); - final int taskViewCount = taskViews.size(); - int lastFocusedTaskIndex = -1; mTmpTaskViewMap.clear(); - mTmpTaskViewMap.ensureCapacity(tasks.size()); + List<TaskView> taskViews = getTaskViews(); + int lastFocusedTaskIndex = -1; + int taskViewCount = taskViews.size(); for (int i = taskViewCount - 1; i >= 0; i--) { - final TaskView tv = taskViews.get(i); - final Task task = tv.getTask(); - final int taskIndex = mStack.indexOfStackTask(task); + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + int taskIndex = mStack.indexOfStackTask(task); // Skip ignored tasks - if (ignoreTasksSet.contains(task)) { + if (ignoreTasksSet.contains(task.key)) { continue; } @@ -445,13 +516,13 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } // Pick up all the newly visible children - int lastVisStackIndex = isValidVisibleStackRange ? visibleStackRange[1] : 0; - for (int i = mStack.getTaskCount() - 1; i >= lastVisStackIndex; i--) { - final Task task = tasks.get(i); - final TaskViewTransform transform = mCurrentTaskTransforms.get(i); + int lastVisStackIndex = isValidVisibleRange ? visibleStackRange[1] : 0; + for (int i = tasks.size() - 1; i >= lastVisStackIndex; i--) { + Task task = tasks.get(i); + TaskViewTransform transform = mCurrentTaskTransforms.get(i); // Skip ignored tasks - if (ignoreTasksSet.contains(task)) { + if (ignoreTasksSet.contains(task.key)) { continue; } @@ -502,26 +573,34 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** - * Cancels any existing {@link TaskView} animations, and updates each {@link TaskView} to its - * current position as defined by the {@link TaskStackLayoutAlgorithm}. + * Relayout the the visible {@link TaskView}s to their current transforms as specified by the + * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any + * animations that are current running on those task views, and will ensure that the children + * {@link TaskView}s will match the set of visible tasks in the stack. * - * @param ignoreTasks the set of tasks to ignore in the relayout + * @see #relayoutTaskViews(TaskViewAnimation, ArraySet<Task.TaskKey>) */ - private void updateTaskViewsToLayout(TaskViewAnimation animation, Task... ignoreTasks) { - // Keep track of the ignore tasks - ArraySet<Task> ignoreTasksSet = mTmpTaskSet; - ignoreTasksSet.clear(); - ignoreTasksSet.ensureCapacity(ignoreTasks.length); - Collections.addAll(ignoreTasksSet, ignoreTasks); + void relayoutTaskViews(TaskViewAnimation animation) { + relayoutTaskViews(animation, mIgnoreTasks); + } + /** + * Relayout the the visible {@link TaskView}s to their current transforms as specified by the + * {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any + * animations that are current running on those task views, and will ensure that the children + * {@link TaskView}s will match the set of visible tasks in the stack. + * + * @param ignoreTasksSet the set of tasks to ignore in the relayout + */ + void relayoutTaskViews(TaskViewAnimation animation, ArraySet<Task.TaskKey> ignoreTasksSet) { // If we had a deferred animation, cancel that - mDeferredTaskViewUpdateAnimation = null; + mDeferredTaskViewLayoutAnimation = null; // Cancel all task view animations cancelAllTaskViewAnimations(); - // Fetch the current set of TaskViews - bindTaskViewsWithStack(ignoreTasksSet); + // Synchronize the current set of TaskViews + bindVisibleTaskViews(mStackScroller.getStackScroll(), ignoreTasksSet); // Animate them to their final transforms with the given animation List<TaskView> taskViews = getTaskViews(); @@ -531,7 +610,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal final int taskIndex = mStack.indexOfStackTask(tv.getTask()); final TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex); - if (ignoreTasksSet.contains(tv.getTask())) { + if (ignoreTasksSet.contains(tv.getTask().key)) { continue; } @@ -542,8 +621,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal /** * Posts an update to synchronize the {@link TaskView}s with the stack on the next frame. */ - private void updateTaskViewsToLayoutOnNextFrame(TaskViewAnimation animation) { - mDeferredTaskViewUpdateAnimation = animation; + void relayoutTaskViewsOnNextFrame(TaskViewAnimation animation) { + mDeferredTaskViewLayoutAnimation = animation; postInvalidateOnAnimation(); } @@ -558,13 +637,62 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** + * Returns the current task transforms of all tasks, falling back to the stack layout if there + * is no {@link TaskView} for the task. + */ + public void getCurrentTaskTransforms(ArrayList<Task> tasks, + ArrayList<TaskViewTransform> transformsOut) { + Utilities.matchTaskListSize(tasks, transformsOut); + for (int i = tasks.size() - 1; i >= 0; i--) { + Task task = tasks.get(i); + TaskViewTransform transform = transformsOut.get(i); + TaskView tv = getChildViewForTask(task); + if (tv != null) { + transform.fillIn(tv); + } else { + mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(), + transform, null); + } + transform.visible = true; + } + } + + /** + * Returns the task transforms for all the tasks in the stack if the stack was at the given + * {@param stackScroll}. + */ + public void getLayoutTaskTransforms(float stackScroll, ArrayList<Task> tasks, + ArrayList<TaskViewTransform> transformsOut) { + Utilities.matchTaskListSize(tasks, transformsOut); + for (int i = tasks.size() - 1; i >= 0; i--) { + Task task = tasks.get(i); + TaskViewTransform transform = transformsOut.get(i); + mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null); + transform.visible = true; + } + } + + /** + * Cancels all {@link TaskView} animations. + * + * @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>) + */ + void cancelAllTaskViewAnimations() { + cancelAllTaskViewAnimations(mIgnoreTasks); + } + + /** * Cancels all {@link TaskView} animations. + * + * @param ignoreTasksSet The set of tasks to continue running their animations. */ - private void cancelAllTaskViewAnimations() { + void cancelAllTaskViewAnimations(ArraySet<Task.TaskKey> ignoreTasksSet) { List<TaskView> taskViews = getTaskViews(); for (int i = taskViews.size() - 1; i >= 0; i--) { final TaskView tv = taskViews.get(i); - tv.cancelTransformAnimation(); + if (!ignoreTasksSet.contains(tv.getTask().key)) { + tv.cancelTransformAnimation(); + } } } @@ -577,11 +705,22 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Update the clip on each task child List<TaskView> taskViews = getTaskViews(); TaskView tmpTv = null; + TaskView prevVisibleTv = null; int taskViewCount = taskViews.size(); for (int i = 0; i < taskViewCount; i++) { TaskView tv = taskViews.get(i); TaskView frontTv = null; int clipBottom = 0; + + if (mIgnoreTasks.contains(tv.getTask().key)) { + // For each of the ignore tasks, update the translationZ of its TaskView to be + // between the translationZ of the tasks immediately underneath it + if (prevVisibleTv != null) { + tv.setTranslationZ(Math.max(tv.getTranslationZ(), + prevVisibleTv.getTranslationZ() + 0.1f)); + } + } + if (i < (taskViewCount - 1) && tv.shouldClipViewInStack()) { // Find the next view to clip against for (int j = i + 1; j < taskViewCount; j++) { @@ -609,33 +748,37 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (!config.useHardwareLayers) { tv.mThumbnailView.updateThumbnailVisibility(clipBottom - tv.getPaddingBottom()); } + prevVisibleTv = tv; } mTaskViewsClipDirty = false; } /** - * Updates the min and max virtual scroll bounds. + * Updates the layout algorithm min and max virtual scroll bounds. * - * @param ignoreTasks the set of tasks to ignore in the relayout + * @see #updateLayoutAlgorithm(boolean, ArraySet<Task.TaskKey>) */ - void updateLayout(boolean boundScrollToNewMinMax, Task... ignoreTasks) { - // Keep track of the ingore tasks - ArraySet<Task> ignoreTasksSet = mTmpTaskSet; - ignoreTasksSet.clear(); - ignoreTasksSet.ensureCapacity(ignoreTasks.length); - Collections.addAll(ignoreTasksSet, ignoreTasks); + void updateLayoutAlgorithm(boolean boundScrollToNewMinMax) { + updateLayoutAlgorithm(boundScrollToNewMinMax, mIgnoreTasks); + } + /** + * Updates the min and max virtual scroll bounds. + * + * @param ignoreTasksSet the set of tasks to ignore in the relayout + */ + void updateLayoutAlgorithm(boolean boundScrollToNewMinMax, + ArraySet<Task.TaskKey> ignoreTasksSet) { // Compute the min and max scroll values mLayoutAlgorithm.update(mStack, ignoreTasksSet); - // Update the freeform workspace + // Update the freeform workspace background SystemServicesProxy ssp = Recents.getSystemServices(); if (ssp.hasFreeformWorkspaceSupport()) { mTmpRect.set(mLayoutAlgorithm.mFreeformRect); mFreeformWorkspaceBackground.setBounds(mTmpRect); } - // Debug logging if (boundScrollToNewMinMax) { mStackScroller.boundScroll(); } @@ -937,10 +1080,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // Notify accessibility sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SCROLLED); } - if (mDeferredTaskViewUpdateAnimation != null) { - updateTaskViewsToLayout(mDeferredTaskViewUpdateAnimation); + if (mDeferredTaskViewLayoutAnimation != null) { + relayoutTaskViews(mDeferredTaskViewLayoutAnimation); mTaskViewsClipDirty = true; - mDeferredTaskViewUpdateAnimation = null; + mDeferredTaskViewLayoutAnimation = null; } if (mTaskViewsClipDirty) { clipTaskViews(); @@ -948,30 +1091,16 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** - * Computes the stack and task rects. - * - * @param ignoreTasks the set of tasks to ignore in the relayout - */ - public void computeRects(Rect taskStackBounds, boolean boundScroll, Task... ignoreTasks) { - // Compute the rects in the stack algorithm - mLayoutAlgorithm.initialize(taskStackBounds, - TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); - - // Update the scroll bounds - updateLayout(boundScroll, ignoreTasks); - } - - /** * This is ONLY used from the Recents component to update the dummy stack view for purposes * of getting the task rect to animate to. */ public void updateLayoutForStack(TaskStack stack) { mStack = stack; - updateLayout(false); + updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET); } /** - * Computes the maximum number of visible tasks and thumbnails. Requires that + * Computes the maximum number of visible tasks and thumbnails. Requires that * updateLayoutForStack() is called first. */ public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() { @@ -1002,16 +1131,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); - // Compute our stack/task rects - computeRects(mStackBounds, false); + // Compute the rects in the stack algorithm + mLayoutAlgorithm.initialize(mStackBounds, + TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); + updateLayoutAlgorithm(false /* boundScroll */, EMPTY_TASK_SET); // If this is the first layout, then scroll to the front of the stack, then update the // TaskViews with the stack so that we can lay them out if (mAwaitingFirstLayout) { mStackScroller.setStackScrollToInitialState(); } - mTmpTaskSet.clear(); - bindTaskViewsWithStack(mTmpTaskSet); + // Rebind all the views, including the ignore ones + bindVisibleTaskViews(mStackScroller.getStackScroll(), EMPTY_TASK_SET); // Measure each of the TaskViews mTmpTaskViews.clear(); @@ -1066,7 +1197,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mStackScroller.boundScroll(); } } - updateTaskViewsToLayout(TaskViewAnimation.IMMEDIATE); + // Relayout all of the task views including the ignored ones + relayoutTaskViews(TaskViewAnimation.IMMEDIATE, EMPTY_TASK_SET); clipTaskViews(); if (mAwaitingFirstLayout || !mEnterAnimationComplete) { @@ -1106,8 +1238,29 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public boolean isTouchPointInView(float x, float y, TaskView tv) { - return (tv.getLeft() <= x && x <= tv.getRight()) && - (tv.getTop() <= y && y <= tv.getBottom()); + mTmpRect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); + mTmpRect.offset((int) tv.getTranslationX(), (int) tv.getTranslationY()); + return mTmpRect.contains((int) x, (int) y); + } + + /** + * Returns a non-ignored task in the {@param tasks} list that can be used as an achor when + * calculating the scroll position before and after a layout change. + */ + public Task findAnchorTask(List<Task> tasks, MutableBoolean isFrontMostTask) { + for (int i = tasks.size() - 1; i >= 0; i--) { + Task task = tasks.get(i); + + // Ignore deleting tasks + if (mIgnoreTasks.contains(task.key)) { + if (i == tasks.size() - 1) { + isFrontMostTask.value = true; + } + continue; + } + return task; + } + return null; } @Override @@ -1152,71 +1305,39 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onStackTaskAdded(TaskStack stack, Task newTask) { // Update the min/max scroll and animate other task views into their new positions - updateLayout(true); + updateLayoutAlgorithm(true /* boundScroll */); // Animate all the tasks into place - updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator)); } + /** + * We expect that the {@link TaskView} associated with the removed task is already hidden. + */ @Override public void onStackTaskRemoved(TaskStack stack, Task removedTask, boolean wasFrontMostTask, - Task newFrontMostTask) { + Task newFrontMostTask, TaskViewAnimation animation) { if (mFocusedTask == removedTask) { resetFocusedTask(removedTask); } - 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); - } + // 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 = mStack.getStackFrontMostTask(); - float prevAnchorTaskScroll = 0; - if (anchorTask != null) { - prevAnchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); - } + // Remove the task from the ignored set + removeIgnoreTask(removedTask); - // Update the min/max scroll and animate other task views into their new positions - updateLayout(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 (anchorTask != null) { - // Otherwise, offset the scroll by the movement of the anchor task - float anchorTaskScroll = mLayoutAlgorithm.getStackScrollForTask(anchorTask); - float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll); - if (mLayoutAlgorithm.getFocusState() != TaskStackLayoutAlgorithm.STATE_FOCUSED) { - // If we are focused, we don't want the front task to move, but otherwise, we - // allow the back task to move up, and the front task to move back - stackScrollOffset /= 2; - } - mStackScroller.setStackScroll(mStackScroller.getStackScroll() + stackScrollOffset); - mStackScroller.boundScroll(); - } - } else { - // 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); - } - - // Update the min/max scroll and animate other task views into their new positions - updateLayout(true); + // If requested, relayout with the given animation + if (animation != null) { + updateLayoutAlgorithm(true /* boundScroll */); + relayoutTaskViews(animation); } - // Animate all the tasks into place - updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, - mFastOutSlowInInterpolator)); - // Update the new front most task's action button if (mScreenPinningEnabled && newFrontMostTask != null) { TaskView frontTv = getChildViewForTask(newFrontMostTask); @@ -1232,7 +1353,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } @Override - public void onHistoryTaskRemoved(TaskStack stack, Task removedTask) { + public void onHistoryTaskRemoved(TaskStack stack, Task removedTask, + TaskViewAnimation animation) { // To be implemented } @@ -1316,7 +1438,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onTaskViewClipStateChanged(TaskView tv) { - clipTaskViews(); + if (!mTaskViewsClipDirty) { + mTaskViewsClipDirty = true; + invalidate(); + } } /**** TaskStackViewScroller.TaskStackViewScrollerCallbacks ****/ @@ -1324,7 +1449,9 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @Override public void onScrollChanged(float prevScroll, float curScroll, TaskViewAnimation animation) { mUIDozeTrigger.poke(); - updateTaskViewsToLayoutOnNextFrame(animation); + if (animation != null) { + relayoutTaskViewsOnNextFrame(animation); + } if (shouldShowHistoryButton() && prevScroll > SHOW_HISTORY_BUTTON_SCROLL_THRESHOLD && @@ -1354,7 +1481,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal tv.dismissTask(); } else { // Otherwise, remove the task from the stack immediately - mStack.removeTask(t); + mStack.removeTask(t, TaskViewAnimation.IMMEDIATE); } } } @@ -1402,7 +1529,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public final void onBusEvent(TaskViewDismissedEvent event) { - removeTaskViewFromStack(event.taskView); + removeTaskViewFromStack(event.taskView, event.task); EventBus.getDefault().send(new DeleteTaskDataEvent(event.task)); } @@ -1455,23 +1582,29 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } public final void onBusEvent(DragDropTargetChangedEvent event) { + TaskViewAnimation animation = new TaskViewAnimation(250, mFastOutSlowInInterpolator); if (event.dropTarget instanceof TaskStack.DockState) { // Calculate the new task stack bounds that matches the window size that Recents will // have after the drop + addIgnoreTask(event.task); final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget; mStackBounds.set(dockState.getDockedTaskStackBounds(getMeasuredWidth(), getMeasuredHeight(), mDividerSize, mLayoutAlgorithm.mSystemInsets, getResources())); - computeRects(mStackBounds, true /* boundScroll */, event.task /* ignoreTask */); - updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator), - event.task /* ignoreTask */); + mLayoutAlgorithm.initialize(mStackBounds, + TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); + updateLayoutAlgorithm(true /* boundScroll */); } else { - // Restore the pre-drag task stack bounds + // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging + // task view, so add it back to the ignore set after updating the layout mStackBounds.set(mStableStackBounds); - computeRects(mStackBounds, true /* boundScroll */); - updateTaskViewsToLayout(new TaskViewAnimation(250, mFastOutSlowInInterpolator), - event.task /* ignoreTask */); + removeIgnoreTask(event.task); + mLayoutAlgorithm.initialize(mStackBounds, + TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack)); + updateLayoutAlgorithm(true /* boundScroll */); + addIgnoreTask(event.task); } + relayoutTaskViews(animation); } public final void onBusEvent(final DragEndEvent event) { @@ -1487,14 +1620,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal if (hasChangedStacks) { // Move the task to the right position in the stack (ie. the front of the stack if - // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks + // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks // before we update their stack ids, otherwise, the keys will have changed. if (event.dropTarget == mFreeformWorkspaceDropTarget) { mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID); } else if (event.dropTarget == mStackDropTarget) { mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID); } - updateLayout(true); + updateLayoutAlgorithm(true /* boundScroll */); // Move the task to the new stack in the system after the animation completes event.addPostAnimationCallback(new Runnable() { @@ -1522,11 +1655,12 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(), mTmpTransform, null); event.getAnimationTrigger().increment(); - updateTaskViewsToLayout(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + relayoutTaskViews(new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator)); updateTaskViewToTransform(event.taskView, mTmpTransform, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, mFastOutSlowInInterpolator, event.getAnimationTrigger().decrementOnAnimationEnd())); + removeIgnoreTask(event.task); } public final void onBusEvent(StackViewScrolledEvent event) { @@ -1593,15 +1727,14 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal * Removes the task from the stack, and updates the focus to the next task in the stack if the * removed TaskView was focused. */ - private void removeTaskViewFromStack(TaskView tv) { - Task task = tv.getTask(); - + private void removeTaskViewFromStack(TaskView tv, Task task) { // Announce for accessibility tv.announceForAccessibility(getContext().getString( - R.string.accessibility_recents_item_dismissed, tv.getTask().title)); + R.string.accessibility_recents_item_dismissed, task.title)); // Remove the task from the stack - mStack.removeTask(task); + mStack.removeTask(task, new TaskViewAnimation(DEFAULT_SYNC_STACK_DURATION, + mFastOutSlowInInterpolator)); } /** @@ -1622,7 +1755,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } /** - * Returns the insert index for the task in the current set of task views. If the given task + * Returns the insert index for the task in the current set of task views. If the given task * is already in the task view list, then this method returns the insert index assuming it * is first removed at the previous index. * 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 32f02acd9d77..5335b14078dd 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java @@ -151,6 +151,7 @@ public class TaskStackViewScroller { /** Animates the stack scroll into bounds */ ObjectAnimator animateBoundScroll() { + // TODO: Take duration for snap back float curScroll = getStackScroll(); float newScroll = getBoundedStackScroll(curScroll); if (Float.compare(newScroll, curScroll) != 0) { 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 4813c19894e3..e9f6f398a286 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java @@ -21,13 +21,16 @@ import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Rect; -import android.util.Log; +import android.util.ArrayMap; +import android.util.MutableBoolean; import android.view.InputDevice; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewParent; +import android.view.animation.Interpolator; +import android.view.animation.PathInterpolator; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.SwipeHelper; @@ -37,19 +40,25 @@ import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.activity.HideRecentsEvent; import com.android.systemui.recents.events.ui.StackViewScrolledEvent; import com.android.systemui.recents.events.ui.TaskViewDismissedEvent; +import com.android.systemui.recents.misc.RectFEvaluator; import com.android.systemui.recents.misc.SystemServicesProxy; import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.Task; import com.android.systemui.statusbar.FlingAnimationUtils; +import java.util.ArrayList; import java.util.List; -/* Handles touch events for a TaskStackView. */ +/** + * Handles touch events for a TaskStackView. + */ class TaskStackViewTouchHandler implements SwipeHelper.Callback { - private static final String TAG = "TaskStackViewTouchHandler"; - private static final boolean DEBUG = false; + private static final int INACTIVE_POINTER_ID = -1; - private static int INACTIVE_POINTER_ID = -1; + private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator(); + private static final Interpolator STACK_TRANSFORM_INTERPOLATOR = + new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f); Context mContext; TaskStackView mSv; @@ -74,6 +83,15 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { final int mWindowTouchSlop; private final StackViewScrolledEvent mStackViewScrolledEvent = new StackViewScrolledEvent(); + + // The current and final set of task transforms, sized to match the list of tasks in the stack + private ArrayList<Task> mCurrentTasks = new ArrayList<>(); + private ArrayList<TaskViewTransform> mCurrentTaskTransforms = new ArrayList<>(); + private ArrayList<TaskViewTransform> mFinalTaskTransforms = new ArrayList<>(); + private ArrayMap<View, Animator> mSwipeHelperAnimations = new ArrayMap<>(); + private TaskViewTransform mTmpTransform = new TaskViewTransform(); + private float mTargetStackScroll; + SwipeHelper mSwipeHelper; boolean mInterceptedBySwipeHelper; @@ -97,8 +115,14 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } @Override - protected void updateSnapBackAnimation(Animator anim) { + protected void prepareDismissAnimation(View v, Animator anim) { + mSwipeHelperAnimations.put(v, anim); + } + + @Override + protected void prepareSnapBackAnimation(View v, Animator anim) { anim.setInterpolator(mSv.mFastOutSlowInInterpolator); + mSwipeHelperAnimations.put(v, anim); } }; mSwipeHelper.setDisableHardwareLayers(true); @@ -119,21 +143,6 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { } } - /** Returns the view at the specified coordinates */ - TaskView findViewAtPoint(int x, int y) { - List<TaskView> taskViews = mSv.getTaskViews(); - int taskViewCount = taskViews.size(); - for (int i = taskViewCount - 1; i >= 0; i--) { - TaskView tv = taskViews.get(i); - if (tv.getVisibility() == View.VISIBLE) { - if (mSv.isTouchPointInView(x, y, tv)) { - return tv; - } - } - } - return null; - } - /** Touch preprocessing for handling below */ public boolean onInterceptTouchEvent(MotionEvent ev) { // Pass through to swipe helper if we are swiping @@ -179,6 +188,15 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { mScroller.stopBoundScrollAnimation(); Utilities.cancelAnimationWithoutCallbacks(mScrollFlingAnimator); + // Finish any existing task animations from the delete + mSv.cancelAllTaskViewAnimations(); + // Finish any of the swipe helper animations + ArrayMap<View, Animator> existingAnimators = new ArrayMap<>(mSwipeHelperAnimations); + for (int i = 0; i < existingAnimators.size(); i++) { + existingAnimators.get(existingAnimators.keyAt(i)).end(); + } + mSwipeHelperAnimations.clear(); + // Initialize the velocity tracker initOrResetVelocityTracker(); mVelocityTracker.addMovement(ev); @@ -214,9 +232,6 @@ 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); - } mStackViewScrolledEvent.updateY(y - mLastY); EventBus.getDefault().send(mStackViewScrolledEvent); } @@ -343,12 +358,19 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { @Override public View getChildAtPosition(MotionEvent ev) { - return findViewAtPoint((int) ev.getX(), (int) ev.getY()); + TaskView tv = findViewAtPoint((int) ev.getX(), (int) ev.getY()); + if (tv != null && canChildBeDismissed(tv)) { + return tv; + } + return null; } @Override public boolean canChildBeDismissed(View v) { - return true; + // Disallow dismissing an already dismissed task + TaskView tv = (TaskView) v; + return !mSwipeHelperAnimations.containsKey(v) && + (mSv.getStack().indexOfStackTask(tv.getTask()) != -1); } @Override @@ -364,34 +386,113 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { if (parent != null) { parent.requestDisallowInterceptTouchEvent(true); } + + // Add this task to the set of tasks we are deleting + mSv.addIgnoreTask(tv.getTask()); + + // Determine if we are animating the other tasks while dismissing this task + mCurrentTasks = mSv.getStack().getStackTasks(); + MutableBoolean isFrontMostTask = new MutableBoolean(false); + Task anchorTask = mSv.findAnchorTask(mCurrentTasks, isFrontMostTask); + TaskStackViewScroller stackScroller = mSv.getScroller(); + if (anchorTask != null) { + // Get the current set of task transforms + mSv.getCurrentTaskTransforms(mCurrentTasks, mCurrentTaskTransforms); + + // Get the stack scroll of the task to anchor to (since we are removing something, the + // front most task will be our anchor task) + float prevAnchorTaskScroll = 0; + boolean pullStackForward = mCurrentTasks.size() > 0; + if (pullStackForward) { + prevAnchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask); + } + + // Calculate where the views would be without the deleting tasks + mSv.updateLayoutAlgorithm(false /* boundScroll */); + + float newStackScroll = stackScroller.getStackScroll(); + if (isFrontMostTask.value) { + // Bound the stack scroll to pull tasks forward if necessary + newStackScroll = stackScroller.getBoundedStackScroll(newStackScroll); + } else if (pullStackForward) { + // Otherwise, offset the scroll by the movement of the anchor task + float anchorTaskScroll = mSv.getStackAlgorithm().getStackScrollForTask(anchorTask); + float stackScrollOffset = (anchorTaskScroll - prevAnchorTaskScroll); + if (mSv.getStackAlgorithm().getFocusState() != + TaskStackLayoutAlgorithm.STATE_FOCUSED) { + // If we are focused, we don't want the front task to move, but otherwise, we + // allow the back task to move up, and the front task to move back + stackScrollOffset /= 2; + } + newStackScroll = stackScroller.getBoundedStackScroll(stackScroller.getStackScroll() + + stackScrollOffset); + } + + // Pick up the newly visible views, not including the deleting tasks + mSv.bindVisibleTaskViews(newStackScroll); + + // Get the final set of task transforms (with task removed) + mSv.getLayoutTaskTransforms(newStackScroll, mCurrentTasks, mFinalTaskTransforms); + + // Set the target to scroll towards upon dismissal + mTargetStackScroll = newStackScroll; + + /* + * Post condition: All views that will be visible as a part of the gesture are retrieved + * and at their initial positions. The stack is still at the current + * scroll, but the layout is updated without the task currently being + * dismissed. + */ + } } @Override public boolean updateSwipeProgress(View v, boolean dismissable, float swipeProgress) { + updateTaskViewTransforms(getDismissFraction(v)); return true; } + /** + * Called after the {@link TaskView} is finished animating away. + */ @Override public void onChildDismissed(View v) { TaskView tv = (TaskView) v; + // Re-enable clipping with the stack (we will reuse this view) tv.setClipViewInStack(true); // Re-enable touch events from this task view tv.setTouchEnabled(true); + // Update the scroll to the final scroll position from onBeginDrag() + mSv.getScroller().setStackScroll(mTargetStackScroll, null); // Remove the task view from the stack EventBus.getDefault().send(new TaskViewDismissedEvent(tv.getTask(), tv)); + // Stop tracking this deletion animation + mSwipeHelperAnimations.remove(v); // Keep track of deletions by keyboard MetricsLogger.histogram(tv.getContext(), "overview_task_dismissed_source", Constants.Metrics.DismissSourceSwipeGesture); } + /** + * Called after the {@link TaskView} is finished animating back into the list. + * onChildDismissed() calls. + */ @Override public void onChildSnappedBack(View v) { TaskView tv = (TaskView) v; + // Re-enable clipping with the stack tv.setClipViewInStack(true); // Re-enable touch events from this task view tv.setTouchEnabled(true); + + // Stop tracking this deleting task, and update the layout to include this task again. The + // stack scroll does not need to be reset, since the scroll has not actually changed in + // onBeginDrag(). + mSv.removeIgnoreTask(tv.getTask()); + mSv.updateLayoutAlgorithm(false /* boundScroll */); + mSwipeHelperAnimations.remove(v); } @Override @@ -414,4 +515,59 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { return 0; } + /** + * Interpolates the non-deleting tasks to their final transforms from their current transforms. + */ + private void updateTaskViewTransforms(float dismissFraction) { + List<TaskView> taskViews = mSv.getTaskViews(); + int taskViewCount = taskViews.size(); + for (int i = 0; i < taskViewCount; i++) { + TaskView tv = taskViews.get(i); + Task task = tv.getTask(); + + if (mSv.isIgnoredTask(task)) { + continue; + } + + int taskIndex = mCurrentTasks.indexOf(task); + TaskViewTransform fromTransform = mCurrentTaskTransforms.get(taskIndex); + TaskViewTransform toTransform = mFinalTaskTransforms.get(taskIndex); + + mTmpTransform.copyFrom(fromTransform); + // We only really need to interpolate the bounds, progress and translation + mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect, + toTransform.rect)); + mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction; + mTmpTransform.translationZ = fromTransform.translationZ + + (toTransform.translationZ - fromTransform.translationZ) * dismissFraction; + + mSv.updateTaskViewToTransform(tv, mTmpTransform, TaskViewAnimation.IMMEDIATE); + } + } + + /** Returns the view at the specified coordinates */ + private TaskView findViewAtPoint(int x, int y) { + List<Task> tasks = mSv.getStack().getStackTasks(); + int taskCount = tasks.size(); + for (int i = taskCount - 1; i >= 0; i--) { + TaskView tv = mSv.getChildViewForTask(tasks.get(i)); + if (tv != null && tv.getVisibility() == View.VISIBLE) { + if (mSv.isTouchPointInView(x, y, tv)) { + return tv; + } + } + } + return null; + } + + /** + * Returns the fraction which we should interpolate the other task views based on the dismissal + * of this given task. + * + * TODO: We can interpolate this to adjust when the other tasks should respond to the dismissal + */ + private float getDismissFraction(View v) { + float fraction = Math.min(1f, Math.abs(v.getTranslationX() / mSv.getWidth())); + return STACK_TRANSFORM_INTERPOLATOR.getInterpolation(fraction); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java index 538c2483cb9f..85b7c82112a4 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java @@ -88,12 +88,39 @@ public class TaskViewTransform { public float alpha = 1f; public boolean visible = false; - float p = 0f; + + // This is the relative task progress of this task, relative to the stack scroll at which this + // transform was computed + public float p = 0f; // This is a window-space rect used for positioning the task in the stack and freeform workspace public RectF rect = new RectF(); /** + * Fills int this transform from the state of the given TaskView. + */ + public void fillIn(TaskView tv) { + translationZ = tv.getTranslationZ(); + scale = tv.getScaleX(); + alpha = tv.getAlpha(); + visible = true; + p = tv.getTaskProgress(); + rect.set(tv.getLeft(), tv.getTop(), tv.getRight(), tv.getBottom()); + } + + /** + * Copies the transform state from another {@link TaskViewTransform}. + */ + public void copyFrom(TaskViewTransform other) { + translationZ = other.translationZ; + scale = other.scale; + alpha = other.alpha; + visible = other.visible; + p = other.p; + rect.set(other.rect); + } + + /** * Resets the current transform. */ public void reset() { |