summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/res/values-sw600dp/config.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/misc/RectFEvaluator.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java449
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java208
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/TaskViewTransform.java29
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() {