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