• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package com.android.quickstep;
17 
18 import static android.view.WindowManager.TRANSIT_OPEN;
19 import static android.view.WindowManager.TRANSIT_TO_FRONT;
20 
21 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
22 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
23 import static com.android.launcher3.LauncherState.NORMAL;
24 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_DELAY_NAV_FADE_IN;
25 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_IN_DURATION;
26 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_OUT_DURATION;
27 import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_IN_INTERPOLATOR;
28 import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_OUT_INTERPOLATOR;
29 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
30 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
31 import static com.android.launcher3.anim.Interpolators.LINEAR;
32 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR;
33 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL;
34 import static com.android.launcher3.anim.Interpolators.clampToProgress;
35 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
36 import static com.android.launcher3.statehandlers.DepthController.DEPTH;
37 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
38 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
39 
40 import android.animation.Animator;
41 import android.animation.AnimatorListenerAdapter;
42 import android.animation.AnimatorSet;
43 import android.animation.ObjectAnimator;
44 import android.annotation.TargetApi;
45 import android.content.ComponentName;
46 import android.content.Context;
47 import android.graphics.Matrix;
48 import android.graphics.Matrix.ScaleToFit;
49 import android.graphics.Rect;
50 import android.graphics.RectF;
51 import android.os.Build;
52 import android.view.SurfaceControl;
53 import android.view.View;
54 import android.window.TransitionInfo;
55 
56 import androidx.annotation.NonNull;
57 import androidx.annotation.Nullable;
58 
59 import com.android.launcher3.BaseActivity;
60 import com.android.launcher3.DeviceProfile;
61 import com.android.launcher3.anim.AnimationSuccessListener;
62 import com.android.launcher3.anim.AnimatorPlaybackController;
63 import com.android.launcher3.anim.Interpolators;
64 import com.android.launcher3.anim.PendingAnimation;
65 import com.android.launcher3.config.FeatureFlags;
66 import com.android.launcher3.model.data.ItemInfo;
67 import com.android.launcher3.statehandlers.DepthController;
68 import com.android.launcher3.statemanager.StateManager;
69 import com.android.launcher3.util.DisplayController;
70 import com.android.quickstep.util.MultiValueUpdateListener;
71 import com.android.quickstep.util.SurfaceTransactionApplier;
72 import com.android.quickstep.util.TaskViewSimulator;
73 import com.android.quickstep.util.TransformParams;
74 import com.android.quickstep.views.RecentsView;
75 import com.android.quickstep.views.TaskThumbnailView;
76 import com.android.quickstep.views.TaskView;
77 import com.android.systemui.shared.recents.model.Task;
78 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
79 import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
80 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams;
81 
82 /**
83  * Utility class for helpful methods related to {@link TaskView} objects and their tasks.
84  */
85 @TargetApi(Build.VERSION_CODES.R)
86 public final class TaskViewUtils {
87 
TaskViewUtils()88     private TaskViewUtils() {}
89 
90     /**
91      * Try to find a TaskView that corresponds with the component of the launched view.
92      *
93      * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
94      * Otherwise, we will assume we are using a normal app transition, but it's possible that the
95      * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
96      */
findTaskViewToLaunch( RecentsView recentsView, View v, RemoteAnimationTargetCompat[] targets)97     public static TaskView findTaskViewToLaunch(
98             RecentsView recentsView, View v, RemoteAnimationTargetCompat[] targets) {
99         if (v instanceof TaskView) {
100             TaskView taskView = (TaskView) v;
101             return recentsView.isTaskViewVisible(taskView) ? taskView : null;
102         }
103 
104         // It's possible that the launched view can still be resolved to a visible task view, check
105         // the task id of the opening task and see if we can find a match.
106         if (v.getTag() instanceof ItemInfo) {
107             ItemInfo itemInfo = (ItemInfo) v.getTag();
108             ComponentName componentName = itemInfo.getTargetComponent();
109             int userId = itemInfo.user.getIdentifier();
110             if (componentName != null) {
111                 for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
112                     TaskView taskView = recentsView.getTaskViewAt(i);
113                     if (recentsView.isTaskViewVisible(taskView)) {
114                         Task.TaskKey key = taskView.getTask().key;
115                         if (componentName.equals(key.getComponent()) && userId == key.userId) {
116                             return taskView;
117                         }
118                     }
119                 }
120             }
121         }
122 
123         if (targets == null) {
124             return null;
125         }
126         // Resolve the opening task id
127         int openingTaskId = -1;
128         for (RemoteAnimationTargetCompat target : targets) {
129             if (target.mode == MODE_OPENING) {
130                 openingTaskId = target.taskId;
131                 break;
132             }
133         }
134 
135         // If there is no opening task id, fall back to the normal app icon launch animation
136         if (openingTaskId == -1) {
137             return null;
138         }
139 
140         // If the opening task id is not currently visible in overview, then fall back to normal app
141         // icon launch animation
142         TaskView taskView = recentsView.getTaskView(openingTaskId);
143         if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
144             return null;
145         }
146         return taskView;
147     }
148 
createRecentsWindowAnimator(TaskView v, boolean skipViewChanges, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController, PendingAnimation out)149     public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
150             RemoteAnimationTargetCompat[] appTargets,
151             RemoteAnimationTargetCompat[] wallpaperTargets,
152             RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
153             PendingAnimation out) {
154         boolean isRunningTask = v.isRunningTask();
155         TransformParams params = null;
156         TaskViewSimulator tsv = null;
157         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
158             params = v.getRecentsView().getLiveTileParams();
159             tsv = v.getRecentsView().getLiveTileTaskViewSimulator();
160         }
161         createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets, nonAppTargets,
162                 depthController, out, params, tsv);
163     }
164 
165     /**
166      * Creates an animation that controls the window of the opening targets for the recents launch
167      * animation.
168      */
createRecentsWindowAnimator(TaskView v, boolean skipViewChanges, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController, PendingAnimation out, @Nullable TransformParams params, @Nullable TaskViewSimulator tsv)169     public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges,
170             RemoteAnimationTargetCompat[] appTargets,
171             RemoteAnimationTargetCompat[] wallpaperTargets,
172             RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController,
173             PendingAnimation out, @Nullable TransformParams params,
174             @Nullable TaskViewSimulator tsv) {
175         boolean isQuickSwitch = v.isEndQuickswitchCuj();
176         v.setEndQuickswitchCuj(false);
177 
178         boolean inLiveTileMode =
179                 ENABLE_QUICKSTEP_LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1;
180         final RemoteAnimationTargets targets =
181                 new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
182                         inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
183         final RemoteAnimationTargetCompat navBarTarget = targets.getNavBarRemoteAnimationTarget();
184 
185         if (params == null) {
186             SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
187             targets.addReleaseCheck(applier);
188 
189             params = new TransformParams()
190                     .setSyncTransactionApplier(applier)
191                     .setTargetSet(targets);
192         }
193 
194         final RecentsView recentsView = v.getRecentsView();
195         int taskIndex = recentsView.indexOfChild(v);
196         Context context = v.getContext();
197         DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
198         boolean showAsGrid = dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get();
199         boolean parallaxCenterAndAdjacentTask =
200                 taskIndex != recentsView.getCurrentPage() && !showAsGrid;
201         float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex);
202         int startScroll = recentsView.getScrollOffset(taskIndex);
203 
204         TaskViewSimulator topMostSimulator = null;
205 
206         if (tsv == null && targets.apps.length > 0) {
207             tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy());
208             tsv.setDp(dp);
209 
210             // RecentsView never updates the display rotation until swipe-up so the value may
211             // be stale. Use the display value instead.
212             int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
213             tsv.getOrientationState().update(displayRotation, displayRotation);
214 
215             tsv.setPreview(targets.apps[targets.apps.length - 1]);
216             tsv.fullScreenProgress.value = 0;
217             tsv.recentsViewScale.value = 1;
218             if (showAsGrid) {
219                 tsv.taskSecondaryTranslation.value = gridTranslationSecondary;
220             }
221             tsv.setScroll(startScroll);
222 
223             // Fade in the task during the initial 20% of the animation
224             out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
225                     clampToProgress(LINEAR, 0, 0.2f));
226         }
227 
228         if (tsv != null) {
229             out.setFloat(tsv.fullScreenProgress,
230                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
231             out.setFloat(tsv.recentsViewScale,
232                     AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
233             if (showAsGrid) {
234                 out.setFloat(tsv.taskSecondaryTranslation, AnimatedFloat.VALUE, 0,
235                         TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL);
236             }
237             out.setFloat(tsv.recentsViewScroll, AnimatedFloat.VALUE, 0,
238                     TOUCH_RESPONSE_INTERPOLATOR);
239 
240             TaskViewSimulator finalTsv = tsv;
241             TransformParams finalParams = params;
242             out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
243             if (navBarTarget != null) {
244                 final Rect cropRect = new Rect();
245                 out.addOnFrameListener(new MultiValueUpdateListener() {
246                     FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0,
247                             ANIMATION_NAV_FADE_OUT_DURATION, NAV_FADE_OUT_INTERPOLATOR);
248                     FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN,
249                             ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR);
250 
251                     @Override
252                     public void onUpdate(float percent, boolean initOnly) {
253                         final SurfaceParams.Builder navBuilder =
254                                 new SurfaceParams.Builder(navBarTarget.leash);
255                         if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
256                             finalTsv.getCurrentCropRect().round(cropRect);
257                             navBuilder.withMatrix(finalTsv.getCurrentMatrix())
258                                     .withWindowCrop(cropRect)
259                                     .withAlpha(mNavFadeIn.value);
260                         } else {
261                             navBuilder.withAlpha(mNavFadeOut.value);
262                         }
263                         finalParams.applySurfaceParams(navBuilder.build());
264                     }
265                 });
266             } else if (inLiveTileMode) {
267                 // There is no transition animation for app launch from recent in live tile mode so
268                 // we have to trigger the navigation bar animation from system here.
269                 final RecentsAnimationController controller =
270                         recentsView.getRecentsAnimationController();
271                 if (controller != null) {
272                     controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION);
273                 }
274             }
275             topMostSimulator = tsv;
276         }
277 
278         if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) {
279             out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
280 
281             TaskViewSimulator simulatorToCopy = topMostSimulator;
282             simulatorToCopy.apply(params);
283 
284             // Mt represents the overall transformation on the thumbnailView relative to the
285             // Launcher's rootView
286             // K(t) represents transformation on the running window by the taskViewSimulator at
287             // any time t.
288             // at t = 0, we know that the simulator matches the thumbnailView. So if we apply K(0)`
289             // on the Launcher's rootView, the thumbnailView would match the full running task
290             // window. If we apply "K(0)` K(t)" thumbnailView will match the final transformed
291             // window at any time t. This gives the overall matrix on thumbnailView to be:
292             //    Mt K(0)` K(t)
293             // During animation we apply transformation on the thumbnailView (and not the rootView)
294             // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
295             //    Mt K(0)` K(t) Mt`
296             TaskThumbnailView ttv = v.getThumbnail();
297             RectF tvBounds = new RectF(0, 0,  ttv.getWidth(), ttv.getHeight());
298             float[] tvBoundsMapped = new float[]{0, 0,  ttv.getWidth(), ttv.getHeight()};
299             getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
300             RectF tvBoundsInRoot = new RectF(
301                     tvBoundsMapped[0], tvBoundsMapped[1],
302                     tvBoundsMapped[2], tvBoundsMapped[3]);
303 
304             Matrix mt = new Matrix();
305             mt.setRectToRect(tvBounds, tvBoundsInRoot, ScaleToFit.FILL);
306 
307             Matrix mti = new Matrix();
308             mt.invert(mti);
309 
310             Matrix k0i = new Matrix();
311             simulatorToCopy.getCurrentMatrix().invert(k0i);
312 
313             Matrix animationMatrix = new Matrix();
314             out.addOnFrameCallback(() -> {
315                 animationMatrix.set(mt);
316                 animationMatrix.postConcat(k0i);
317                 animationMatrix.postConcat(simulatorToCopy.getCurrentMatrix());
318                 animationMatrix.postConcat(mti);
319                 ttv.setAnimationMatrix(animationMatrix);
320             });
321 
322             out.addListener(new AnimatorListenerAdapter() {
323                 @Override
324                 public void onAnimationEnd(Animator animation) {
325                     ttv.setAnimationMatrix(null);
326                 }
327             });
328         }
329 
330         out.addListener(new AnimationSuccessListener() {
331             @Override
332             public void onAnimationSuccess(Animator animator) {
333                 if (isQuickSwitch) {
334                     InteractionJankMonitorWrapper.end(
335                             InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH);
336                 }
337             }
338 
339             @Override
340             public void onAnimationEnd(Animator animation) {
341                 targets.release();
342                 super.onAnimationEnd(animation);
343             }
344         });
345 
346         if (depthController != null) {
347             out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(context),
348                     TOUCH_RESPONSE_INTERPOLATOR);
349         }
350     }
351 
352     /**
353      * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method
354      * if needed
355      *
356      * We could manually try to animate the just the bounds for the leashes we get back, but we try
357      * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for
358      * us.
359      *
360      * First you have to call TVS#setPreview() to indicate which leash it will operate one
361      * Then operations happen in TVS#apply() on each frame callback.
362      *
363      * TVS uses DeviceProfile to try to figure out things like task height and such based on if the
364      * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the
365      * device is considered in multiWindowMode and things like insets and stuff change
366      * and calculations have to be adjusted in the animations for that
367      */
composeRecentsSplitLaunchAnimator(@onNull TaskView initialView, @NonNull TaskView v, @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t, @NonNull Runnable finishCallback)368     public static void composeRecentsSplitLaunchAnimator(@NonNull TaskView initialView,
369             @NonNull TaskView v, @NonNull TransitionInfo transitionInfo,
370             SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
371 
372         final TransitionInfo.Change[] splitRoots = new TransitionInfo.Change[2];
373         for (int i = 0; i < transitionInfo.getChanges().size(); ++i) {
374             final TransitionInfo.Change change = transitionInfo.getChanges().get(i);
375             final int taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1;
376             final int mode = change.getMode();
377             // Find the target tasks' root tasks since those are the split stages that need to
378             // be animated (the tasks themselves are children and thus inherit animation).
379             if (taskId == initialView.getTask().key.id || taskId == v.getTask().key.id) {
380                 if (!(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
381                     throw new IllegalStateException(
382                             "Expected task to be showing, but it is " + mode);
383                 }
384                 if (change.getParent() == null) {
385                     throw new IllegalStateException("Initiating multi-split launch but the split"
386                             + "root of " + taskId + " is already visible or has broken hierarchy.");
387                 }
388                 splitRoots[taskId == initialView.getTask().key.id ? 0 : 1] =
389                         transitionInfo.getChange(change.getParent());
390             }
391         }
392 
393         // This is where we should animate the split roots. For now, though, just make them visible.
394         for (int i = 0; i < 2; ++i) {
395             t.show(splitRoots[i].getLeash());
396             t.setAlpha(splitRoots[i].getLeash(), 1.f);
397         }
398 
399         // This contains the initial state (before animation), so apply this at the beginning of
400         // the animation.
401         t.apply();
402 
403         // Once there is an animation, this should be called AFTER the animation completes.
404         finishCallback.run();
405     }
406 
407     /** Legacy version (until shell transitions are enabled) */
composeRecentsSplitLaunchAnimatorLegacy(@onNull AnimatorSet anim, @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing, @NonNull StateManager stateManager, @NonNull DepthController depthController, int targetStage)408     public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull AnimatorSet anim,
409             @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets,
410             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
411             @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
412             @NonNull StateManager stateManager, @NonNull DepthController depthController,
413             int targetStage) {
414         PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION);
415         boolean isRunningTask = v.isRunningTask();
416         TransformParams params = null;
417         TaskViewSimulator tvs = null;
418         RecentsView recentsView = v.getRecentsView();
419         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) {
420             params = recentsView.getLiveTileParams();
421             tvs = recentsView.getLiveTileTaskViewSimulator();
422         }
423 
424         boolean inLiveTileMode =
425                 ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1;
426         final RemoteAnimationTargets targets =
427                 new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
428                         inLiveTileMode ? MODE_CLOSING : MODE_OPENING);
429 
430         if (params == null) {
431             SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
432             targets.addReleaseCheck(applier);
433 
434             params = new TransformParams()
435                     .setSyncTransactionApplier(applier)
436                     .setTargetSet(targets);
437         }
438 
439         Rect crop = new Rect();
440         Context context = v.getContext();
441         DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
442         if (tvs == null && targets.apps.length > 0) {
443             tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy());
444             tvs.setDp(dp);
445 
446             // RecentsView never updates the display rotation until swipe-up so the value may
447             // be stale. Use the display value instead.
448             int displayRotation = DisplayController.INSTANCE.get(recentsView.getContext())
449                     .getInfo().rotation;
450             tvs.getOrientationState().update(displayRotation, displayRotation);
451 
452             tvs.setPreview(targets.apps[targets.apps.length - 1]);
453             tvs.fullScreenProgress.value = 0;
454             tvs.recentsViewScale.value = 1;
455 //            tvs.setScroll(startScroll);
456 
457             // Fade in the task during the initial 20% of the animation
458             out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1,
459                     clampToProgress(LINEAR, 0, 0.2f));
460         }
461 
462         TaskViewSimulator topMostSimulator = null;
463 
464         if (tvs != null) {
465             out.setFloat(tvs.fullScreenProgress,
466                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR);
467             out.setFloat(tvs.recentsViewScale,
468                     AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR);
469             out.setFloat(tvs.recentsViewScroll,
470                     AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR);
471 
472             TaskViewSimulator finalTsv = tvs;
473             TransformParams finalParams = params;
474             out.addOnFrameCallback(() -> finalTsv.apply(finalParams));
475             topMostSimulator = tvs;
476         }
477 
478         anim.play(out.buildAnim());
479     }
480 
composeRecentsLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing, @NonNull StateManager stateManager, @NonNull RecentsView recentsView, @NonNull DepthController depthController)481     public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
482             @NonNull RemoteAnimationTargetCompat[] appTargets,
483             @NonNull RemoteAnimationTargetCompat[] wallpaperTargets,
484             @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing,
485             @NonNull StateManager stateManager, @NonNull RecentsView recentsView,
486             @NonNull DepthController depthController) {
487         boolean skipLauncherChanges = !launcherClosing;
488 
489         TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
490         PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
491         createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets,
492                 nonAppTargets, depthController, pa);
493 
494         Animator childStateAnimation = null;
495         // Found a visible recents task that matches the opening app, lets launch the app from there
496         Animator launcherAnim;
497         final AnimatorListenerAdapter windowAnimEndListener;
498         if (launcherClosing) {
499             Context context = v.getContext();
500             DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile();
501             launcherAnim = dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get()
502                     ? ObjectAnimator.ofFloat(recentsView, RecentsView.CONTENT_ALPHA, 0)
503                     : recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
504             launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR);
505             launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
506 
507             // Make sure recents gets fixed up by resetting task alphas and scales, etc.
508             windowAnimEndListener = new AnimatorListenerAdapter() {
509                 @Override
510                 public void onAnimationEnd(Animator animation) {
511                     recentsView.finishRecentsAnimation(false /* toRecents */, () -> {
512                         recentsView.post(() -> {
513                             stateManager.moveToRestState();
514                             stateManager.reapplyState();
515                         });
516                     });
517                 }
518             };
519         } else {
520             AnimatorPlaybackController controller =
521                     stateManager.createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
522             controller.dispatchOnStart();
523             childStateAnimation = controller.getTarget();
524             launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
525             windowAnimEndListener = new AnimatorListenerAdapter() {
526                 @Override
527                 public void onAnimationEnd(Animator animation) {
528                     recentsView.finishRecentsAnimation(false /* toRecents */,
529                             () -> stateManager.goToState(NORMAL, false));
530                 }
531             };
532         }
533         pa.add(launcherAnim);
534         if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) {
535             pa.addOnFrameCallback(recentsView::redrawLiveTile);
536         }
537         anim.play(pa.buildAnim());
538 
539         // Set the current animation first, before adding windowAnimEndListener. Setting current
540         // animation adds some listeners which need to be called before windowAnimEndListener
541         // (the ordering of listeners matter in this case).
542         stateManager.setCurrentAnimation(anim, childStateAnimation);
543         anim.addListener(windowAnimEndListener);
544     }
545 }
546