blob: 81bf6affc94a17b93b497ec641da50743f970fc4 [file] [log] [blame]
Winsonf24f2162016-01-05 12:11:55 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Winson3f32e7e2016-04-20 17:18:08 -070021import android.animation.TimeInterpolator;
Winsonaa0dea72016-04-28 10:59:30 -070022import android.animation.ValueAnimator;
Winsonf24f2162016-01-05 12:11:55 -080023import android.content.Context;
Jorim Jaggi545c5c22016-04-12 18:59:45 -070024import android.content.res.Configuration;
Winsonf24f2162016-01-05 12:11:55 -080025import android.content.res.Resources;
Winson3f3d7442016-03-01 10:07:04 -080026import android.util.Log;
Winsonf24f2162016-01-05 12:11:55 -080027import android.view.View;
Winson05e46ca2016-02-05 15:40:29 -080028import android.view.animation.Interpolator;
Winson50448632016-02-01 18:04:59 -080029import android.view.animation.PathInterpolator;
Winsonc0d70582016-01-29 10:24:39 -080030
31import com.android.systemui.Interpolators;
Winsonf24f2162016-01-05 12:11:55 -080032import com.android.systemui.R;
33import com.android.systemui.recents.Recents;
34import com.android.systemui.recents.RecentsActivityLaunchState;
35import com.android.systemui.recents.RecentsConfiguration;
Matthew Ng43db6d22017-06-27 15:29:39 -070036import com.android.systemui.recents.RecentsDebugFlags;
Matthew Ngc57b7f62017-08-10 15:37:19 -070037import com.android.systemui.recents.events.EventBus;
38import com.android.systemui.recents.events.component.SetWaitingForTransitionStartEvent;
Winsonf24f2162016-01-05 12:11:55 -080039import com.android.systemui.recents.misc.ReferenceCountedTrigger;
40import com.android.systemui.recents.model.Task;
41import com.android.systemui.recents.model.TaskStack;
Matthew Ng43db6d22017-06-27 15:29:39 -070042import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
Winsonf24f2162016-01-05 12:11:55 -080043
Winson05e46ca2016-02-05 15:40:29 -080044import java.util.ArrayList;
Winsonf24f2162016-01-05 12:11:55 -080045import java.util.List;
46
47/**
48 * A helper class to create task view animations for {@link TaskView}s in a {@link TaskStackView},
49 * but not the contents of the {@link TaskView}s.
50 */
51public class TaskStackAnimationHelper {
52
53 /**
54 * Callbacks from the helper to coordinate view-content animations with view animations.
55 */
56 public interface Callbacks {
57 /**
58 * Callback to prepare for the start animation for the launch target {@link TaskView}.
59 */
60 void onPrepareLaunchTargetForEnterAnimation();
61
62 /**
63 * Callback to start the animation for the launch target {@link TaskView}.
64 */
Winsone693aaf2016-03-01 12:05:59 -080065 void onStartLaunchTargetEnterAnimation(TaskViewTransform transform, int duration,
66 boolean screenPinningEnabled, ReferenceCountedTrigger postAnimationTrigger);
Winsonf24f2162016-01-05 12:11:55 -080067
68 /**
69 * Callback to start the animation for the launch target {@link TaskView} when it is
70 * launched from Recents.
71 */
72 void onStartLaunchTargetLaunchAnimation(int duration, boolean screenPinningRequested,
73 ReferenceCountedTrigger postAnimationTrigger);
Winson59924fe2016-03-17 14:13:18 -070074
75 /**
76 * Callback to start the animation for the front {@link TaskView} if there is no launch
77 * target.
78 */
79 void onStartFrontTaskEnterAnimation(boolean screenPinningEnabled);
Winsonf24f2162016-01-05 12:11:55 -080080 }
81
Winson3f32e7e2016-04-20 17:18:08 -070082 private static final int DOUBLE_FRAME_OFFSET_MS = 33;
Winson50448632016-02-01 18:04:59 -080083 private static final int FRAME_OFFSET_MS = 16;
84
Winson3f32e7e2016-04-20 17:18:08 -070085 private static final int ENTER_EXIT_NUM_ANIMATING_TASKS = 5;
Jorim Jaggi899327f2016-02-25 20:44:18 -050086
Winson3f32e7e2016-04-20 17:18:08 -070087 private static final int ENTER_FROM_HOME_ALPHA_DURATION = 100;
88 public static final int ENTER_FROM_HOME_TRANSLATION_DURATION = 300;
Winson3f32e7e2016-04-20 17:18:08 -070089 private static final Interpolator ENTER_FROM_HOME_ALPHA_INTERPOLATOR = Interpolators.LINEAR;
Winson50448632016-02-01 18:04:59 -080090
Winson3f32e7e2016-04-20 17:18:08 -070091 public static final int EXIT_TO_HOME_TRANSLATION_DURATION = 200;
92 private static final Interpolator EXIT_TO_HOME_TRANSLATION_INTERPOLATOR =
93 new PathInterpolator(0.4f, 0, 0.6f, 1f);
94
95 private static final int DISMISS_TASK_DURATION = 175;
96 private static final int DISMISS_ALL_TASKS_DURATION = 200;
97 private static final Interpolator DISMISS_ALL_TRANSLATION_INTERPOLATOR =
Winson50448632016-02-01 18:04:59 -080098 new PathInterpolator(0.4f, 0, 1f, 1f);
99
Winson3f32e7e2016-04-20 17:18:08 -0700100 private static final Interpolator FOCUS_NEXT_TASK_INTERPOLATOR =
Winson05e46ca2016-02-05 15:40:29 -0800101 new PathInterpolator(0.4f, 0, 0, 1f);
Winson3f32e7e2016-04-20 17:18:08 -0700102 private static final Interpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR =
Winson05e46ca2016-02-05 15:40:29 -0800103 new PathInterpolator(0, 0, 0, 1f);
Winson3f32e7e2016-04-20 17:18:08 -0700104 private static final Interpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR =
105 Interpolators.LINEAR_OUT_SLOW_IN;
Winson05e46ca2016-02-05 15:40:29 -0800106
Winson3f32e7e2016-04-20 17:18:08 -0700107 private static final Interpolator ENTER_WHILE_DOCKING_INTERPOLATOR =
108 Interpolators.LINEAR_OUT_SLOW_IN;
Jorim Jaggi899327f2016-02-25 20:44:18 -0500109
Manu Cornet45c80482016-12-20 18:42:16 -0800110 private final int mEnterAndExitFromHomeTranslationOffset;
Winsonf24f2162016-01-05 12:11:55 -0800111 private TaskStackView mStackView;
112
Winsonf24f2162016-01-05 12:11:55 -0800113 private TaskViewTransform mTmpTransform = new TaskViewTransform();
Winson05e46ca2016-02-05 15:40:29 -0800114 private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>();
115 private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>();
Winsonf24f2162016-01-05 12:11:55 -0800116
117 public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
118 mStackView = stackView;
Manu Cornet45c80482016-12-20 18:42:16 -0800119 mEnterAndExitFromHomeTranslationOffset = Recents.getConfiguration().isGridEnabled
120 ? 0 : DOUBLE_FRAME_OFFSET_MS;
Winsonf24f2162016-01-05 12:11:55 -0800121 }
122
123 /**
124 * Prepares the stack views and puts them in their initial animation state while visible, before
125 * the in-app enter animations start (after the window-transition completes).
126 */
127 public void prepareForEnterAnimation() {
128 RecentsConfiguration config = Recents.getConfiguration();
129 RecentsActivityLaunchState launchState = config.getLaunchState();
130 Resources res = mStackView.getResources();
Jorim Jaggie370e152016-04-15 14:13:33 -0700131 Resources appResources = mStackView.getContext().getApplicationContext().getResources();
Winsonf24f2162016-01-05 12:11:55 -0800132
133 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
134 TaskStackViewScroller stackScroller = mStackView.getScroller();
135 TaskStack stack = mStackView.getStack();
136 Task launchTargetTask = stack.getLaunchTarget();
137
138 // Break early if there are no tasks
Winson4b057c62016-01-12 15:01:52 -0800139 if (stack.getTaskCount() == 0) {
Winsonf24f2162016-01-05 12:11:55 -0800140 return;
141 }
142
Winson40a22732016-02-02 18:07:00 -0800143 int offscreenYOffset = stackLayout.mStackRect.height();
Winsonf24f2162016-01-05 12:11:55 -0800144 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
Winson59924fe2016-03-17 14:13:18 -0700145 R.dimen.recents_task_stack_animation_affiliate_enter_offset);
Jorim Jaggi899327f2016-02-25 20:44:18 -0500146 int launchedWhileDockingOffset = res.getDimensionPixelSize(
Winson59924fe2016-03-17 14:13:18 -0700147 R.dimen.recents_task_stack_animation_launched_while_docking_offset);
Jorim Jaggie370e152016-04-15 14:13:33 -0700148 boolean isLandscape = appResources.getConfiguration().orientation
149 == Configuration.ORIENTATION_LANDSCAPE;
Winsonf24f2162016-01-05 12:11:55 -0800150
Matthew Ng43db6d22017-06-27 15:29:39 -0700151 float top = 0;
152 final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
153 if (isLowRamDevice && launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
154 stackLayout.getStackTransform(launchTargetTask, stackScroller.getStackScroll(),
155 mTmpTransform, null /* frontTransform */);
156 top = mTmpTransform.rect.top;
157 }
158
Winsonf24f2162016-01-05 12:11:55 -0800159 // Prepare each of the task views for their enter animation from front to back
160 List<TaskView> taskViews = mStackView.getTaskViews();
161 for (int i = taskViews.size() - 1; i >= 0; i--) {
162 TaskView tv = taskViews.get(i);
163 Task task = tv.getTask();
Winson8a3ef372016-05-26 10:46:11 -0700164 boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
165 launchTargetTask.group != null &&
166 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
167 boolean hideTask = launchTargetTask != null &&
168 launchTargetTask.isFreeformTask() &&
169 task.isFreeformTask();
Winsonf24f2162016-01-05 12:11:55 -0800170
171 // Get the current transform for the task, which will be used to position it offscreen
172 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
Jiaquan He26f637b2016-12-27 14:44:14 -0800173 null);
Winsonf24f2162016-01-05 12:11:55 -0800174
175 if (hideTask) {
176 tv.setVisibility(View.INVISIBLE);
Winsonc69249f2016-03-28 13:38:39 -0700177 } else if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
Winsonf24f2162016-01-05 12:11:55 -0800178 if (task.isLaunchTarget) {
179 tv.onPrepareLaunchTargetForEnterAnimation();
Matthew Ng43db6d22017-06-27 15:29:39 -0700180 } else if (isLowRamDevice && i >= taskViews.size() -
181 (TaskStackLowRamLayoutAlgorithm.MAX_LAYOUT_TASK_COUNT + 1)
182 && !RecentsDebugFlags.Static.DisableRecentsLowRamEnterExitAnimation) {
183 // Move the last 2nd and 3rd last tasks in-app animation to match the motion of
184 // the last task's app transition
185 stackLayout.getStackTransform(task, stackScroller.getStackScroll(),
186 mTmpTransform, null);
187 mTmpTransform.rect.offset(0, -top);
188 mTmpTransform.alpha = 0f;
189 mStackView.updateTaskViewToTransform(tv, mTmpTransform,
190 AnimationProps.IMMEDIATE);
191 stackLayout.getStackTransform(task, stackScroller.getStackScroll(),
192 mTmpTransform, null);
193 mTmpTransform.alpha = 1f;
194 // Duration see {@link
195 // com.android.server.wm.AppTransition#DEFAULT_APP_TRANSITION_DURATION}
196 mStackView.updateTaskViewToTransform(tv, mTmpTransform,
197 new AnimationProps(336, Interpolators.FAST_OUT_SLOW_IN));
Winsonf24f2162016-01-05 12:11:55 -0800198 } else if (currentTaskOccludesLaunchTarget) {
199 // Move the task view slightly lower so we can animate it in
Winsonc69249f2016-03-28 13:38:39 -0700200 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
201 mTmpTransform.alpha = 0f;
202 mStackView.updateTaskViewToTransform(tv, mTmpTransform,
203 AnimationProps.IMMEDIATE);
Winson65c851e2016-01-20 12:43:35 -0800204 tv.setClipViewInStack(false);
Winsonf24f2162016-01-05 12:11:55 -0800205 }
206 } else if (launchState.launchedFromHome) {
Matthew Ng43db6d22017-06-27 15:29:39 -0700207 if (isLowRamDevice) {
208 mTmpTransform.rect.offset(0, stackLayout.getTaskRect().height() / 4);
209 } else {
210 // Move the task view off screen (below) so we can animate it in
211 mTmpTransform.rect.offset(0, offscreenYOffset);
212 }
Winsonc69249f2016-03-28 13:38:39 -0700213 mTmpTransform.alpha = 0f;
214 mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
215 } else if (launchState.launchedViaDockGesture) {
Jorim Jaggi545c5c22016-04-12 18:59:45 -0700216 int offset = isLandscape
217 ? launchedWhileDockingOffset
218 : (int) (offscreenYOffset * 0.9f);
219 mTmpTransform.rect.offset(0, offset);
Winsonc69249f2016-03-28 13:38:39 -0700220 mTmpTransform.alpha = 0f;
221 mStackView.updateTaskViewToTransform(tv, mTmpTransform, AnimationProps.IMMEDIATE);
Winsonf24f2162016-01-05 12:11:55 -0800222 }
223 }
224 }
225
226 /**
227 * Starts the in-app enter animation, which animates the {@link TaskView}s to their final places
228 * depending on how Recents was triggered.
229 */
230 public void startEnterAnimation(final ReferenceCountedTrigger postAnimationTrigger) {
231 RecentsConfiguration config = Recents.getConfiguration();
232 RecentsActivityLaunchState launchState = config.getLaunchState();
233 Resources res = mStackView.getResources();
Jorim Jaggie370e152016-04-15 14:13:33 -0700234 Resources appRes = mStackView.getContext().getApplicationContext().getResources();
Winsonf24f2162016-01-05 12:11:55 -0800235
236 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
237 TaskStackViewScroller stackScroller = mStackView.getScroller();
238 TaskStack stack = mStackView.getStack();
239 Task launchTargetTask = stack.getLaunchTarget();
240
241 // Break early if there are no tasks
Winson4b057c62016-01-12 15:01:52 -0800242 if (stack.getTaskCount() == 0) {
Winsonf24f2162016-01-05 12:11:55 -0800243 return;
244 }
245
Matthew Ngc57b7f62017-08-10 15:37:19 -0700246 final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
Winsonf24f2162016-01-05 12:11:55 -0800247 int taskViewEnterFromAppDuration = res.getInteger(
248 R.integer.recents_task_enter_from_app_duration);
Winson65c851e2016-01-20 12:43:35 -0800249 int taskViewEnterFromAffiliatedAppDuration = res.getInteger(
250 R.integer.recents_task_enter_from_affiliated_app_duration);
Jorim Jaggie370e152016-04-15 14:13:33 -0700251 int dockGestureAnimDuration = appRes.getInteger(
252 R.integer.long_press_dock_anim_duration);
Winsonf24f2162016-01-05 12:11:55 -0800253
Matthew Ngc57b7f62017-08-10 15:37:19 -0700254 // Since low ram devices have an animation when entering app -> recents, do not allow
255 // toggle until the animation is complete
256 if (launchState.launchedFromApp && !launchState.launchedViaDockGesture && isLowRamDevice) {
257 postAnimationTrigger.addLastDecrementRunnable(() -> EventBus.getDefault()
258 .send(new SetWaitingForTransitionStartEvent(false)));
259 }
260
Winsonf24f2162016-01-05 12:11:55 -0800261 // Create enter animations for each of the views from front to back
262 List<TaskView> taskViews = mStackView.getTaskViews();
263 int taskViewCount = taskViews.size();
264 for (int i = taskViewCount - 1; i >= 0; i--) {
Winson50448632016-02-01 18:04:59 -0800265 int taskIndexFromFront = taskViewCount - i - 1;
Jorim Jaggi899327f2016-02-25 20:44:18 -0500266 int taskIndexFromBack = i;
Winson65c851e2016-01-20 12:43:35 -0800267 final TaskView tv = taskViews.get(i);
Winsonf24f2162016-01-05 12:11:55 -0800268 Task task = tv.getTask();
Winson8a3ef372016-05-26 10:46:11 -0700269 boolean currentTaskOccludesLaunchTarget = launchTargetTask != null &&
270 launchTargetTask.group != null &&
271 launchTargetTask.group.isTaskAboveTask(task, launchTargetTask);
Winsonf24f2162016-01-05 12:11:55 -0800272
273 // Get the current transform for the task, which will be updated to the final transform
274 // to animate to depending on how recents was invoked
275 stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
Jiaquan He26f637b2016-12-27 14:44:14 -0800276 null);
Winsonf24f2162016-01-05 12:11:55 -0800277
Winsonc69249f2016-03-28 13:38:39 -0700278 if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
Winsonf24f2162016-01-05 12:11:55 -0800279 if (task.isLaunchTarget) {
Winsone693aaf2016-03-01 12:05:59 -0800280 tv.onStartLaunchTargetEnterAnimation(mTmpTransform,
281 taskViewEnterFromAppDuration, mStackView.mScreenPinningEnabled,
282 postAnimationTrigger);
Winsonf24f2162016-01-05 12:11:55 -0800283 } else {
284 // Animate the task up if it was occluding the launch target
285 if (currentTaskOccludesLaunchTarget) {
Winsonbe8e6962016-02-01 14:27:52 -0800286 AnimationProps taskAnimation = new AnimationProps(
Winsonc0d70582016-01-29 10:24:39 -0800287 taskViewEnterFromAffiliatedAppDuration, Interpolators.ALPHA_IN,
Winson65c851e2016-01-20 12:43:35 -0800288 new AnimatorListenerAdapter() {
289 @Override
290 public void onAnimationEnd(Animator animation) {
291 postAnimationTrigger.decrement();
Winson59924fe2016-03-17 14:13:18 -0700292 tv.setClipViewInStack(true);
Winson65c851e2016-01-20 12:43:35 -0800293 }
294 });
Winsonf24f2162016-01-05 12:11:55 -0800295 postAnimationTrigger.increment();
296 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
297 }
298 }
299
300 } else if (launchState.launchedFromHome) {
Winson3f32e7e2016-04-20 17:18:08 -0700301 // Animate the tasks up, but offset the animations to be relative to the front-most
302 // task animation
Matthew Nged85be92017-06-02 12:46:33 -0700303 final float startOffsetFraction = (float) (Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS,
304 taskIndexFromFront) * mEnterAndExitFromHomeTranslationOffset) /
305 ENTER_FROM_HOME_TRANSLATION_DURATION;
Winson50448632016-02-01 18:04:59 -0800306 AnimationProps taskAnimation = new AnimationProps()
Winson3f32e7e2016-04-20 17:18:08 -0700307 .setInterpolator(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_INTERPOLATOR)
Winson50448632016-02-01 18:04:59 -0800308 .setListener(postAnimationTrigger.decrementOnAnimationEnd());
Matthew Ngc57b7f62017-08-10 15:37:19 -0700309 if (isLowRamDevice) {
Matthew Ng43db6d22017-06-27 15:29:39 -0700310 taskAnimation.setInterpolator(AnimationProps.BOUNDS,
311 Interpolators.FAST_OUT_SLOW_IN)
312 .setDuration(AnimationProps.BOUNDS, 150)
313 .setDuration(AnimationProps.ALPHA, 150);
314 } else {
315 taskAnimation.setStartDelay(AnimationProps.ALPHA,
316 Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS, taskIndexFromFront) *
317 FRAME_OFFSET_MS)
318 .setInterpolator(AnimationProps.BOUNDS,
319 new RecentsEntrancePathInterpolator(0f, 0f, 0.2f, 1f,
320 startOffsetFraction))
321 .setDuration(AnimationProps.BOUNDS, ENTER_FROM_HOME_TRANSLATION_DURATION)
322 .setDuration(AnimationProps.ALPHA, ENTER_FROM_HOME_ALPHA_DURATION);
323 }
Winsonf24f2162016-01-05 12:11:55 -0800324 postAnimationTrigger.increment();
325 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
Winson59924fe2016-03-17 14:13:18 -0700326 if (i == taskViewCount - 1) {
327 tv.onStartFrontTaskEnterAnimation(mStackView.mScreenPinningEnabled);
328 }
Winsonc69249f2016-03-28 13:38:39 -0700329 } else if (launchState.launchedViaDockGesture) {
Jorim Jaggic5887ea2016-05-13 18:21:48 -0700330 // Animate the tasks up - add some delay to match the divider animation
Jorim Jaggi899327f2016-02-25 20:44:18 -0500331 AnimationProps taskAnimation = new AnimationProps()
Winson3f32e7e2016-04-20 17:18:08 -0700332 .setDuration(AnimationProps.BOUNDS, dockGestureAnimDuration +
333 (taskIndexFromBack * DOUBLE_FRAME_OFFSET_MS))
Jorim Jaggi899327f2016-02-25 20:44:18 -0500334 .setInterpolator(AnimationProps.BOUNDS,
335 ENTER_WHILE_DOCKING_INTERPOLATOR)
Jorim Jaggic5887ea2016-05-13 18:21:48 -0700336 .setStartDelay(AnimationProps.BOUNDS, 48)
Jorim Jaggi899327f2016-02-25 20:44:18 -0500337 .setListener(postAnimationTrigger.decrementOnAnimationEnd());
338 postAnimationTrigger.increment();
339 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
Winsonf24f2162016-01-05 12:11:55 -0800340 }
341 }
342 }
343
344 /**
345 * Starts an in-app animation to hide all the task views so that we can transition back home.
346 */
347 public void startExitToHomeAnimation(boolean animated,
348 ReferenceCountedTrigger postAnimationTrigger) {
Winsonf24f2162016-01-05 12:11:55 -0800349 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
Winsonf24f2162016-01-05 12:11:55 -0800350 TaskStack stack = mStackView.getStack();
351
352 // Break early if there are no tasks
Winson4b057c62016-01-12 15:01:52 -0800353 if (stack.getTaskCount() == 0) {
Winsonf24f2162016-01-05 12:11:55 -0800354 return;
355 }
356
Winson40a22732016-02-02 18:07:00 -0800357 int offscreenYOffset = stackLayout.mStackRect.height();
Winsonf24f2162016-01-05 12:11:55 -0800358
359 // Create the animations for each of the tasks
360 List<TaskView> taskViews = mStackView.getTaskViews();
361 int taskViewCount = taskViews.size();
362 for (int i = 0; i < taskViewCount; i++) {
Winson50448632016-02-01 18:04:59 -0800363 int taskIndexFromFront = taskViewCount - i - 1;
Winsonf24f2162016-01-05 12:11:55 -0800364 TaskView tv = taskViews.get(i);
Winson61560f02016-05-02 16:36:20 -0700365 Task task = tv.getTask();
366
367 if (mStackView.isIgnoredTask(task)) {
368 continue;
369 }
Winson50448632016-02-01 18:04:59 -0800370
371 // Animate the tasks down
372 AnimationProps taskAnimation;
373 if (animated) {
Winson3f32e7e2016-04-20 17:18:08 -0700374 int delay = Math.min(ENTER_EXIT_NUM_ANIMATING_TASKS , taskIndexFromFront) *
Manu Cornet45c80482016-12-20 18:42:16 -0800375 mEnterAndExitFromHomeTranslationOffset;
Winson50448632016-02-01 18:04:59 -0800376 taskAnimation = new AnimationProps()
Winson3f32e7e2016-04-20 17:18:08 -0700377 .setDuration(AnimationProps.BOUNDS, EXIT_TO_HOME_TRANSLATION_DURATION)
Winson50448632016-02-01 18:04:59 -0800378 .setListener(postAnimationTrigger.decrementOnAnimationEnd());
Matthew Ng43db6d22017-06-27 15:29:39 -0700379 if (Recents.getConfiguration().isLowRamDevice) {
380 taskAnimation.setInterpolator(AnimationProps.BOUNDS,
381 Interpolators.FAST_OUT_SLOW_IN);
382 } else {
383 taskAnimation.setStartDelay(AnimationProps.BOUNDS, delay)
384 .setInterpolator(AnimationProps.BOUNDS,
385 EXIT_TO_HOME_TRANSLATION_INTERPOLATOR);
386 }
Winson50448632016-02-01 18:04:59 -0800387 postAnimationTrigger.increment();
388 } else {
389 taskAnimation = AnimationProps.IMMEDIATE;
390 }
Winsonf24f2162016-01-05 12:11:55 -0800391
Winson3b6ba1a2016-03-22 15:37:54 -0700392 mTmpTransform.fillIn(tv);
Matthew Ng43db6d22017-06-27 15:29:39 -0700393 if (Recents.getConfiguration().isLowRamDevice) {
394 taskAnimation.setInterpolator(AnimationProps.ALPHA,
395 EXIT_TO_HOME_TRANSLATION_INTERPOLATOR)
396 .setDuration(AnimationProps.ALPHA, EXIT_TO_HOME_TRANSLATION_DURATION);
397 mTmpTransform.rect.offset(0, stackLayout.mTaskStackLowRamLayoutAlgorithm
398 .getTaskRect().height() / 4);
399 mTmpTransform.alpha = 0f;
400 } else {
401 mTmpTransform.rect.offset(0, offscreenYOffset);
402 }
Winsonf24f2162016-01-05 12:11:55 -0800403 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
404 }
405 }
406
407 /**
408 * Starts the animation for the launching task view, hiding any tasks that might occlude the
409 * window transition for the launching task.
410 */
411 public void startLaunchTaskAnimation(TaskView launchingTaskView, boolean screenPinningRequested,
412 final ReferenceCountedTrigger postAnimationTrigger) {
413 Resources res = mStackView.getResources();
Winsonf24f2162016-01-05 12:11:55 -0800414
415 int taskViewExitToAppDuration = res.getInteger(
416 R.integer.recents_task_exit_to_app_duration);
417 int taskViewAffiliateGroupEnterOffset = res.getDimensionPixelSize(
Winson59924fe2016-03-17 14:13:18 -0700418 R.dimen.recents_task_stack_animation_affiliate_enter_offset);
Winsonf24f2162016-01-05 12:11:55 -0800419
420 Task launchingTask = launchingTaskView.getTask();
421 List<TaskView> taskViews = mStackView.getTaskViews();
422 int taskViewCount = taskViews.size();
423 for (int i = 0; i < taskViewCount; i++) {
424 TaskView tv = taskViews.get(i);
425 Task task = tv.getTask();
Winson8a3ef372016-05-26 10:46:11 -0700426 boolean currentTaskOccludesLaunchTarget = launchingTask != null &&
427 launchingTask.group != null &&
428 launchingTask.group.isTaskAboveTask(task, launchingTask);
Winsonf24f2162016-01-05 12:11:55 -0800429
430 if (tv == launchingTaskView) {
431 tv.setClipViewInStack(false);
Winsona1ededd2016-03-25 12:23:12 -0700432 postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
433 @Override
434 public void run() {
435 tv.setClipViewInStack(true);
436 }
437 });
Winsonf24f2162016-01-05 12:11:55 -0800438 tv.onStartLaunchTargetLaunchAnimation(taskViewExitToAppDuration,
439 screenPinningRequested, postAnimationTrigger);
440 } else if (currentTaskOccludesLaunchTarget) {
441 // Animate this task out of view
Winsonbe8e6962016-02-01 14:27:52 -0800442 AnimationProps taskAnimation = new AnimationProps(
Winsonc0d70582016-01-29 10:24:39 -0800443 taskViewExitToAppDuration, Interpolators.ALPHA_OUT,
Winsonf24f2162016-01-05 12:11:55 -0800444 postAnimationTrigger.decrementOnAnimationEnd());
445 postAnimationTrigger.increment();
446
Winson3b6ba1a2016-03-22 15:37:54 -0700447 mTmpTransform.fillIn(tv);
Winsonf24f2162016-01-05 12:11:55 -0800448 mTmpTransform.alpha = 0f;
449 mTmpTransform.rect.offset(0, taskViewAffiliateGroupEnterOffset);
450 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
451 }
452 }
453 }
454
455 /**
456 * Starts the delete animation for the specified {@link TaskView}.
457 */
Manu Cornet5df76672017-01-11 15:14:14 -0800458 public void startDeleteTaskAnimation(final TaskView deleteTaskView, boolean gridLayout,
Winsonf24f2162016-01-05 12:11:55 -0800459 final ReferenceCountedTrigger postAnimationTrigger) {
Manu Cornet5df76672017-01-11 15:14:14 -0800460 if (gridLayout) {
461 startTaskGridDeleteTaskAnimation(deleteTaskView, postAnimationTrigger);
462 } else {
463 startTaskStackDeleteTaskAnimation(deleteTaskView, postAnimationTrigger);
464 }
Winsonf24f2162016-01-05 12:11:55 -0800465 }
466
467 /**
Winson3b6ba1a2016-03-22 15:37:54 -0700468 * Starts the delete animation for all the {@link TaskView}s.
469 */
Manu Cornet5df76672017-01-11 15:14:14 -0800470 public void startDeleteAllTasksAnimation(final List<TaskView> taskViews, boolean gridLayout,
471 final ReferenceCountedTrigger postAnimationTrigger) {
472 if (gridLayout) {
473 for (int i = 0; i < taskViews.size(); i++) {
474 startTaskGridDeleteTaskAnimation(taskViews.get(i), postAnimationTrigger);
475 }
476 } else {
477 startTaskStackDeleteAllTasksAnimation(taskViews, postAnimationTrigger);
Winson3b6ba1a2016-03-22 15:37:54 -0700478 }
479 }
480
481 /**
Winson05e46ca2016-02-05 15:40:29 -0800482 * Starts the animation to focus the next {@link TaskView} when paging through recents.
483 *
484 * @return whether or not this will trigger a scroll in the stack
485 */
486 public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask,
487 boolean requestViewFocus) {
488 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
489 TaskStackViewScroller stackScroller = mStackView.getScroller();
490 TaskStack stack = mStackView.getStack();
491
Winson3f3d7442016-03-01 10:07:04 -0800492 final float curScroll = stackScroller.getStackScroll();
Winsonaeb298c2016-04-05 13:08:11 -0700493 final float newScroll = stackScroller.getBoundedStackScroll(
494 stackLayout.getStackScrollForTask(newFocusedTask));
Winson3f3d7442016-03-01 10:07:04 -0800495 boolean willScrollToFront = newScroll > curScroll;
496 boolean willScroll = Float.compare(newScroll, curScroll) != 0;
Winson05e46ca2016-02-05 15:40:29 -0800497
498 // Get the current set of task transforms
Winson3f3d7442016-03-01 10:07:04 -0800499 int taskViewCount = mStackView.getTaskViews().size();
Winson05e46ca2016-02-05 15:40:29 -0800500 ArrayList<Task> stackTasks = stack.getStackTasks();
501 mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
502
503 // Pick up the newly visible views after the scroll
504 mStackView.bindVisibleTaskViews(newScroll);
505
506 // Update the internal state
507 stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
508 stackScroller.setStackScroll(newScroll, null /* animation */);
509 mStackView.cancelDeferredTaskViewLayoutAnimation();
510
511 // Get the final set of task transforms
Winson14991502016-02-15 15:40:08 -0800512 mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
Winsond2a03062016-04-15 11:19:07 -0700513 true /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
Winson05e46ca2016-02-05 15:40:29 -0800514
515 // Focus the task view
516 TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask);
Winson3f3d7442016-03-01 10:07:04 -0800517 if (newFocusedTaskView == null) {
518 // Log the error if we have no task view, and skip the animation
519 Log.e("TaskStackAnimationHelper", "b/27389156 null-task-view prebind:" + taskViewCount +
520 " postbind:" + mStackView.getTaskViews().size() + " prescroll:" + curScroll +
521 " postscroll: " + newScroll);
522 return false;
523 }
Winson05e46ca2016-02-05 15:40:29 -0800524 newFocusedTaskView.setFocusedState(true, requestViewFocus);
525
526 // Setup the end listener to return all the hidden views to the view pool after the
527 // focus animation
Winson91b225d2016-02-16 15:18:07 -0800528 ReferenceCountedTrigger postAnimTrigger = new ReferenceCountedTrigger();
529 postAnimTrigger.addLastDecrementRunnable(new Runnable() {
Winson05e46ca2016-02-05 15:40:29 -0800530 @Override
Winson91b225d2016-02-16 15:18:07 -0800531 public void run() {
Winson05e46ca2016-02-05 15:40:29 -0800532 mStackView.bindVisibleTaskViews(newScroll);
533 }
Winson91b225d2016-02-16 15:18:07 -0800534 });
Winson05e46ca2016-02-05 15:40:29 -0800535
536 List<TaskView> taskViews = mStackView.getTaskViews();
Winson3f3d7442016-03-01 10:07:04 -0800537 taskViewCount = taskViews.size();
Winson05e46ca2016-02-05 15:40:29 -0800538 int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView);
539 for (int i = 0; i < taskViewCount; i++) {
540 TaskView tv = taskViews.get(i);
541 Task task = tv.getTask();
542
543 if (mStackView.isIgnoredTask(task)) {
544 continue;
545 }
546
547 int taskIndex = stackTasks.indexOf(task);
548 TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
549 TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
550
551 // Update the task to the initial state (for the newly picked up tasks)
552 mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
553
554 int duration;
555 Interpolator interpolator;
556 if (willScrollToFront) {
Winsond2a03062016-04-15 11:19:07 -0700557 duration = calculateStaggeredAnimDuration(i);
Winson05e46ca2016-02-05 15:40:29 -0800558 interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
559 } else {
560 if (i < newFocusTaskViewIndex) {
561 duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50);
562 interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
563 } else if (i > newFocusTaskViewIndex) {
564 duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50));
565 interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR;
566 } else {
567 duration = 200;
568 interpolator = FOCUS_NEXT_TASK_INTERPOLATOR;
569 }
570 }
571
572 AnimationProps anim = new AnimationProps()
573 .setDuration(AnimationProps.BOUNDS, duration)
574 .setInterpolator(AnimationProps.BOUNDS, interpolator)
Winson91b225d2016-02-16 15:18:07 -0800575 .setListener(postAnimTrigger.decrementOnAnimationEnd());
576 postAnimTrigger.increment();
Winson05e46ca2016-02-05 15:40:29 -0800577 mStackView.updateTaskViewToTransform(tv, toTransform, anim);
578 }
579 return willScroll;
580 }
Winsond2a03062016-04-15 11:19:07 -0700581
582 /**
583 * Starts the animation to go to the initial stack layout with a task focused. In addition, the
584 * previous task will be animated in after the scroll completes.
585 */
586 public void startNewStackScrollAnimation(TaskStack newStack,
587 ReferenceCountedTrigger animationTrigger) {
588 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
589 TaskStackViewScroller stackScroller = mStackView.getScroller();
590
591 // Get the current set of task transforms
592 ArrayList<Task> stackTasks = newStack.getStackTasks();
593 mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
594
595 // Update the stack
596 mStackView.setTasks(newStack, false /* allowNotifyStackChanges */);
597 mStackView.updateLayoutAlgorithm(false /* boundScroll */);
598
599 // Pick up the newly visible views after the scroll
600 final float newScroll = stackLayout.mInitialScrollP;
601 mStackView.bindVisibleTaskViews(newScroll);
602
603 // Update the internal state
604 stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_UNFOCUSED);
605 stackLayout.setTaskOverridesForInitialState(newStack, true /* ignoreScrollToFront */);
606 stackScroller.setStackScroll(newScroll);
607 mStackView.cancelDeferredTaskViewLayoutAnimation();
608
609 // Get the final set of task transforms
610 mStackView.getLayoutTaskTransforms(newScroll, stackLayout.getFocusState(), stackTasks,
611 false /* ignoreTaskOverrides */, mTmpFinalTaskTransforms);
612
613 // Hide the front most task view until the scroll is complete
614 Task frontMostTask = newStack.getStackFrontMostTask(false /* includeFreeform */);
615 final TaskView frontMostTaskView = mStackView.getChildViewForTask(frontMostTask);
616 final TaskViewTransform frontMostTransform = mTmpFinalTaskTransforms.get(
617 stackTasks.indexOf(frontMostTask));
618 if (frontMostTaskView != null) {
619 mStackView.updateTaskViewToTransform(frontMostTaskView,
620 stackLayout.getFrontOfStackTransform(), AnimationProps.IMMEDIATE);
621 }
622
623 // Setup the end listener to return all the hidden views to the view pool after the
624 // focus animation
625 animationTrigger.addLastDecrementRunnable(new Runnable() {
626 @Override
627 public void run() {
628 mStackView.bindVisibleTaskViews(newScroll);
629
630 // Now, animate in the front-most task
631 if (frontMostTaskView != null) {
632 mStackView.updateTaskViewToTransform(frontMostTaskView, frontMostTransform,
Winson3f32e7e2016-04-20 17:18:08 -0700633 new AnimationProps(75, 250, FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR));
Winsond2a03062016-04-15 11:19:07 -0700634 }
635 }
636 });
637
638 List<TaskView> taskViews = mStackView.getTaskViews();
639 int taskViewCount = taskViews.size();
640 for (int i = 0; i < taskViewCount; i++) {
641 TaskView tv = taskViews.get(i);
642 Task task = tv.getTask();
643
644 if (mStackView.isIgnoredTask(task)) {
645 continue;
646 }
647 if (task == frontMostTask && frontMostTaskView != null) {
648 continue;
649 }
650
651 int taskIndex = stackTasks.indexOf(task);
652 TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
653 TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
654
655 // Update the task to the initial state (for the newly picked up tasks)
656 mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
657
658 int duration = calculateStaggeredAnimDuration(i);
659 Interpolator interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
660
661 AnimationProps anim = new AnimationProps()
662 .setDuration(AnimationProps.BOUNDS, duration)
663 .setInterpolator(AnimationProps.BOUNDS, interpolator)
664 .setListener(animationTrigger.decrementOnAnimationEnd());
665 animationTrigger.increment();
666 mStackView.updateTaskViewToTransform(tv, toTransform, anim);
667 }
668 }
669
670 /**
671 * Caclulates a staggered duration for {@link #startScrollToFocusedTaskAnimation} and
672 * {@link #startNewStackScrollAnimation}.
673 */
674 private int calculateStaggeredAnimDuration(int i) {
675 return Math.max(100, 100 + ((i - 1) * 50));
676 }
Manu Cornet5df76672017-01-11 15:14:14 -0800677
678 private void startTaskGridDeleteTaskAnimation(final TaskView deleteTaskView,
679 final ReferenceCountedTrigger postAnimationTrigger) {
680 postAnimationTrigger.increment();
681 postAnimationTrigger.addLastDecrementRunnable(() -> {
682 mStackView.getTouchHandler().onChildDismissed(deleteTaskView);
683 });
Manu Cornet8ea36af2017-01-24 18:49:49 +0900684 deleteTaskView.animate().setDuration(300).scaleX(0.9f).scaleY(0.9f).alpha(0).setListener(
Manu Cornet5df76672017-01-11 15:14:14 -0800685 new AnimatorListenerAdapter() {
686 @Override
687 public void onAnimationEnd(Animator animation) {
688 postAnimationTrigger.decrement();
689 }}).start();
690 }
691
692 private void startTaskStackDeleteTaskAnimation(final TaskView deleteTaskView,
693 final ReferenceCountedTrigger postAnimationTrigger) {
694 TaskStackViewTouchHandler touchHandler = mStackView.getTouchHandler();
695 touchHandler.onBeginManualDrag(deleteTaskView);
696
697 postAnimationTrigger.increment();
698 postAnimationTrigger.addLastDecrementRunnable(() -> {
699 touchHandler.onChildDismissed(deleteTaskView);
700 });
701
702 final float dismissSize = touchHandler.getScaledDismissSize();
703 ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
704 animator.setDuration(400);
705 animator.addUpdateListener((animation) -> {
706 float progress = (Float) animation.getAnimatedValue();
707 deleteTaskView.setTranslationX(progress * dismissSize);
708 touchHandler.updateSwipeProgress(deleteTaskView, true, progress);
709 });
710 animator.addListener(new AnimatorListenerAdapter() {
711 @Override
712 public void onAnimationEnd(Animator animation) {
713 postAnimationTrigger.decrement();
714 }
715 });
716 animator.start();
717 }
718
719 private void startTaskStackDeleteAllTasksAnimation(final List<TaskView> taskViews,
720 final ReferenceCountedTrigger postAnimationTrigger) {
721 TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
722
723 int offscreenXOffset = mStackView.getMeasuredWidth() - stackLayout.getTaskRect().left;
724
725 int taskViewCount = taskViews.size();
726 for (int i = taskViewCount - 1; i >= 0; i--) {
727 TaskView tv = taskViews.get(i);
728 int taskIndexFromFront = taskViewCount - i - 1;
729 int startDelay = taskIndexFromFront * DOUBLE_FRAME_OFFSET_MS;
730
731 // Disabling clipping with the stack while the view is animating away
732 tv.setClipViewInStack(false);
733
734 // Compose the new animation and transform and star the animation
735 AnimationProps taskAnimation = new AnimationProps(startDelay,
736 DISMISS_ALL_TASKS_DURATION, DISMISS_ALL_TRANSLATION_INTERPOLATOR,
737 new AnimatorListenerAdapter() {
738 @Override
739 public void onAnimationEnd(Animator animation) {
740 postAnimationTrigger.decrement();
741
742 // Re-enable clipping with the stack (we will reuse this view)
743 tv.setClipViewInStack(true);
744 }
745 });
746 postAnimationTrigger.increment();
747
748 mTmpTransform.fillIn(tv);
749 mTmpTransform.rect.offset(offscreenXOffset, 0);
750 mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
751 }
752 }
Winsonf24f2162016-01-05 12:11:55 -0800753}