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