• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
onPrepareLaunchTargetForEnterAnimation()56         void onPrepareLaunchTargetForEnterAnimation();
57 
58         /**
59          * Callback to start the animation for the launch target {@link TaskView}.
60          */
onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration, boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger)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          */
onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested, ReferenceCountedTrigger postAnimationTrigger)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          */
onStartFrontTaskEnterAnimation(boolean screenPinningEnabled)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 
TaskStackAnimationHelper(Context context, TaskStackView stackView)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      */
prepareForEnterAnimation()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      */
startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger)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      */
startExitToHomeAnimation(boolean animated, ReferenceCountedTrigger postAnimationTrigger)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      */
startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested, final ReferenceCountedTrigger postAnimationTrigger)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      */
startDeleteTaskAnimation(final TaskView deleteTaskView, boolean gridLayout, final ReferenceCountedTrigger postAnimationTrigger)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      */
startDeleteAllTasksAnimation(final List<TaskView> taskViews, boolean gridLayout, final ReferenceCountedTrigger postAnimationTrigger)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      */
startScrollToFocusedTaskAnimation(Task newFocusedTask, boolean requestViewFocus)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      */
startNewStackScrollAnimation(TaskStack newStack, ReferenceCountedTrigger animationTrigger)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      */
calculateStaggeredAnimDuration(int i)611     private int calculateStaggeredAnimDuration(int i) {
612         return Math.max(100, 100 + ((i - 1) * 50));
613     }
614 
startTaskGridDeleteTaskAnimation(final TaskView deleteTaskView, final ReferenceCountedTrigger postAnimationTrigger)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 
startTaskStackDeleteTaskAnimation(final TaskView deleteTaskView, final ReferenceCountedTrigger postAnimationTrigger)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 
startTaskStackDeleteAllTasksAnimation(final List<TaskView> taskViews, final ReferenceCountedTrigger postAnimationTrigger)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