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_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,
    394             final ReferenceCountedTrigger postAnimationTrigger) {
    395         TaskStackViewTouchHandler touchHandler = mStackView.getTouchHandler();
    396         touchHandler.onBeginManualDrag(deleteTaskView);
    397 
    398         postAnimationTrigger.increment();
    399         postAnimationTrigger.addLastDecrementRunnable(() -> {
    400             touchHandler.onChildDismissed(deleteTaskView);
    401         });
    402 
    403         final float dismissSize = touchHandler.getScaledDismissSize();
    404         ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
    405         animator.setDuration(400);
    406         animator.addUpdateListener((animation) -> {
    407             float progress = (Float) animation.getAnimatedValue();
    408             deleteTaskView.setTranslationX(progress * dismissSize);
    409             touchHandler.updateSwipeProgress(deleteTaskView, true, progress);
    410         });
    411         animator.addListener(new AnimatorListenerAdapter() {
    412             @Override
    413             public void onAnimationEnd(Animator animation) {
    414                 postAnimationTrigger.decrement();
    415             }
    416         });
    417         animator.start();
    418     }
    419 
    420     /**
    421      * Starts the delete animation for all the {@link TaskView}s.
    422      */
    423     public void startDeleteAllTasksAnimation(final List<TaskView> taskViews,
    424                                              final ReferenceCountedTrigger postAnimationTrigger) {
    425         TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
    426 
    427         int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.mTaskRect.left;
    428 
    429         int taskViewCount = taskViews.size();
    430         for (int i = taskViewCount - 1; i >= 0; i--) {
    431             TaskView tv = taskViews.get(i);
    432             int taskIndexFromFront = taskViewCount - i - 1;
    433             int startDelay = taskIndexFromFront * DOUBLE_FRAME_OFFSET_MS;
    434 
    435             // Disabling clipping with the stack while the view is animating away
    436             tv.setClipViewInStack(false);
    437 
    438             // Compose the new animation and transform and star the animation
    439             AnimationProps taskAnimation = new AnimationProps(startDelay,
    440                     DISMISS_ALL_TASKS_DURATION, DISMISS_ALL_TRANSLATION_INTERPOLATOR,
    441                     new AnimatorListenerAdapter() {
    442                 @Override
    443                 public void onAnimationEnd(Animator animation) {
    444                     postAnimationTrigger.decrement();
    445 
    446                     // Re-enable clipping with the stack (we will reuse this view)
    447                     tv.setClipViewInStack(true);
    448                 }
    449             });
    450             postAnimationTrigger.increment();
    451 
    452             mTmpTransform.fillIn(tv);
    453             mTmpTransform.rect.offset(offscreenXOffset, 0);
    454             mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
    455         }
    456     }
    457 
    458     /**
    459      * Starts the animation to focus the next {@link TaskView} when paging through recents.
    460      *
    461      * @return whether or not this will trigger a scroll in the stack
    462      */
    463     public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask,
    464             boolean requestViewFocus) {
    465         TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
    466         TaskStackViewScroller stackScroller = mStackView.getScroller();
    467         TaskStack stack = mStackView.getStack();
    468 
    469         final float curScroll = stackScroller.getStackScroll();
    470         final float newScroll = stackScroller.getBoundedStackScroll(
    471                 stackLayout.getStackScrollForTask(newFocusedTask));
    472         boolean willScrollToFront = newScroll > curScroll;
    473         boolean willScroll = Float.compare(newScroll, curScroll) != 0;
    474 
    475         // Get the current set of task transforms
    476         int taskViewCount = mStackView.getTaskViews().size();
    477         ArrayList<Task> stackTasks = stack.getStackTasks();
    478         mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
    479 
    480         // Pick up the newly visible views after the scroll
    481         mStackView.bindVisibleTaskViews(newScroll);
    482 
    483         // Update the internal state
    484         stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
    485         stackScroller.setStackScroll(newScroll, null /* animation */);
    486         mStackView.cancelDeferredTaskViewLayoutAnimation();
    487 
    488         // Get the final set of task transforms
    489         mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
    490                 true /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
    491 
    492         // Focus the task view
    493         TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask);
    494         if (newFocusedTaskView == null) {
    495             // Log the error if we have no task view, and skip the animation
    496             Log.e("TaskStackAnimationHelper", "b/27389156 null-task-view prebind:" + taskViewCount +
    497                     " postbind:" + mStackView.getTaskViews().size() + " prescroll:" + curScroll +
    498                     " postscroll: " + newScroll);
    499             return false;
    500         }
    501         newFocusedTaskView.setFocusedState(true, requestViewFocus);
    502 
    503         // Setup the end listener to return all the hidden views to the view pool after the
    504         // focus animation
    505         ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger();
    506         postAnimTrigger.addLastDecrementRunnable(new Runnable() {
    507             @Override
    508             public void run() {
    509                 mStackView.bindVisibleTaskViews(newScroll);
    510             }
    511         });
    512 
    513         List<TaskView> taskViews = mStackView.getTaskViews();
    514         taskViewCount = taskViews.size();
    515         int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView);
    516         for (int i = 0; i < taskViewCount; i++) {
    517             TaskView tv = taskViews.get(i);
    518             Task task = tv.getTask();
    519 
    520             if (mStackView.isIgnoredTask(task)) {
    521                 continue;
    522             }
    523 
    524             int taskIndex = stackTasks.indexOf(task);
    525             TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
    526             TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
    527 
    528             // Update the task to the initial state (for the newly picked up tasks)
    529             mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
    530 
    531             int duration;
    532             Interpolator interpolator;
    533             if (willScrollToFront) {
    534                 duration = calculateStaggeredAnimDuration(i);
    535                 interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
    536             } else {
    537                 if (i < newFocusTaskViewIndex) {
    538                     duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50);
    539                     interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
    540                 } else if (i > newFocusTaskViewIndex) {
    541                     duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50));
    542                     interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR;
    543                 } else {
    544                     duration = 200;
    545                     interpolator = FOCUS_NEXT_TASK_INTERPOLATOR;
    546                 }
    547             }
    548 
    549             AnimationProps anim = new AnimationProps()
    550                     .setDuration(AnimationProps.BOUNDS, duration)
    551                     .setInterpolator(AnimationProps.BOUNDS, interpolator)
    552                     .setListener(postAnimTrigger.decrementOnAnimationEnd());
    553             postAnimTrigger.increment();
    554             mStackView.updateTaskViewToTransform(tv, toTransform, anim);
    555         }
    556         return willScroll;
    557     }
    558 
    559     /**
    560      * Starts the animation to go to the initial stack layout with a task focused.  In addition, the
    561      * previous task will be animated in after the scroll completes.
    562      */
    563     public void startNewStackScrollAnimation(TaskStack newStack,
    564             ReferenceCountedTrigger animationTrigger) {
    565         TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
    566         TaskStackViewScroller stackScroller = mStackView.getScroller();
    567 
    568         // Get the current set of task transforms
    569         ArrayList<Task> stackTasks = newStack.getStackTasks();
    570         mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
    571 
    572         // Update the stack
    573         mStackView.setTasks(newStack, false /* allowNotifyStackChanges */);
    574         mStackView.updateLayoutAlgorithm(false /* boundScroll */);
    575 
    576         // Pick up the newly visible views after the scroll
    577         final float newScroll = stackLayout.mInitialScrollP;
    578         mStackView.bindVisibleTaskViews(newScroll);
    579 
    580         // Update the internal state
    581         stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
    582         stackLayout.setTaskOverridesForInitialState(newStack, true /* ignoreScrollToFront */);
    583         stackScroller.setStackScroll(newScroll);
    584         mStackView.cancelDeferredTaskViewLayoutAnimation();
    585 
    586         // Get the final set of task transforms
    587         mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
    588                 false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
    589 
    590         // Hide the front most task view until the scroll is complete
    591         Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */);
    592         final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
    593         final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
    594                 stackTasks.indexOf(frontMostTask));
    595         if (frontMostTaskView != null) {
    596             mStackView.updateTaskViewToTransform(frontMostTaskView,
    597                     stackLayout.getFrontOfStackTransform(), AnimationProps.IMMEDIATE);
    598         }
    599 
    600         // Setup the end listener to return all the hidden views to the view pool after the
    601         // focus animation
    602         animationTrigger.addLastDecrementRunnable(new Runnable() {
    603             @Override
    604             public void run() {
    605                 mStackView.bindVisibleTaskViews(newScroll);
    606 
    607                 // Now, animate in the front-most task
    608                 if (frontMostTaskView != null) {
    609                     mStackView.updateTaskViewToTransform(frontMostTaskView, frontMostTransform,
    610                             new AnimationProps(75, 250, FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR));
    611                 }
    612             }
    613         });
    614 
    615         List<TaskView> taskViews = mStackView.getTaskViews();
    616         int taskViewCount = taskViews.size();
    617         for (int i = 0; i < taskViewCount; i++) {
    618             TaskView tv = taskViews.get(i);
    619             Task task = tv.getTask();
    620 
    621             if (mStackView.isIgnoredTask(task)) {
    622                 continue;
    623             }
    624             if (task == frontMostTask && frontMostTaskView != null) {
    625                 continue;
    626             }
    627 
    628             int taskIndex = stackTasks.indexOf(task);
    629             TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
    630             TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
    631 
    632             // Update the task to the initial state (for the newly picked up tasks)
    633             mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
    634 
    635             int duration = calculateStaggeredAnimDuration(i);
    636             Interpolator interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
    637 
    638             AnimationProps anim = new AnimationProps()
    639                     .setDuration(AnimationProps.BOUNDS, duration)
    640                     .setInterpolator(AnimationProps.BOUNDS, interpolator)
    641                     .setListener(animationTrigger.decrementOnAnimationEnd());
    642             animationTrigger.increment();
    643             mStackView.updateTaskViewToTransform(tv, toTransform, anim);
    644         }
    645     }
    646 
    647     /**
    648      * Caclulates a staggered duration for {@link #startScrollToFocusedTaskAnimation} and
    649      * {@link #startNewStackScrollAnimation}.
    650      */
    651     private int calculateStaggeredAnimDuration(int i) {
    652         return Math.max(100, 100 + ((i - 1) * 50));
    653     }
    654 }
    655