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.RemoteAnimationTarget.MODE_CLOSING;
19 import static android.view.RemoteAnimationTarget.MODE_OPENING;
20 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
21 
22 import static com.android.app.animation.Interpolators.LINEAR;
23 import static com.android.app.animation.Interpolators.TOUCH_RESPONSE;
24 import static com.android.app.animation.Interpolators.clampToProgress;
25 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA;
26 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
27 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
28 import static com.android.launcher3.LauncherState.BACKGROUND_APP;
29 import static com.android.launcher3.LauncherState.NORMAL;
30 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_DELAY_NAV_FADE_IN;
31 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_IN_DURATION;
32 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_OUT_DURATION;
33 import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_IN_INTERPOLATOR;
34 import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_OUT_INTERPOLATOR;
35 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION;
36 import static com.android.launcher3.QuickstepTransitionManager.SPLIT_DIVIDER_ANIM_DURATION;
37 import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION;
38 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor;
39 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
40 import static com.android.quickstep.util.AnimUtils.clampToDuration;
41 
42 import android.animation.Animator;
43 import android.animation.AnimatorListenerAdapter;
44 import android.animation.AnimatorSet;
45 import android.animation.ValueAnimator;
46 import android.content.ComponentName;
47 import android.content.Context;
48 import android.graphics.Matrix;
49 import android.graphics.Matrix.ScaleToFit;
50 import android.graphics.Rect;
51 import android.graphics.RectF;
52 import android.view.RemoteAnimationTarget;
53 import android.view.SurfaceControl;
54 import android.view.View;
55 import android.window.TransitionInfo;
56 
57 import androidx.annotation.NonNull;
58 import androidx.annotation.Nullable;
59 
60 import com.android.app.animation.Interpolators;
61 import com.android.internal.jank.Cuj;
62 import com.android.launcher3.BaseActivity;
63 import com.android.launcher3.DeviceProfile;
64 import com.android.launcher3.anim.AnimatedFloat;
65 import com.android.launcher3.anim.AnimationSuccessListener;
66 import com.android.launcher3.anim.AnimatorPlaybackController;
67 import com.android.launcher3.anim.PendingAnimation;
68 import com.android.launcher3.model.data.ItemInfo;
69 import com.android.launcher3.statehandlers.DepthController;
70 import com.android.launcher3.statemanager.StateManager;
71 import com.android.launcher3.taskbar.TaskbarUIController;
72 import com.android.launcher3.util.DisplayController;
73 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
74 import com.android.quickstep.util.MultiValueUpdateListener;
75 import com.android.quickstep.util.SurfaceTransaction;
76 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
77 import com.android.quickstep.util.SurfaceTransactionApplier;
78 import com.android.quickstep.util.TaskViewSimulator;
79 import com.android.quickstep.util.TransformParams;
80 import com.android.quickstep.views.DesktopTaskView;
81 import com.android.quickstep.views.GroupedTaskView;
82 import com.android.quickstep.views.RecentsView;
83 import com.android.quickstep.views.TaskThumbnailViewDeprecated;
84 import com.android.quickstep.views.TaskView;
85 import com.android.systemui.animation.RemoteAnimationTargetCompat;
86 import com.android.systemui.shared.recents.model.Task;
87 import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
88 
89 import java.util.ArrayList;
90 import java.util.List;
91 import java.util.function.Consumer;
92 
93 /**
94  * Utility class for helpful methods related to {@link TaskView} objects and their tasks.
95  */
96 public final class TaskViewUtils {
97 
TaskViewUtils()98     private TaskViewUtils() {}
99 
100     /**
101      * Try to find a TaskView that corresponds with the component of the launched view.
102      *
103      * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation.
104      * Otherwise, we will assume we are using a normal app transition, but it's possible that the
105      * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView.
106      */
findTaskViewToLaunch( RecentsView recentsView, View v, RemoteAnimationTarget[] targets)107     public static TaskView findTaskViewToLaunch(
108             RecentsView recentsView, View v, RemoteAnimationTarget[] targets) {
109         if (v instanceof TaskView) {
110             TaskView taskView = (TaskView) v;
111             return recentsView.isTaskViewVisible(taskView) ? taskView : null;
112         }
113 
114         // It's possible that the launched view can still be resolved to a visible task view, check
115         // the task id of the opening task and see if we can find a match.
116         if (v.getTag() instanceof ItemInfo) {
117             ItemInfo itemInfo = (ItemInfo) v.getTag();
118             ComponentName componentName = itemInfo.getTargetComponent();
119             int userId = itemInfo.user.getIdentifier();
120             if (componentName != null) {
121                 for (int i = 0; i < recentsView.getTaskViewCount(); i++) {
122                     TaskView taskView = recentsView.getTaskViewAt(i);
123                     if (recentsView.isTaskViewVisible(taskView)) {
124                         Task.TaskKey key = taskView.getFirstTask().key;
125                         if (componentName.equals(key.getComponent()) && userId == key.userId) {
126                             return taskView;
127                         }
128                     }
129                 }
130             }
131         }
132 
133         if (targets == null) {
134             return null;
135         }
136         // Resolve the opening task id
137         int openingTaskId = -1;
138         for (RemoteAnimationTarget target : targets) {
139             if (target.mode == MODE_OPENING) {
140                 openingTaskId = target.taskId;
141                 break;
142             }
143         }
144 
145         // If there is no opening task id, fall back to the normal app icon launch animation
146         if (openingTaskId == -1) {
147             return null;
148         }
149 
150         // If the opening task id is not currently visible in overview, then fall back to normal app
151         // icon launch animation
152         TaskView taskView = recentsView.getTaskViewByTaskId(openingTaskId);
153         if (taskView == null || !recentsView.isTaskViewVisible(taskView)) {
154             return null;
155         }
156         return taskView;
157     }
158 
createRecentsWindowAnimator( @onNull RecentsView recentsView, @NonNull TaskView v, boolean skipViewChanges, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, @Nullable DepthController depthController, PendingAnimation out)159     public static void createRecentsWindowAnimator(
160             @NonNull RecentsView recentsView,
161             @NonNull TaskView v,
162             boolean skipViewChanges,
163             @NonNull RemoteAnimationTarget[] appTargets,
164             @NonNull RemoteAnimationTarget[] wallpaperTargets,
165             @NonNull RemoteAnimationTarget[] nonAppTargets,
166             @Nullable DepthController depthController,
167             PendingAnimation out) {
168         boolean isQuickSwitch = v.isEndQuickSwitchCuj();
169         v.setEndQuickSwitchCuj(false);
170 
171         final RemoteAnimationTargets targets =
172                 new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets,
173                         MODE_OPENING);
174         final RemoteAnimationTarget navBarTarget = targets.getNavBarRemoteAnimationTarget();
175 
176         SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v);
177         targets.addReleaseCheck(applier);
178 
179         RemoteTargetHandle[] remoteTargetHandles;
180         RemoteTargetHandle[] recentsViewHandles = recentsView.getRemoteTargetHandles();
181         if (v.isRunningTask() && recentsViewHandles != null) {
182             // Re-use existing handles
183             remoteTargetHandles = recentsViewHandles;
184         } else {
185             boolean forDesktop = v instanceof DesktopTaskView;
186             RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(),
187                     recentsView.getSizeStrategy(), targets, forDesktop);
188             if (forDesktop) {
189                 remoteTargetHandles = gluer.assignTargetsForDesktop(targets);
190             } else if (v.containsMultipleTasks()) {
191                 remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets,
192                         ((GroupedTaskView) v).getSplitBoundsConfig());
193             } else {
194                 remoteTargetHandles = gluer.assignTargets(targets);
195             }
196         }
197 
198         final int recentsActivityRotation =
199                 recentsView.getPagedViewOrientedState().getRecentsActivityRotation();
200         for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
201             remoteTargetHandle.getTaskViewSimulator().getOrientationState()
202                     .setRecentsRotation(recentsActivityRotation);
203             remoteTargetHandle.getTransformParams().setSyncTransactionApplier(applier);
204         }
205 
206         int taskIndex = recentsView.indexOfChild(v);
207         Context context = v.getContext();
208         BaseActivity baseActivity = BaseActivity.fromContext(context);
209         DeviceProfile dp = baseActivity.getDeviceProfile();
210         boolean showAsGrid = dp.isTablet;
211         boolean parallaxCenterAndAdjacentTask =
212                 !showAsGrid && taskIndex != recentsView.getCurrentPage();
213         int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex);
214         int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0;
215 
216         RemoteTargetHandle[] topMostSimulators = null;
217 
218         if (!v.isRunningTask()) {
219             // TVSs already initialized from the running task, no need to re-init
220             for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
221                 TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
222                 tvsLocal.setDp(dp);
223 
224                 // RecentsView never updates the display rotation until swipe-up so the value may
225                 // be stale. Use the display value instead.
226                 int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation;
227                 tvsLocal.getOrientationState().update(displayRotation, displayRotation);
228 
229                 tvsLocal.fullScreenProgress.value = 0;
230                 tvsLocal.recentsViewScale.value = 1;
231                 tvsLocal.setIsGridTask(v.isGridTask());
232                 tvsLocal.getOrientationState().getOrientationHandler().set(tvsLocal,
233                         TaskViewSimulator::setTaskRectTranslation, taskRectTranslationPrimary,
234                         taskRectTranslationSecondary);
235 
236                 // Fade in the task during the initial 20% of the animation
237                 out.addFloat(targetHandle.getTransformParams(), TransformParams.TARGET_ALPHA, 0, 1,
238                         clampToProgress(LINEAR, 0, 0.2f));
239             }
240         }
241 
242         for (RemoteTargetHandle targetHandle : remoteTargetHandles) {
243             TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator();
244             out.setFloat(tvsLocal.fullScreenProgress,
245                     AnimatedFloat.VALUE, 1, TOUCH_RESPONSE);
246             out.setFloat(tvsLocal.recentsViewScale,
247                     AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(),
248                     TOUCH_RESPONSE);
249             out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0,
250                     TOUCH_RESPONSE);
251             out.addListener(new AnimatorListenerAdapter() {
252                 @Override
253                 public void onAnimationStart(Animator animation) {
254                     super.onAnimationStart(animation);
255                     final SurfaceTransaction showTransaction = new SurfaceTransaction();
256                     for (int i = targets.apps.length - 1; i >= 0; --i) {
257                         showTransaction.getTransaction().show(targets.apps[i].leash);
258                     }
259                     applier.scheduleApply(showTransaction);
260                 }
261             });
262             out.addOnFrameCallback(() -> {
263                 for (RemoteTargetHandle handle : remoteTargetHandles) {
264                     handle.getTaskViewSimulator().apply(handle.getTransformParams());
265                 }
266             });
267             if (navBarTarget != null) {
268                 final Rect cropRect = new Rect();
269                 out.addOnFrameListener(new MultiValueUpdateListener() {
270                     FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration(
271                             NAV_FADE_OUT_INTERPOLATOR,
272                             0,
273                             ANIMATION_NAV_FADE_OUT_DURATION,
274                             out.getDuration()));
275                     FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration(
276                             NAV_FADE_IN_INTERPOLATOR,
277                             ANIMATION_DELAY_NAV_FADE_IN,
278                             ANIMATION_NAV_FADE_IN_DURATION,
279                             out.getDuration()));
280 
281                     @Override
282                     public void onUpdate(float percent, boolean initOnly) {
283 
284 
285                         // TODO Do we need to operate over multiple TVSs for the navbar leash?
286                         for (RemoteTargetHandle handle : remoteTargetHandles) {
287                             SurfaceTransaction transaction = new SurfaceTransaction();
288                             SurfaceProperties navBuilder =
289                                     transaction.forSurface(navBarTarget.leash);
290 
291                             if (mNavFadeIn.value > mNavFadeIn.getStartValue()) {
292                                 TaskViewSimulator taskViewSimulator = handle.getTaskViewSimulator();
293                                 taskViewSimulator.getCurrentCropRect().round(cropRect);
294                                 navBuilder.setMatrix(taskViewSimulator.getCurrentMatrix())
295                                         .setWindowCrop(cropRect)
296                                         .setAlpha(mNavFadeIn.value);
297                             } else {
298                                 navBuilder.setAlpha(mNavFadeOut.value);
299                             }
300                             handle.getTransformParams().applySurfaceParams(transaction);
301                         }
302                     }
303                 });
304             } else {
305                 // There is no transition animation for app launch from recent in live tile mode so
306                 // we have to trigger the navigation bar animation from system here.
307                 final RecentsAnimationController controller =
308                         recentsView.getRecentsAnimationController();
309                 if (controller != null) {
310                     controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION);
311                 }
312             }
313             topMostSimulators = remoteTargetHandles;
314         }
315 
316         if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulators != null
317                 && topMostSimulators.length > 0) {
318             out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f));
319 
320             RemoteTargetHandle[] simulatorCopies = topMostSimulators;
321             for (RemoteTargetHandle handle : simulatorCopies) {
322                 handle.getTaskViewSimulator().apply(handle.getTransformParams());
323             }
324 
325             // Mt represents the overall transformation on the thumbnailView relative to the
326             // Launcher's rootView
327             // K(t) represents transformation on the running window by the taskViewSimulator at
328             // any time t.
329             // at t = 0, we know that the simulator matches the thumbnailView. So if we apply K(0)`
330             // on the Launcher's rootView, the thumbnailView would match the full running task
331             // window. If we apply "K(0)` K(t)" thumbnailView will match the final transformed
332             // window at any time t. This gives the overall matrix on thumbnailView to be:
333             //    Mt K(0)` K(t)
334             // During animation we apply transformation on the thumbnailView (and not the rootView)
335             // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is:
336             //    Mt K(0)` K(t) Mt`
337             TaskThumbnailViewDeprecated[] thumbnails = v.getThumbnailViews();
338 
339             // In case simulator copies and thumbnail size do no match, ensure we get the lesser.
340             // This ensures we do not create arrays with empty elements or attempt to references
341             // indexes out of array bounds.
342             final int matrixSize = Math.min(simulatorCopies.length, thumbnails.length);
343 
344             Matrix[] mt = new Matrix[matrixSize];
345             Matrix[] mti = new Matrix[matrixSize];
346             for (int i = 0; i < matrixSize; i++) {
347                 TaskThumbnailViewDeprecated ttv = thumbnails[i];
348                 RectF localBounds = new RectF(0, 0,  ttv.getWidth(), ttv.getHeight());
349                 float[] tvBoundsMapped = new float[]{0, 0,  ttv.getWidth(), ttv.getHeight()};
350                 getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false);
351                 RectF localBoundsInRoot = new RectF(
352                         tvBoundsMapped[0], tvBoundsMapped[1],
353                         tvBoundsMapped[2], tvBoundsMapped[3]);
354                 Matrix localMt = new Matrix();
355                 localMt.setRectToRect(localBounds, localBoundsInRoot, ScaleToFit.FILL);
356                 mt[i] = localMt;
357 
358                 Matrix localMti = new Matrix();
359                 localMt.invert(localMti);
360                 mti[i] = localMti;
361 
362                 // Translations for child thumbnails also get scaled as the parent taskView scales
363                 // Add inverse scaling to keep translations the same
364                 float translationY = ttv.getTranslationY();
365                 float translationX = ttv.getTranslationX();
366                 float fullScreenScale =
367                         topMostSimulators[i].getTaskViewSimulator().getFullScreenScale();
368                 out.addFloat(ttv, VIEW_TRANSLATE_Y, translationY,
369                         translationY / fullScreenScale, TOUCH_RESPONSE);
370                 out.addFloat(ttv, VIEW_TRANSLATE_X, translationX,
371                          translationX / fullScreenScale, TOUCH_RESPONSE);
372             }
373 
374             Matrix[] k0i = new Matrix[matrixSize];
375             for (int i = 0; i < matrixSize; i++) {
376                 k0i[i] = new Matrix();
377                 simulatorCopies[i].getTaskViewSimulator().getCurrentMatrix().invert(k0i[i]);
378             }
379             Matrix animationMatrix = new Matrix();
380             out.addOnFrameCallback(() -> {
381                 for (int i = 0; i < matrixSize; i++) {
382                     animationMatrix.set(mt[i]);
383                     animationMatrix.postConcat(k0i[i]);
384                     animationMatrix.postConcat(simulatorCopies[i]
385                             .getTaskViewSimulator().getCurrentMatrix());
386                     animationMatrix.postConcat(mti[i]);
387                     thumbnails[i].setAnimationMatrix(animationMatrix);
388                 }
389             });
390 
391             out.addListener(new AnimatorListenerAdapter() {
392                 @Override
393                 public void onAnimationEnd(Animator animation) {
394                     for (TaskThumbnailViewDeprecated ttv : thumbnails) {
395                         ttv.setAnimationMatrix(null);
396                     }
397                 }
398             });
399         }
400 
401         out.addListener(new AnimationSuccessListener() {
402             @Override
403             public void onAnimationStart(Animator animation) {
404                 for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) {
405                     remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false);
406                 }
407             }
408 
409             @Override
410             public void onAnimationSuccess(Animator animator) {
411                 if (isQuickSwitch) {
412                     InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH);
413                 }
414             }
415 
416             @Override
417             public void onAnimationEnd(Animator animation) {
418                 targets.release();
419                 super.onAnimationEnd(animation);
420             }
421         });
422 
423         if (depthController != null) {
424             out.setFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE,
425                     BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE);
426         }
427     }
428 
429     /**
430      * If {@param launchingTaskView} is not null, then this will play the tasks launch animation
431      * from the position of the GroupedTaskView (when user taps on the TaskView to start it).
432      * Technically this case should be taken care of by
433      * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch tasks whether
434      * it's a single task or multiple tasks results in different entry-points.
435      */
composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView, @NonNull StateManager stateManager, @Nullable DepthController depthController, @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t, @NonNull Runnable finishCallback)436     public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView,
437             @NonNull StateManager stateManager, @Nullable DepthController depthController,
438             @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t,
439             @NonNull Runnable finishCallback) {
440         AnimatorSet animatorSet = new AnimatorSet();
441         animatorSet.addListener(new AnimatorListenerAdapter() {
442             @Override
443             public void onAnimationEnd(Animator animation) {
444                 finishCallback.run();
445             }
446         });
447 
448         final RemoteAnimationTarget[] appTargets =
449                 RemoteAnimationTargetCompat.wrapApps(transitionInfo, t, null /* leashMap */);
450         final RemoteAnimationTarget[] wallpaperTargets =
451                 RemoteAnimationTargetCompat.wrapNonApps(
452                         transitionInfo, true /* wallpapers */, t, null /* leashMap */);
453         final RemoteAnimationTarget[] nonAppTargets =
454                 RemoteAnimationTargetCompat.wrapNonApps(
455                         transitionInfo, false /* wallpapers */, t, null /* leashMap */);
456         final RecentsView recentsView = launchingTaskView.getRecentsView();
457         composeRecentsLaunchAnimator(animatorSet, launchingTaskView, appTargets, wallpaperTargets,
458                 nonAppTargets, /* launcherClosing */ true, stateManager, recentsView,
459                 depthController);
460 
461         t.apply();
462         animatorSet.start();
463     }
464 
465     /**
466      * Legacy version (until shell transitions are enabled)
467      *
468      * If {@param launchingTaskView} is not null, then this will play the tasks launch animation
469      * from the position of the GroupedTaskView (when user taps on the TaskView to start it).
470      * Technically this case should be taken care of by
471      * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch tasks whether
472      * it's a single task or multiple tasks results in different entry-points.
473      *
474      * If it is null, then it will simply fade in the starting apps and fade out launcher (for the
475      * case where launcher handles animating starting split tasks from app icon)
476      * @deprecated with shell transitions
477      */
composeRecentsSplitLaunchAnimatorLegacy( @ullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, @NonNull StateManager stateManager, @Nullable DepthController depthController, @NonNull Runnable finishCallback)478     public static void composeRecentsSplitLaunchAnimatorLegacy(
479             @Nullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId,
480             @NonNull RemoteAnimationTarget[] appTargets,
481             @NonNull RemoteAnimationTarget[] wallpaperTargets,
482             @NonNull RemoteAnimationTarget[] nonAppTargets,
483             @NonNull StateManager stateManager,
484             @Nullable DepthController depthController,
485             @NonNull Runnable finishCallback) {
486         if (launchingTaskView != null) {
487             AnimatorSet animatorSet = new AnimatorSet();
488             RecentsView recentsView = launchingTaskView.getRecentsView();
489             animatorSet.addListener(new AnimatorListenerAdapter() {
490                 @Override
491                 public void onAnimationEnd(Animator animation) {
492                     finishCallback.run();
493                 }
494             });
495             composeRecentsLaunchAnimator(animatorSet, launchingTaskView,
496                     appTargets, wallpaperTargets, nonAppTargets,
497                     true, stateManager,
498                     recentsView, depthController);
499             animatorSet.start();
500             return;
501         }
502 
503         final ArrayList<SurfaceControl> openingTargets = new ArrayList<>();
504         final ArrayList<SurfaceControl> closingTargets = new ArrayList<>();
505         for (RemoteAnimationTarget appTarget : appTargets) {
506             final int taskId = appTarget.taskInfo != null ? appTarget.taskInfo.taskId : -1;
507             final int mode = appTarget.mode;
508             final SurfaceControl leash = appTarget.leash;
509             if (leash == null) {
510                 continue;
511             }
512 
513             if (mode == MODE_OPENING) {
514                 openingTargets.add(leash);
515             } else if (taskId == initialTaskId || taskId == secondTaskId) {
516                 throw new IllegalStateException("Expected task to be opening, but it is " + mode);
517             } else if (mode == MODE_CLOSING) {
518                 closingTargets.add(leash);
519             }
520         }
521 
522         for (int i = 0; i < nonAppTargets.length; ++i) {
523             final SurfaceControl leash = nonAppTargets[i].leash;
524             if (nonAppTargets[i].windowType == TYPE_DOCK_DIVIDER && leash != null) {
525                 openingTargets.add(leash);
526             }
527         }
528 
529         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
530         ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
531         animator.setDuration(SPLIT_LAUNCH_DURATION);
532         animator.addUpdateListener(valueAnimator -> {
533             float progress = valueAnimator.getAnimatedFraction();
534             for (SurfaceControl leash: openingTargets) {
535                 t.setAlpha(leash, progress);
536             }
537             t.apply();
538         });
539         animator.addListener(new AnimatorListenerAdapter() {
540             @Override
541             public void onAnimationStart(Animator animation) {
542                 for (SurfaceControl leash: openingTargets) {
543                     t.show(leash).setAlpha(leash, 0.0f);
544                 }
545                 t.apply();
546             }
547 
548             @Override
549             public void onAnimationEnd(Animator animation) {
550                 for (SurfaceControl leash: closingTargets) {
551                     t.hide(leash);
552                 }
553                 finishCallback.run();
554             }
555         });
556         animator.start();
557     }
558 
559     /**
560      * Start recents to desktop animation
561      */
composeRecentsDesktopLaunchAnimator( @onNull DesktopTaskView launchingTaskView, @NonNull StateManager stateManager, @Nullable DepthController depthController, @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t, @NonNull Runnable finishCallback)562     public static void composeRecentsDesktopLaunchAnimator(
563             @NonNull DesktopTaskView launchingTaskView,
564             @NonNull StateManager stateManager, @Nullable DepthController depthController,
565             @NonNull TransitionInfo transitionInfo,
566             SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
567 
568         AnimatorSet animatorSet = new AnimatorSet();
569         animatorSet.addListener(new AnimatorListenerAdapter() {
570             @Override
571             public void onAnimationStart(Animator animation) {
572                 t.apply();
573             }
574 
575             @Override
576             public void onAnimationEnd(Animator animation) {
577                 finishCallback.run();
578             }
579         });
580 
581         final RemoteAnimationTarget[] apps = RemoteAnimationTargetCompat.wrapApps(
582                 transitionInfo, t, null /* leashMap */);
583         final RemoteAnimationTarget[] wallpaper = RemoteAnimationTargetCompat.wrapNonApps(
584                 transitionInfo, true /* wallpapers */, t, null /* leashMap */);
585         final RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps(
586                 transitionInfo, false /* wallpapers */, t, null /* leashMap */);
587 
588         composeRecentsLaunchAnimator(animatorSet, launchingTaskView, apps, wallpaper, nonApps,
589                 true /* launcherClosing */, stateManager, launchingTaskView.getRecentsView(),
590                 depthController);
591 
592         animatorSet.start();
593     }
594 
composeRecentsLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing, @NonNull StateManager stateManager, @NonNull RecentsView recentsView, @Nullable DepthController depthController)595     public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v,
596             @NonNull RemoteAnimationTarget[] appTargets,
597             @NonNull RemoteAnimationTarget[] wallpaperTargets,
598             @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing,
599             @NonNull StateManager stateManager, @NonNull RecentsView recentsView,
600             @Nullable DepthController depthController) {
601         boolean skipLauncherChanges = !launcherClosing;
602 
603         TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets);
604         PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION);
605         createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets,
606                 wallpaperTargets, nonAppTargets, depthController, pa);
607         if (launcherClosing) {
608             // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app"
609             TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/,
610                     (dividerAnimator) -> {
611                         // If split apps are launching, we want to delay showing the divider bar
612                         // until the very end once the apps are mostly in place. This is because we
613                         // aren't moving the divider leash in the relative position with the
614                         // launching apps.
615                         dividerAnimator.setStartDelay(pa.getDuration()
616                                 - SPLIT_DIVIDER_ANIM_DURATION);
617                         pa.add(dividerAnimator);
618                     });
619         }
620 
621         Animator childStateAnimation = null;
622         // Found a visible recents task that matches the opening app, lets launch the app from there
623         Animator launcherAnim;
624         final AnimatorListenerAdapter windowAnimEndListener;
625         if (launcherClosing) {
626             // Since Overview is in launcher, just opening overview sets willFinishToHome to true.
627             // Now that we are closing the launcher, we need to (re)set willFinishToHome back to
628             // false. Otherwise, RecentsAnimationController can't differentiate between closing
629             // overview to 3p home vs closing overview to app.
630             final RecentsAnimationController raController =
631                     recentsView.getRecentsAnimationController();
632             if (raController != null) {
633                 raController.setWillFinishToHome(false);
634             }
635             launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView);
636             launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE);
637             launcherAnim.setDuration(RECENTS_LAUNCH_DURATION);
638 
639             windowAnimEndListener = new AnimationSuccessListener() {
640                 @Override
641                 public void onAnimationStart(Animator animation) {
642                     recentsView.onTaskLaunchedInLiveTileMode();
643                 }
644 
645                 // Make sure recents gets fixed up by resetting task alphas and scales, etc.
646                 // This should only be run onAnimationSuccess, otherwise finishRecentsAnimation will
647                 // interfere with a rapid swipe up to home in the live tile + running task case.
648                 @Override
649                 public void onAnimationSuccess(Animator animation) {
650                     recentsView.finishRecentsAnimation(false /* toRecents */, () -> {
651                         recentsView.post(() -> {
652                             stateManager.moveToRestState();
653                             stateManager.reapplyState();
654 
655                             // We may have notified launcher is not visible so that taskbar can
656                             // stash immediately. Now that the animation is over, we can update
657                             // that launcher is still visible.
658                             TaskbarUIController controller = recentsView.getSizeStrategy()
659                                     .getTaskbarController();
660                             if (controller != null) {
661                                 boolean launcherVisible = true;
662                                 for (RemoteAnimationTarget target : appTargets) {
663                                     launcherVisible &= target.isTranslucent;
664                                 }
665                                 if (launcherVisible) {
666                                     controller.onLauncherVisibilityChanged(true);
667                                 }
668                             }
669                         });
670                     });
671                 }
672 
673                 @Override
674                 public void onAnimationCancel(Animator animation) {
675                     super.onAnimationCancel(animation);
676                     recentsView.onTaskLaunchedInLiveTileModeCancelled();
677                 }
678 
679                 @Override
680                 public void onAnimationEnd(Animator animation) {
681                     super.onAnimationEnd(animation);
682                     recentsView.setTaskLaunchCancelledRunnable(null);
683                 }
684             };
685         } else {
686             AnimatorPlaybackController controller =
687                     stateManager.createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION);
688             controller.dispatchOnStart();
689             childStateAnimation = controller.getTarget();
690             launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION);
691             windowAnimEndListener = new AnimatorListenerAdapter() {
692                 @Override
693                 public void onAnimationEnd(Animator animation) {
694                     recentsView.finishRecentsAnimation(false /* toRecents */,
695                             () -> stateManager.goToState(NORMAL, false));
696                 }
697             };
698         }
699         pa.add(launcherAnim);
700         if (recentsView.getRunningTaskIndex() != -1) {
701             pa.addOnFrameCallback(recentsView::redrawLiveTile);
702         }
703         anim.play(pa.buildAnim());
704 
705         // Set the current animation first, before adding windowAnimEndListener. Setting current
706         // animation adds some listeners which need to be called before windowAnimEndListener
707         // (the ordering of listeners matter in this case).
708         stateManager.setCurrentAnimation(anim, childStateAnimation);
709         anim.addListener(windowAnimEndListener);
710     }
711 
712     /**
713      * Creates an animation to show/hide the auxiliary surfaces (aka. divider bar), only calling
714      * {@param animatorHandler} if there are valid surfaces to animate.
715      * Passing null handler to apply the visibility immediately.
716      *
717      * @return the animator animating the surfaces
718      */
createSplitAuxiliarySurfacesAnimator( @ullable RemoteAnimationTarget[] nonApps, boolean shown, @Nullable Consumer<ValueAnimator> animatorHandler)719     public static ValueAnimator createSplitAuxiliarySurfacesAnimator(
720             @Nullable RemoteAnimationTarget[] nonApps, boolean shown,
721             @Nullable Consumer<ValueAnimator> animatorHandler) {
722         if (nonApps == null || nonApps.length == 0) {
723             return null;
724         }
725 
726         List<SurfaceControl> auxiliarySurfaces = new ArrayList<>();
727         for (RemoteAnimationTarget target : nonApps) {
728             final SurfaceControl leash = target.leash;
729             if (target.windowType == TYPE_DOCK_DIVIDER && leash != null && leash.isValid()) {
730                 auxiliarySurfaces.add(leash);
731             }
732         }
733         if (auxiliarySurfaces.isEmpty()) {
734             return null;
735         }
736 
737         SurfaceControl.Transaction t = new SurfaceControl.Transaction();
738         if (animatorHandler == null) {
739             // Apply the visibility directly without fade animation.
740             for (SurfaceControl leash : auxiliarySurfaces) {
741                 t.setVisibility(leash, shown);
742             }
743             t.apply();
744             t.close();
745             return null;
746         }
747 
748         ValueAnimator dockFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
749         dockFadeAnimator.addUpdateListener(valueAnimator -> {
750             float progress = valueAnimator.getAnimatedFraction();
751             for (SurfaceControl leash : auxiliarySurfaces) {
752                 if (leash != null && leash.isValid()) {
753                     t.setAlpha(leash, shown ? progress : 1 - progress);
754                 }
755             }
756             t.apply();
757         });
758         dockFadeAnimator.addListener(new AnimatorListenerAdapter() {
759             @Override
760             public void onAnimationStart(Animator animation) {
761                 if (shown) {
762                     for (SurfaceControl leash : auxiliarySurfaces) {
763                         t.setLayer(leash, Integer.MAX_VALUE);
764                         t.setAlpha(leash, 0);
765                         t.show(leash);
766                     }
767                     t.apply();
768                 }
769             }
770 
771             @Override
772             public void onAnimationEnd(Animator animation) {
773                 if (!shown) {
774                     for (SurfaceControl leash : auxiliarySurfaces) {
775                         if (leash != null && leash.isValid()) {
776                             t.hide(leash);
777                         }
778                     }
779                     t.apply();
780                 }
781                 t.close();
782             }
783         });
784         dockFadeAnimator.setDuration(SPLIT_DIVIDER_ANIM_DURATION);
785         animatorHandler.accept(dockFadeAnimator);
786         return dockFadeAnimator;
787     }
788 }
789