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