*/
public int getInitialFocusTaskIndex(int numTasks) {
RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+ RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
if (launchedFromAppWithThumbnail) {
- if (debugFlags.isFastToggleRecentsEnabled()) {
+ if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
// If fast toggling, focus the front most task so that the next tap will focus the
// N-1 task
return numTasks - 1;
// If coming from another app, focus the next task
return numTasks - 2;
} else {
- if (debugFlags.isFastToggleRecentsEnabled()) {
+ if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
// If fast toggling, defer focusing until the next tap (which will automatically
// focus the front most task)
return -1;
}
};
+ public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
+
/**
* @return the first parent walking up the view hierarchy that has the given class type.
*
import android.graphics.Path;
import android.graphics.RectF;
import android.view.View;
+import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.systemui.Interpolators;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
+import java.util.ArrayList;
import java.util.List;
/**
private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR =
new PathInterpolator(0.4f, 0, 1f, 1f);
+ private static final PathInterpolator FOCUS_NEXT_TASK_INTERPOLATOR =
+ new PathInterpolator(0.4f, 0, 0, 1f);
+ private static final PathInterpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR =
+ new PathInterpolator(0, 0, 0, 1f);
+ private static final PathInterpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR =
+ new PathInterpolator(0.4f, 0, 0.2f, 1f);
+
private TaskStackView mStackView;
private TaskViewTransform mTmpTransform = new TaskViewTransform();
+ private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>();
+ private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>();
public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
mStackView = stackView;
mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
}
}
+
+ /**
+ * Starts the animation to focus the next {@link TaskView} when paging through recents.
+ *
+ * @return whether or not this will trigger a scroll in the stack
+ */
+ public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask,
+ boolean requestViewFocus) {
+ TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
+ TaskStackViewScroller stackScroller = mStackView.getScroller();
+ TaskStack stack = mStackView.getStack();
+
+ final float newScroll = stackLayout.getStackScrollForTask(newFocusedTask);
+ boolean willScrollToFront = newScroll > stackScroller.getStackScroll();
+ boolean willScroll = Float.compare(newScroll, stackScroller.getStackScroll()) != 0;
+
+ // Get the current set of task transforms
+ ArrayList<Task> stackTasks = stack.getStackTasks();
+ mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
+
+ // Pick up the newly visible views after the scroll
+ mStackView.bindVisibleTaskViews(newScroll);
+
+ // Update the internal state
+ stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
+ stackScroller.setStackScroll(newScroll, null /* animation */);
+ mStackView.cancelDeferredTaskViewLayoutAnimation();
+
+ // Get the final set of task transforms
+ mStackView.getLayoutTaskTransforms(newScroll, stackTasks, mTmpFinalTaskTransforms);
+
+ // Focus the task view
+ TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask);
+ newFocusedTaskView.setFocusedState(true, requestViewFocus);
+
+ // Setup the end listener to return all the hidden views to the view pool after the
+ // focus animation
+ AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStackView.bindVisibleTaskViews(newScroll);
+ }
+ };
+
+ List<TaskView> taskViews = mStackView.getTaskViews();
+ int taskViewCount = taskViews.size();
+ int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView);
+ for (int i = 0; i < taskViewCount; i++) {
+ TaskView tv = taskViews.get(i);
+ Task task = tv.getTask();
+
+ if (mStackView.isIgnoredTask(task)) {
+ continue;
+ }
+
+ int taskIndex = stackTasks.indexOf(task);
+ TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
+ TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
+
+ // Update the task to the initial state (for the newly picked up tasks)
+ mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
+
+ int duration;
+ Interpolator interpolator;
+ if (willScrollToFront) {
+ duration = Math.max(100, 100 + ((i - 1) * 50));
+ interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
+ } else {
+ if (i < newFocusTaskViewIndex) {
+ duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50);
+ interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
+ } else if (i > newFocusTaskViewIndex) {
+ duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50));
+ interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR;
+ } else {
+ duration = 200;
+ interpolator = FOCUS_NEXT_TASK_INTERPOLATOR;
+ }
+ }
+
+ AnimationProps anim = new AnimationProps()
+ .setDuration(AnimationProps.BOUNDS, duration)
+ .setInterpolator(AnimationProps.BOUNDS, interpolator)
+ .setListener(endListener);
+ mStackView.updateTaskViewToTransform(tv, toTransform, anim);
+ }
+ return willScroll;
+ }
}
launchTaskIndex - 1));
}
} else {
- float offsetPct = (float) (mTaskRect.height() / 2) / mStackRect.height();
+ float offsetPct = (float) (mTaskRect.height() / 3) / mStackRect.height();
float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX)));
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.ComponentName;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import com.android.internal.logging.MetricsLogger;
private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
+ LayoutInflater mInflater;
TaskStack mStack;
TaskStackLayoutAlgorithm mLayoutAlgorithm;
TaskStackViewScroller mStackScroller;
boolean mScreenPinningEnabled;
// The stable stack bounds are the full bounds that we were measured with from RecentsView
- Rect mStableStackBounds = new Rect();
+ private Rect mStableStackBounds = new Rect();
// The current stack bounds are dynamic and may change as the user drags and drops
- Rect mStackBounds = new Rect();
+ private Rect mStackBounds = new Rect();
- int[] mTmpVisibleRange = new int[2];
- Rect mTmpRect = new Rect();
- ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
- List<TaskView> mTmpTaskViews = new ArrayList<>();
- TaskViewTransform mTmpTransform = new TaskViewTransform();
- LayoutInflater mInflater;
+ private int[] mTmpVisibleRange = new int[2];
+ private Rect mTmpRect = new Rect();
+ private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
+ private List<TaskView> mTmpTaskViews = new ArrayList<>();
+ private TaskViewTransform mTmpTransform = new TaskViewTransform();
// A convenience update listener to request updating clipping of tasks
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
int frontMostVisibleIndex = -1;
int backMostVisibleIndex = -1;
boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
+ boolean targetScrollIsInFront = targetStackScroll > curStackScroll;
// We can reuse the task transforms where possible to reduce object allocation
Utilities.matchTaskListSize(tasks, taskTransforms);
frontMostVisibleIndex = i;
}
backMostVisibleIndex = i;
- } else {
+ } else if (!targetScrollIsInFront) {
if (backMostVisibleIndex != -1) {
// We've reached the end of the visible range, so going down the rest of the
// stack, we can just reset the transforms accordingly
}
// Skip the invisible non-freeform stack tasks
- if (i > visibleStackRange[0] && !task.isFreeformTask()) {
+ if (!task.isFreeformTask() && !transform.visible) {
continue;
}
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);
+ mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null,
+ true /* forceUpdate */);
transform.visible = true;
}
}
/**
+ * Cancels the next deferred task view layout.
+ */
+ void cancelDeferredTaskViewLayoutAnimation() {
+ mDeferredTaskViewLayoutAnimation = null;
+ }
+
+ /**
* Cancels all {@link TaskView} animations.
*
* @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>)
TaskView frontTv = null;
int clipBottom = 0;
- if (mIgnoreTasks.contains(tv.getTask().key)) {
+ if (isIgnoredTask(tv.getTask())) {
// 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) {
}
/**
- * Sets the focused task to the provided (bounded taskIndex).
+ * Sets the focused task to the provided (bounded focusTaskIndex).
*
* @return whether or not the stack will scroll as a part of this focus change
*/
- private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
- final boolean requestViewFocus, final int timerIndicatorDuration) {
+ private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
+ boolean requestViewFocus, int timerIndicatorDuration) {
// Find the next task to focus
int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
- Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
+ Math.max(0, Math.min(mStack.getTaskCount() - 1, focusTaskIndex)) : -1;
final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
mStack.getStackTasks().get(newFocusedTaskIndex) : null;
}
boolean willScroll = false;
-
mFocusedTask = newFocusedTask;
if (newFocusedTask != null) {
}
}
- Runnable focusTaskRunnable = new Runnable() {
- @Override
- public void run() {
- final TaskView tv = getChildViewForTask(newFocusedTask);
- if (tv != null) {
- tv.setFocusedState(true, requestViewFocus);
- }
- }
- };
-
if (scrollToTask) {
// Cancel any running enter animations at this point when we scroll or change focus
if (!mEnterAnimationComplete) {
cancelAllTaskViewAnimations();
}
- // TODO: Center the newly focused task view, only if not freeform
- float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
- if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
- mStackScroller.animateScroll(newScroll, focusTaskRunnable);
- willScroll = true;
- } else {
- focusTaskRunnable.run();
- }
- mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
+ willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask,
+ requestViewFocus);
} else {
- focusTaskRunnable.run();
+ // Focus the task view
+ TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask);
+ if (newFocusedTaskView != null) {
+ newFocusedTaskView.setFocusedState(true, requestViewFocus);
+ }
}
}
return willScroll;
Task task = tasks.get(i);
// Ignore deleting tasks
- if (mIgnoreTasks.contains(task.key)) {
+ if (isIgnoredTask(task)) {
if (i == tasks.size() - 1) {
isFrontMostTask.value = true;
}
}
@Override
- public void prepareViewToEnterPool(TaskView tv) {
+ public void onReturnViewToPool(TaskView tv) {
final Task task = tv.getTask();
// Report that this tasks's data is no longer being used
}
@Override
- public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
+ public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) {
// Find the index where this task should be placed in the stack
int taskIndex = mStack.indexOfStackTask(task);
int insertIndex = findTaskViewInsertIndex(task, taskIndex);
stopScroller();
stopBoundScrollAnimation();
- mFinalAnimatedScroll = newScroll;
- mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
- mScrollAnimator.setDuration(mContext.getResources().getInteger(
- R.integer.recents_animate_task_stack_scroll_duration));
- mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- mScrollAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (postRunnable != null) {
- postRunnable.run();
+ if (Float.compare(mStackScrollP, newScroll) != 0) {
+ mFinalAnimatedScroll = newScroll;
+ mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
+ mScrollAnimator.setDuration(mContext.getResources().getInteger(
+ R.integer.recents_animate_task_stack_scroll_duration));
+ mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ mScrollAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (postRunnable != null) {
+ postRunnable.run();
+ }
+ mScrollAnimator.removeAllListeners();
}
- mScrollAnimator.removeAllListeners();
+ });
+ mScrollAnimator.start();
+ } else {
+ if (postRunnable != null) {
+ postRunnable.run();
}
- });
- mScrollAnimator.start();
+ }
}
/** Aborts any current stack scrolls */
class TaskStackViewTouchHandler implements SwipeHelper.Callback {
private static final 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);
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.rect.set(Utilities.RECTF_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;
/* An interface to the consumer of a view pool */
public interface ViewPoolConsumer<V, T> {
public V createView(Context context);
- public void prepareViewToEnterPool(V v);
- public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
+ public void onReturnViewToPool(V v);
+ public void onPickUpViewFromPool(V v, T prepareData, boolean isNewView);
public boolean hasPreferredData(V v, T preferredData);
}
/** Returns a view into the pool */
void returnViewToPool(V v) {
- mViewCreator.prepareViewToEnterPool(v);
+ mViewCreator.onReturnViewToPool(v);
mPool.push(v);
}
v = mPool.pop();
}
}
- mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
+ mViewCreator.onPickUpViewFromPool(v, prepareData, isNewView);
return v;
}