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