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