TaskStackAnimationHelper.java revision a0610760f986d9e714e855e21e8bd71267650596
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.TimeInterpolator;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.content.res.Configuration;
25import android.content.res.Resources;
26import android.util.Log;
27import android.view.View;
28import android.view.animation.Interpolator;
29import android.view.animation.PathInterpolator;
30
31import com.android.systemui.Interpolators;
32import com.android.systemui.R;
33import com.android.systemui.recents.Recents;
34import com.android.systemui.recents.RecentsActivityLaunchState;
35import com.android.systemui.recents.RecentsConfiguration;
36import com.android.systemui.recents.RecentsDebugFlags;
37import com.android.systemui.recents.events.EventBus;
38import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
39import com.android.systemui.recents.misc.ReferenceCountedTrigger;
40import com.android.systemui.recents.model.Task;
41import com.android.systemui.recents.model.TaskStack;
42import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
43
44import java.util.ArrayList;
45import java.util.List;
46
47/**
48 * A helper class to create task view animations for {@link TaskView}s in a {@link TaskStackView},
49 * but not the contents of the {@link TaskView}s.
50 */
51public class TaskStackAnimationHelper {
52
53    /**
54     * Callbacks from the helper to coordinate view-content animations with view animations.
55     */
56    public interface Callbacks {
57        /**
58         * Callback to prepare for the start animation for the launch target {@link TaskView}.
59         */
60        void onPrepareLaunchTargetForEnterAnimation();
61
62        /**
63         * Callback to start the animation for the launch target {@link TaskView}.
64         */
65        void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
66                boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger);
67
68        /**
69         * Callback to start the animation for the launch target {@link TaskView} when it is
70         * launched from Recents.
71         */
72        void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested,
73                ReferenceCountedTrigger postAnimationTrigger);
74
75        /**
76         * Callback to start the animation for the front {@link TaskView} if there is no launch
77         * target.
78         */
79        void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled);
80    }
81
82    private static final int DOUBLE_FRAME_OFFSET_MS = 33;
83    private static final int FRAME_OFFSET_MS = 16;
84
85    private static final int ENTER_EXIT_NUM_ANIMATING_TASKS = 5;
86
87    private static final int ENTER_FROM_HOME_ALPHA_DURATION = 100;
88    public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 300;
89    private static final Interpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR = Interpolators.LINEAR;
90
91    public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 200;
92    private static final Interpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR =
93            new PathInterpolator(0.4f, 0, 0.6f, 1f);
94
95    private static final int DISMISS_TASK_DURATION = 175;
96    private static final int DISMISS_ALL_TASKS_DURATION = 200;
97    private static final Interpolator DISMISS_ALL_TRANSLATION_INTERPOLATOR =
98            new PathInterpolator(0.4f, 0, 1f, 1f);
99
100    private static final Interpolator FOCUS_NEXT_TASK_INTERPOLATOR =
101            new PathInterpolator(0.4f, 0, 0, 1f);
102    private static final Interpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR =
103            new PathInterpolator(0, 0, 0, 1f);
104    private static final Interpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR =
105            Interpolators.LINEAR_OUT_SLOW_IN;
106
107    private static final Interpolator ENTER_WHILE_DOCKING_INTERPOLATOR =
108            Interpolators.LINEAR_OUT_SLOW_IN;
109
110    private final int mEnterAndExitFromHomeTranslationOffset;
111    private TaskStackView mStackView;
112
113    private TaskViewTransform mTmpTransform = new TaskViewTransform();
114    private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>();
115    private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>();
116
117    public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
118        mStackView = stackView;
119        mEnterAndExitFromHomeTranslationOffset = Recents.getConfiguration().isGridEnabled
120                ? 0 : DOUBLE_FRAME_OFFSET_MS;
121    }
122
123    /**
124     * Prepares the stack views and puts them in their initial animation state while visible, before
125     * the in-app enter animations start (after the window-transition completes).
126     */
127    public void prepareForEnterAnimation() {
128        RecentsConfiguration config = Recents.getConfiguration();
129        RecentsActivityLaunchState launchState = config.getLaunchState();
130        Resources res = mStackView.getResources();
131        Resources appResources = mStackView.getContext().getApplicationContext().getResources();
132
133        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
134        TaskStackViewScroller stackScroller = mStackView.getScroller();
135        TaskStack stack = mStackView.getStack();
136        Task launchTargetTask = stack.getLaunchTarget();
137
138        // Break early if there are no tasks
139        if (stack.getTaskCount() == 0) {
140            return;
141        }
142
143        int offscreenYOffset = stackLayout.mStackRect.height();
144        int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
145                R.dimen.recents_task_stack_animation_affiliate_enter_offset);
146        int launchedWhileDockingOffset = res.getDimensionPixelSize(
147                R.dimen.recents_task_stack_animation_launched_while_docking_offset);
148        boolean isLandscape = appResources.getConfiguration().orientation
149                == Configuration.ORIENTATION_LANDSCAPE;
150
151        float top = 0;
152        final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
153        if (isLowRamDevice && launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
154            stackLayout.getStackTransform(launchTargetTask, stackScroller.getStackScroll(),
155                    mTmpTransform, null /* frontTransform */);
156            top = mTmpTransform.rect.top;
157        }
158
159        // Prepare each of the task views for their enter animation from front to back
160        List<TaskView> taskViews = mStackView.getTaskViews();
161        for (int i = taskViews.size() - 1; i >= 0; i--) {
162            TaskView tv = taskViews.get(i);
163            Task task = tv.getTask();
164            boolean hideTask = launchTargetTask != null &&
165                    launchTargetTask.isFreeformTask() &&
166                    task.isFreeformTask();
167
168            // Get the current transform for the task, which will be used to position it offscreen
169            stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
170                    null);
171
172            if (hideTask) {
173                tv.setVisibility(View.INVISIBLE);
174            } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
175                if (task.isLaunchTarget) {
176                    tv.onPrepareLaunchTargetForEnterAnimation();
177                } else if (isLowRamDevice && i >= taskViews.size() -
178                            (TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT + 1)
179                        && !RecentsDebugFlags.Static.DisableRecentsLowRamEnterExitAnimation) {
180                    // Move the last 2nd and 3rd last tasks in-app animation to match the motion of
181                    // the last task's app transition
182                    stackLayout.getStackTransform(task, stackScroller.getStackScroll(),
183                            mTmpTransform, null);
184                    mTmpTransform.rect.offset(0, -top);
185                    mTmpTransform.alpha = 0f;
186                    mStackView.updateTaskViewToTransform(tv, mTmpTransform,
187                            AnimationProps.IMMEDIATE);
188                    stackLayout.getStackTransform(task, stackScroller.getStackScroll(),
189                            mTmpTransform, null);
190                    mTmpTransform.alpha = 1f;
191                    // Duration see {@link
192                    // com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION}
193                    mStackView.updateTaskViewToTransform(tv, mTmpTransform,
194                            new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN));
195                }
196            } else if (launchState.launchedFromHome) {
197                if (isLowRamDevice) {
198                    mTmpTransform.rect.offset(0, stackLayout.getTaskRect().height() / 4);
199                } else {
200                    // Move the task view off screen (below) so we can animate it in
201                    mTmpTransform.rect.offset(0, offscreenYOffset);
202                }
203                mTmpTransform.alpha = 0f;
204                mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
205            } else if (launchState.launchedViaDockGesture) {
206                int offset = isLandscape
207                        ? launchedWhileDockingOffset
208                        : (int) (offscreenYOffset * 0.9f);
209                mTmpTransform.rect.offset(0, offset);
210                mTmpTransform.alpha = 0f;
211                mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
212            }
213        }
214    }
215
216    /**
217     * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places
218     * depending on how Recents was triggered.
219     */
220    public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) {
221        RecentsConfiguration config = Recents.getConfiguration();
222        RecentsActivityLaunchState launchState = config.getLaunchState();
223        Resources res = mStackView.getResources();
224        Resources appRes = mStackView.getContext().getApplicationContext().getResources();
225
226        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
227        TaskStackViewScroller stackScroller = mStackView.getScroller();
228        TaskStack stack = mStackView.getStack();
229        Task launchTargetTask = stack.getLaunchTarget();
230
231        // Break early if there are no tasks
232        if (stack.getTaskCount() == 0) {
233            return;
234        }
235
236        final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
237        int taskViewEnterFromAppDuration = res.getInteger(
238                R.integer.recents_task_enter_from_app_duration);
239        int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
240                R.integer.recents_task_enter_from_affiliated_app_duration);
241        int dockGestureAnimDuration = appRes.getInteger(
242                R.integer.long_press_dock_anim_duration);
243
244        // Since low ram devices have an animation when entering app -> recents, do not allow
245        // toggle until the animation is complete
246        if (launchState.launchedFromApp && !launchState.launchedViaDockGesture && isLowRamDevice) {
247            postAnimationTrigger.addLastDecrementRunnable(() -> EventBus.getDefault()
248                .send(new SetWaitingForTransitionStartEvent(false)));
249        }
250
251        // Create enter animations for each of the views from front to back
252        List<TaskView> taskViews = mStackView.getTaskViews();
253        int taskViewCount = taskViews.size();
254        for (int i = taskViewCount - 1; i >= 0; i--) {
255            int taskIndexFromFront = taskViewCount - i - 1;
256            int taskIndexFromBack = i;
257            final TaskView tv = taskViews.get(i);
258            Task task = tv.getTask();
259
260            // Get the current transform for the task, which will be updated to the final transform
261            // to animate to depending on how recents was invoked
262            stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
263                    null);
264
265            if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
266                if (task.isLaunchTarget) {
267                    tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
268                            taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
269                            postAnimationTrigger);
270                }
271
272            } else if (launchState.launchedFromHome) {
273                // Animate the tasks up, but offset the animations to be relative to the front-most
274                // task animation
275                final float startOffsetFraction = (float) (Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS,
276                        taskIndexFromFront) * mEnterAndExitFromHomeTranslationOffset) /
277                        ENTER_FROM_HOME_TRANSLATION_DURATION;
278                AnimationProps taskAnimation = new AnimationProps()
279                        .setInterpolator(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_INTERPOLATOR)
280                        .setListener(postAnimationTrigger.decrementOnAnimationEnd());
281                if (isLowRamDevice) {
282                    taskAnimation.setInterpolator(AnimationProps.BOUNDS,
283                            Interpolators.FAST_OUT_SLOW_IN)
284                            .setDuration(AnimationProps.BOUNDS, 150)
285                            .setDuration(AnimationProps.ALPHA, 150);
286                } else {
287                    taskAnimation.setStartDelay(AnimationProps.ALPHA,
288                                Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) *
289                                        FRAME_OFFSET_MS)
290                            .setInterpolator(AnimationProps.BOUNDS,
291                                new RecentsEntrancePathInterpolator(0f, 0f, 0.2f, 1f,
292                                        startOffsetFraction))
293                            .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION)
294                            .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION);
295                }
296                postAnimationTrigger.increment();
297                mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
298                if (i == taskViewCount - 1) {
299                    tv.onStartFrontTaskEnterAnimation(mStackView.mScreenPinningEnabled);
300                }
301            } else if (launchState.launchedViaDockGesture) {
302                // Animate the tasks up - add some delay to match the divider animation
303                AnimationProps taskAnimation = new AnimationProps()
304                        .setDuration(AnimationProps.BOUNDS, dockGestureAnimDuration +
305                                (taskIndexFromBack * DOUBLE_FRAME_OFFSET_MS))
306                        .setInterpolator(AnimationProps.BOUNDS,
307                                ENTER_WHILE_DOCKING_INTERPOLATOR)
308                        .setStartDelay(AnimationProps.BOUNDS, 48)
309                        .setListener(postAnimationTrigger.decrementOnAnimationEnd());
310                postAnimationTrigger.increment();
311                mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
312            }
313        }
314    }
315
316    /**
317     * Starts an in-app animation to hide all the task views so that we can transition back home.
318     */
319    public void startExitToHomeAnimation(boolean animated,
320            ReferenceCountedTrigger postAnimationTrigger) {
321        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
322        TaskStack stack = mStackView.getStack();
323
324        // Break early if there are no tasks
325        if (stack.getTaskCount() == 0) {
326            return;
327        }
328
329        int offscreenYOffset = stackLayout.mStackRect.height();
330
331        // Create the animations for each of the tasks
332        List<TaskView> taskViews = mStackView.getTaskViews();
333        int taskViewCount = taskViews.size();
334        for (int i = 0; i < taskViewCount; i++) {
335            int taskIndexFromFront = taskViewCount - i - 1;
336            TaskView tv = taskViews.get(i);
337            Task task = tv.getTask();
338
339            if (mStackView.isIgnoredTask(task)) {
340                continue;
341            }
342
343            // Animate the tasks down
344            AnimationProps taskAnimation;
345            if (animated) {
346                int delay = Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS , taskIndexFromFront) *
347                        mEnterAndExitFromHomeTranslationOffset;
348                taskAnimation = new AnimationProps()
349                        .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION)
350                        .setListener(postAnimationTrigger.decrementOnAnimationEnd());
351                if (Recents.getConfiguration().isLowRamDevice) {
352                    taskAnimation.setInterpolator(AnimationProps.BOUNDS,
353                            Interpolators.FAST_OUT_SLOW_IN);
354                } else {
355                    taskAnimation.setStartDelay(AnimationProps.BOUNDS, delay)
356                            .setInterpolator(AnimationProps.BOUNDS,
357                                    EXIT_TO_HOME_TRANSLATION_INTERPOLATOR);
358                }
359                postAnimationTrigger.increment();
360            } else {
361                taskAnimation = AnimationProps.IMMEDIATE;
362            }
363
364            mTmpTransform.fillIn(tv);
365            if (Recents.getConfiguration().isLowRamDevice) {
366                taskAnimation.setInterpolator(AnimationProps.ALPHA,
367                                EXIT_TO_HOME_TRANSLATION_INTERPOLATOR)
368                        .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_TRANSLATION_DURATION);
369                mTmpTransform.rect.offset(0, stackLayout.mTaskStackLowRamLayoutAlgorithm
370                        .getTaskRect().height() / 4);
371                mTmpTransform.alpha = 0f;
372            } else {
373                mTmpTransform.rect.offset(0, offscreenYOffset);
374            }
375            mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
376        }
377    }
378
379    /**
380     * Starts the animation for the launching task view, hiding any tasks that might occlude the
381     * window transition for the launching task.
382     */
383    public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested,
384            final ReferenceCountedTrigger postAnimationTrigger) {
385        Resources res = mStackView.getResources();
386
387        int taskViewExitToAppDuration = res.getInteger(
388                R.integer.recents_task_exit_to_app_duration);
389        int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
390                R.dimen.recents_task_stack_animation_affiliate_enter_offset);
391
392        Task launchingTask = launchingTaskView.getTask();
393        List<TaskView> taskViews = mStackView.getTaskViews();
394        int taskViewCount = taskViews.size();
395        for (int i = 0; i < taskViewCount; i++) {
396            TaskView tv = taskViews.get(i);
397            Task task = tv.getTask();
398
399            if (tv == launchingTaskView) {
400                tv.setClipViewInStack(false);
401                postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
402                    @Override
403                    public void run() {
404                        tv.setClipViewInStack(true);
405                    }
406                });
407                tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration,
408                        screenPinningRequested, postAnimationTrigger);
409            }
410        }
411    }
412
413    /**
414     * Starts the delete animation for the specified {@link TaskView}.
415     */
416    public void startDeleteTaskAnimation(final TaskView deleteTaskView, boolean gridLayout,
417            final ReferenceCountedTrigger postAnimationTrigger) {
418        if (gridLayout) {
419            startTaskGridDeleteTaskAnimation(deleteTaskView, postAnimationTrigger);
420        } else {
421            startTaskStackDeleteTaskAnimation(deleteTaskView, postAnimationTrigger);
422        }
423    }
424
425    /**
426     * Starts the delete animation for all the {@link TaskView}s.
427     */
428    public void startDeleteAllTasksAnimation(final List<TaskView> taskViews, boolean gridLayout,
429            final ReferenceCountedTrigger postAnimationTrigger) {
430        if (gridLayout) {
431            for (int i = 0; i < taskViews.size(); i++) {
432                startTaskGridDeleteTaskAnimation(taskViews.get(i), postAnimationTrigger);
433            }
434        } else {
435            startTaskStackDeleteAllTasksAnimation(taskViews, postAnimationTrigger);
436        }
437    }
438
439    /**
440     * Starts the animation to focus the next {@link TaskView} when paging through recents.
441     *
442     * @return whether or not this will trigger a scroll in the stack
443     */
444    public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask,
445            boolean requestViewFocus) {
446        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
447        TaskStackViewScroller stackScroller = mStackView.getScroller();
448        TaskStack stack = mStackView.getStack();
449
450        final float curScroll = stackScroller.getStackScroll();
451        final float newScroll = stackScroller.getBoundedStackScroll(
452                stackLayout.getStackScrollForTask(newFocusedTask));
453        boolean willScrollToFront = newScroll > curScroll;
454        boolean willScroll = Float.compare(newScroll, curScroll) != 0;
455
456        // Get the current set of task transforms
457        int taskViewCount = mStackView.getTaskViews().size();
458        ArrayList<Task> stackTasks = stack.getStackTasks();
459        mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
460
461        // Pick up the newly visible views after the scroll
462        mStackView.bindVisibleTaskViews(newScroll);
463
464        // Update the internal state
465        stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
466        stackScroller.setStackScroll(newScroll, null /* animation */);
467        mStackView.cancelDeferredTaskViewLayoutAnimation();
468
469        // Get the final set of task transforms
470        mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
471                true /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
472
473        // Focus the task view
474        TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask);
475        if (newFocusedTaskView == null) {
476            // Log the error if we have no task view, and skip the animation
477            Log.e("TaskStackAnimationHelper", "b/27389156 null-task-view prebind:" + taskViewCount +
478                    " postbind:" + mStackView.getTaskViews().size() + " prescroll:" + curScroll +
479                    " postscroll: " + newScroll);
480            return false;
481        }
482        newFocusedTaskView.setFocusedState(true, requestViewFocus);
483
484        // Setup the end listener to return all the hidden views to the view pool after the
485        // focus animation
486        ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger();
487        postAnimTrigger.addLastDecrementRunnable(new Runnable() {
488            @Override
489            public void run() {
490                mStackView.bindVisibleTaskViews(newScroll);
491            }
492        });
493
494        List<TaskView> taskViews = mStackView.getTaskViews();
495        taskViewCount = taskViews.size();
496        int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView);
497        for (int i = 0; i < taskViewCount; i++) {
498            TaskView tv = taskViews.get(i);
499            Task task = tv.getTask();
500
501            if (mStackView.isIgnoredTask(task)) {
502                continue;
503            }
504
505            int taskIndex = stackTasks.indexOf(task);
506            TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
507            TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
508
509            // Update the task to the initial state (for the newly picked up tasks)
510            mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
511
512            int duration;
513            Interpolator interpolator;
514            if (willScrollToFront) {
515                duration = calculateStaggeredAnimDuration(i);
516                interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
517            } else {
518                if (i < newFocusTaskViewIndex) {
519                    duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50);
520                    interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
521                } else if (i > newFocusTaskViewIndex) {
522                    duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50));
523                    interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR;
524                } else {
525                    duration = 200;
526                    interpolator = FOCUS_NEXT_TASK_INTERPOLATOR;
527                }
528            }
529
530            AnimationProps anim = new AnimationProps()
531                    .setDuration(AnimationProps.BOUNDS, duration)
532                    .setInterpolator(AnimationProps.BOUNDS, interpolator)
533                    .setListener(postAnimTrigger.decrementOnAnimationEnd());
534            postAnimTrigger.increment();
535            mStackView.updateTaskViewToTransform(tv, toTransform, anim);
536        }
537        return willScroll;
538    }
539
540    /**
541     * Starts the animation to go to the initial stack layout with a task focused.  In addition, the
542     * previous task will be animated in after the scroll completes.
543     */
544    public void startNewStackScrollAnimation(TaskStack newStack,
545            ReferenceCountedTrigger animationTrigger) {
546        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
547        TaskStackViewScroller stackScroller = mStackView.getScroller();
548
549        // Get the current set of task transforms
550        ArrayList<Task> stackTasks = newStack.getStackTasks();
551        mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
552
553        // Update the stack
554        mStackView.setTasks(newStack, false /* allowNotifyStackChanges */);
555        mStackView.updateLayoutAlgorithm(false /* boundScroll */);
556
557        // Pick up the newly visible views after the scroll
558        final float newScroll = stackLayout.mInitialScrollP;
559        mStackView.bindVisibleTaskViews(newScroll);
560
561        // Update the internal state
562        stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
563        stackLayout.setTaskOverridesForInitialState(newStack, true /* ignoreScrollToFront */);
564        stackScroller.setStackScroll(newScroll);
565        mStackView.cancelDeferredTaskViewLayoutAnimation();
566
567        // Get the final set of task transforms
568        mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
569                false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
570
571        // Hide the front most task view until the scroll is complete
572        Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */);
573        final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
574        final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
575                stackTasks.indexOf(frontMostTask));
576        if (frontMostTaskView != null) {
577            mStackView.updateTaskViewToTransform(frontMostTaskView,
578                    stackLayout.getFrontOfStackTransform(), AnimationProps.IMMEDIATE);
579        }
580
581        // Setup the end listener to return all the hidden views to the view pool after the
582        // focus animation
583        animationTrigger.addLastDecrementRunnable(new Runnable() {
584            @Override
585            public void run() {
586                mStackView.bindVisibleTaskViews(newScroll);
587
588                // Now, animate in the front-most task
589                if (frontMostTaskView != null) {
590                    mStackView.updateTaskViewToTransform(frontMostTaskView, frontMostTransform,
591                            new AnimationProps(75, 250, FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR));
592                }
593            }
594        });
595
596        List<TaskView> taskViews = mStackView.getTaskViews();
597        int taskViewCount = taskViews.size();
598        for (int i = 0; i < taskViewCount; i++) {
599            TaskView tv = taskViews.get(i);
600            Task task = tv.getTask();
601
602            if (mStackView.isIgnoredTask(task)) {
603                continue;
604            }
605            if (task == frontMostTask && frontMostTaskView != null) {
606                continue;
607            }
608
609            int taskIndex = stackTasks.indexOf(task);
610            TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
611            TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
612
613            // Update the task to the initial state (for the newly picked up tasks)
614            mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
615
616            int duration = calculateStaggeredAnimDuration(i);
617            Interpolator interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
618
619            AnimationProps anim = new AnimationProps()
620                    .setDuration(AnimationProps.BOUNDS, duration)
621                    .setInterpolator(AnimationProps.BOUNDS, interpolator)
622                    .setListener(animationTrigger.decrementOnAnimationEnd());
623            animationTrigger.increment();
624            mStackView.updateTaskViewToTransform(tv, toTransform, anim);
625        }
626    }
627
628    /**
629     * Caclulates a staggered duration for {@link #startScrollToFocusedTaskAnimation} and
630     * {@link #startNewStackScrollAnimation}.
631     */
632    private int calculateStaggeredAnimDuration(int i) {
633        return Math.max(100, 100 + ((i - 1) * 50));
634    }
635
636    private void startTaskGridDeleteTaskAnimation(final TaskView deleteTaskView,
637            final ReferenceCountedTrigger postAnimationTrigger) {
638        postAnimationTrigger.increment();
639        postAnimationTrigger.addLastDecrementRunnable(() -> {
640            mStackView.getTouchHandler().onChildDismissed(deleteTaskView);
641        });
642        deleteTaskView.animate().setDuration(300).scaleX(0.9f).scaleY(0.9f).alpha(0).setListener(
643                new AnimatorListenerAdapter() {
644                    @Override
645                    public void onAnimationEnd(Animator animation) {
646                        postAnimationTrigger.decrement();
647                    }}).start();
648    }
649
650    private void startTaskStackDeleteTaskAnimation(final TaskView deleteTaskView,
651            final ReferenceCountedTrigger postAnimationTrigger) {
652        TaskStackViewTouchHandler touchHandler = mStackView.getTouchHandler();
653        touchHandler.onBeginManualDrag(deleteTaskView);
654
655        postAnimationTrigger.increment();
656        postAnimationTrigger.addLastDecrementRunnable(() -> {
657            touchHandler.onChildDismissed(deleteTaskView);
658        });
659
660        final float dismissSize = touchHandler.getScaledDismissSize();
661        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
662        animator.setDuration(400);
663        animator.addUpdateListener((animation) -> {
664            float progress = (Float) animation.getAnimatedValue();
665            deleteTaskView.setTranslationX(progress * dismissSize);
666            touchHandler.updateSwipeProgress(deleteTaskView, true, progress);
667        });
668        animator.addListener(new AnimatorListenerAdapter() {
669            @Override
670            public void onAnimationEnd(Animator animation) {
671                postAnimationTrigger.decrement();
672            }
673        });
674        animator.start();
675    }
676
677    private void startTaskStackDeleteAllTasksAnimation(final List<TaskView> taskViews,
678            final ReferenceCountedTrigger postAnimationTrigger) {
679        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
680
681        int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.getTaskRect().left;
682
683        int taskViewCount = taskViews.size();
684        for (int i = taskViewCount - 1; i >= 0; i--) {
685            TaskView tv = taskViews.get(i);
686            int taskIndexFromFront = taskViewCount - i - 1;
687            int startDelay = taskIndexFromFront * DOUBLE_FRAME_OFFSET_MS;
688
689            // Disabling clipping with the stack while the view is animating away
690            tv.setClipViewInStack(false);
691
692            // Compose the new animation and transform and star the animation
693            AnimationProps taskAnimation = new AnimationProps(startDelay,
694                    DISMISS_ALL_TASKS_DURATION, DISMISS_ALL_TRANSLATION_INTERPOLATOR,
695                    new AnimatorListenerAdapter() {
696                        @Override
697                        public void onAnimationEnd(Animator animation) {
698                            postAnimationTrigger.decrement();
699
700                            // Re-enable clipping with the stack (we will reuse this view)
701                            tv.setClipViewInStack(true);
702                        }
703                    });
704            postAnimationTrigger.increment();
705
706            mTmpTransform.fillIn(tv);
707            mTmpTransform.rect.offset(offscreenXOffset, 0);
708            mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
709        }
710    }
711}
712