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