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