1 /*
2  * Copyright (C) 2022 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 
17 package com.android.wm.shell.activityembedding;
18 
19 import static android.view.WindowManager.TRANSIT_CHANGE;
20 import static android.view.WindowManager.TRANSIT_CLOSE;
21 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
22 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
23 
24 import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
25 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
26 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
27 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
28 
29 import android.animation.Animator;
30 import android.animation.ValueAnimator;
31 import android.content.Context;
32 import android.graphics.Rect;
33 import android.os.IBinder;
34 import android.util.ArraySet;
35 import android.util.Log;
36 import android.view.Choreographer;
37 import android.view.SurfaceControl;
38 import android.view.animation.Animation;
39 import android.window.TransitionInfo;
40 import android.window.WindowContainerToken;
41 
42 import androidx.annotation.NonNull;
43 import androidx.annotation.Nullable;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
47 import com.android.wm.shell.common.ScreenshotUtils;
48 import com.android.wm.shell.util.TransitionUtil;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.Set;
53 import java.util.function.Consumer;
54 
55 /** To run the ActivityEmbedding animations. */
56 class ActivityEmbeddingAnimationRunner {
57 
58     private static final String TAG = "ActivityEmbeddingAnimR";
59 
60     private final ActivityEmbeddingController mController;
61     @VisibleForTesting
62     final ActivityEmbeddingAnimationSpec mAnimationSpec;
63 
64     @Nullable
65     private Animator mActiveAnimator;
66 
ActivityEmbeddingAnimationRunner(@onNull Context context, @NonNull ActivityEmbeddingController controller)67     ActivityEmbeddingAnimationRunner(@NonNull Context context,
68             @NonNull ActivityEmbeddingController controller) {
69         mController = controller;
70         mAnimationSpec = new ActivityEmbeddingAnimationSpec(context);
71     }
72 
73     /** Creates and starts animation for ActivityEmbedding transition. */
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)74     void startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
75             @NonNull SurfaceControl.Transaction startTransaction,
76             @NonNull SurfaceControl.Transaction finishTransaction) {
77         // There may be some surface change that we want to apply after the start transaction is
78         // applied to make sure the surface is ready.
79         final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
80                 new ArrayList<>();
81         final Animator animator = createAnimator(info, startTransaction,
82                 finishTransaction,
83                 () -> mController.onAnimationFinished(transition), postStartTransactionCallbacks);
84         mActiveAnimator = animator;
85 
86         // Start the animation.
87         if (!postStartTransactionCallbacks.isEmpty()) {
88             // postStartTransactionCallbacks require that the start transaction is already
89             // applied to run otherwise they may result in flickers and UI inconsistencies.
90             startTransaction.apply(true /* sync */);
91 
92             // Run tasks that require startTransaction to already be applied
93             final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
94             for (Consumer<SurfaceControl.Transaction> postStartTransactionCallback :
95                     postStartTransactionCallbacks) {
96                 postStartTransactionCallback.accept(t);
97             }
98             t.apply();
99             animator.start();
100         } else {
101             startTransaction.apply();
102             animator.start();
103         }
104     }
105 
cancelAnimationFromMerge()106     void cancelAnimationFromMerge() {
107         if (mActiveAnimator == null) {
108             Log.e(TAG,
109                     "No active ActivityEmbedding animator running but mergeAnimation is "
110                             + "trying to cancel one."
111             );
112             return;
113         }
114         mActiveAnimator.end();
115     }
116 
117     /**
118      * Sets transition animation scale settings value.
119      * @param scale The setting value of transition animation scale.
120      */
setAnimScaleSetting(float scale)121     void setAnimScaleSetting(float scale) {
122         mAnimationSpec.setAnimScaleSetting(scale);
123     }
124 
125     /** Creates the animator for the given {@link TransitionInfo}. */
126     @VisibleForTesting
127     @NonNull
createAnimator(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Runnable animationFinishCallback, @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks)128     Animator createAnimator(@NonNull TransitionInfo info,
129             @NonNull SurfaceControl.Transaction startTransaction,
130             @NonNull SurfaceControl.Transaction finishTransaction,
131             @NonNull Runnable animationFinishCallback,
132             @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
133         final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
134                 startTransaction);
135         final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
136         long duration = 0;
137         if (adapters.isEmpty()) {
138             // Jump cut
139             // No need to modify the animator, but to update the startTransaction with the changes'
140             // ending states.
141             prepareForJumpCut(info, startTransaction);
142         } else {
143             addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
144                     postStartTransactionCallbacks, adapters);
145             addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
146             for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
147                 duration = Math.max(duration, adapter.getDurationHint());
148             }
149             animator.addUpdateListener((anim) -> {
150                 // Update all adapters in the same transaction.
151                 final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
152                 t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
153                 for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
154                     adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
155                 }
156                 t.apply();
157             });
158             prepareForFirstFrame(startTransaction, adapters);
159         }
160         animator.setDuration(duration);
161         animator.addListener(new Animator.AnimatorListener() {
162             @Override
163             public void onAnimationStart(Animator animation) {}
164 
165             @Override
166             public void onAnimationEnd(Animator animation) {
167                 final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
168                 for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
169                     adapter.onAnimationEnd(t);
170                 }
171                 t.apply();
172                 mActiveAnimator = null;
173                 animationFinishCallback.run();
174             }
175 
176             @Override
177             public void onAnimationCancel(Animator animation) {}
178 
179             @Override
180             public void onAnimationRepeat(Animator animation) {}
181         });
182         return animator;
183     }
184 
185     /**
186      * Creates list of {@link ActivityEmbeddingAnimationAdapter} to handle animations on all window
187      * changes.
188      */
189     @NonNull
createAnimationAdapters( @onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)190     private List<ActivityEmbeddingAnimationAdapter> createAnimationAdapters(
191             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
192         boolean isChangeTransition = false;
193         for (TransitionInfo.Change change : info.getChanges()) {
194             if (change.hasFlags(FLAG_IS_BEHIND_STARTING_WINDOW)) {
195                 // Skip the animation if the windows are behind an app starting window.
196                 return new ArrayList<>();
197             }
198             if (!isChangeTransition && change.getMode() == TRANSIT_CHANGE
199                     && !change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
200                 isChangeTransition = true;
201             }
202         }
203         if (isChangeTransition) {
204             return createChangeAnimationAdapters(info, startTransaction);
205         }
206         if (TransitionUtil.isClosingType(info.getType())) {
207             return createCloseAnimationAdapters(info, startTransaction);
208         }
209         return createOpenAnimationAdapters(info, startTransaction);
210     }
211 
212     @NonNull
createOpenAnimationAdapters( @onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)213     private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
214             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
215         return createOpenCloseAnimationAdapters(info, true /* isOpening */,
216                 mAnimationSpec::loadOpenAnimation, startTransaction);
217     }
218 
219     @NonNull
createCloseAnimationAdapters( @onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)220     private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
221             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
222         return createOpenCloseAnimationAdapters(info, false /* isOpening */,
223                 mAnimationSpec::loadCloseAnimation, startTransaction);
224     }
225 
226     /**
227      * Creates {@link ActivityEmbeddingAnimationAdapter} for OPEN and CLOSE types of transition.
228      * @param isOpening {@code true} for OPEN type, {@code false} for CLOSE type.
229      */
230     @NonNull
createOpenCloseAnimationAdapters( @onNull TransitionInfo info, boolean isOpening, @NonNull AnimationProvider animationProvider, @NonNull SurfaceControl.Transaction startTransaction)231     private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
232             @NonNull TransitionInfo info, boolean isOpening,
233             @NonNull AnimationProvider animationProvider,
234             @NonNull SurfaceControl.Transaction startTransaction) {
235         // We need to know if the change window is only a partial of the whole animation screen.
236         // If so, we will need to adjust it to make the whole animation screen looks like one.
237         final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
238         final List<TransitionInfo.Change> closingChanges = new ArrayList<>();
239         final Rect openingWholeScreenBounds = new Rect();
240         final Rect closingWholeScreenBounds = new Rect();
241         for (TransitionInfo.Change change : info.getChanges()) {
242             if (TransitionUtil.isOpeningType(change.getMode())) {
243                 openingChanges.add(change);
244                 openingWholeScreenBounds.union(change.getEndAbsBounds());
245             } else {
246                 closingChanges.add(change);
247                 // Also union with the start bounds because the closing transition may be shrunk.
248                 closingWholeScreenBounds.union(change.getStartAbsBounds());
249                 closingWholeScreenBounds.union(change.getEndAbsBounds());
250             }
251         }
252 
253         // For OPEN transition, open windows should be above close windows.
254         // For CLOSE transition, open windows should be below close windows.
255         int offsetLayer = TYPE_LAYER_OFFSET;
256         final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
257         for (TransitionInfo.Change change : openingChanges) {
258             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
259                     info, change, animationProvider, openingWholeScreenBounds);
260             if (isOpening) {
261                 adapter.overrideLayer(offsetLayer++);
262             }
263             adapters.add(adapter);
264         }
265         for (TransitionInfo.Change change : closingChanges) {
266             if (shouldUseSnapshotAnimationForClosingChange(change)) {
267                 SurfaceControl screenshot = getOrCreateScreenshot(change, change, startTransaction);
268                 if (screenshot != null) {
269                     final SnapshotAdapter snapshotAdapter = new SnapshotAdapter(
270                             createShowSnapshotForClosingAnimation(), change, screenshot,
271                             TransitionUtil.getRootFor(change, info));
272                     if (!isOpening) {
273                         snapshotAdapter.overrideLayer(offsetLayer++);
274                     }
275                     adapters.add(snapshotAdapter);
276                 }
277             }
278             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
279                     info, change, animationProvider, closingWholeScreenBounds);
280             if (!isOpening) {
281                 adapter.overrideLayer(offsetLayer++);
282             }
283             adapters.add(adapter);
284         }
285         return adapters;
286     }
287 
288     /**
289      * Returns whether we should use snapshot animation for the closing change.
290      * It's usually because the end bounds of the closing change are shrunk, which leaves a black
291      * area in the transition.
292      */
shouldUseSnapshotAnimationForClosingChange( @onNull TransitionInfo.Change closingChange)293     static boolean shouldUseSnapshotAnimationForClosingChange(
294             @NonNull TransitionInfo.Change closingChange) {
295         // Only check closing type because we only take screenshot for closing bounds-changing
296         // changes.
297         if (!TransitionUtil.isClosingType(closingChange.getMode())) {
298             return false;
299         }
300         // Don't need to take screenshot if there's no bounds change.
301         return !closingChange.getStartAbsBounds().equals(closingChange.getEndAbsBounds());
302     }
303 
304     /** Sets the first frame to the {@code startTransaction} to avoid any flicker on start. */
prepareForFirstFrame(@onNull SurfaceControl.Transaction startTransaction, @NonNull List<ActivityEmbeddingAnimationAdapter> adapters)305     private void prepareForFirstFrame(@NonNull SurfaceControl.Transaction startTransaction,
306             @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
307         startTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
308         for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
309             adapter.prepareForFirstFrame(startTransaction);
310         }
311     }
312 
313     /** Adds edge extension to the surfaces that have such an animation property. */
addEdgeExtensionIfNeeded(@onNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks, @NonNull List<ActivityEmbeddingAnimationAdapter> adapters)314     private void addEdgeExtensionIfNeeded(@NonNull SurfaceControl.Transaction startTransaction,
315             @NonNull SurfaceControl.Transaction finishTransaction,
316             @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks,
317             @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
318         for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
319             final Animation animation = adapter.mAnimation;
320             if (!animation.hasExtension()) {
321                 continue;
322             }
323             final TransitionInfo.Change change = adapter.mChange;
324             if (TransitionUtil.isOpeningType(adapter.mChange.getMode())) {
325                 // Need to screenshot after startTransaction is applied otherwise activity
326                 // may not be visible or ready yet.
327                 postStartTransactionCallbacks.add(
328                         t -> edgeExtendWindow(change, animation, t, finishTransaction));
329             } else {
330                 // Can screenshot now (before startTransaction is applied)
331                 edgeExtendWindow(change, animation, startTransaction, finishTransaction);
332             }
333         }
334     }
335 
336     /** Adds background color to the transition if any animation has such a property. */
addBackgroundColorIfNeeded(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull List<ActivityEmbeddingAnimationAdapter> adapters)337     private void addBackgroundColorIfNeeded(@NonNull TransitionInfo info,
338             @NonNull SurfaceControl.Transaction startTransaction,
339             @NonNull SurfaceControl.Transaction finishTransaction,
340             @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
341         for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
342             final int backgroundColor = getTransitionBackgroundColorIfSet(info, adapter.mChange,
343                     adapter.mAnimation, 0 /* defaultColor */);
344             if (backgroundColor != 0) {
345                 // We only need to show one color.
346                 addBackgroundToTransition(info.getRootLeash(), backgroundColor, startTransaction,
347                         finishTransaction);
348                 return;
349             }
350         }
351     }
352 
353     @NonNull
createOpenCloseAnimationAdapter( @onNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds)354     private ActivityEmbeddingAnimationAdapter createOpenCloseAnimationAdapter(
355             @NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
356             @NonNull AnimationProvider animationProvider, @NonNull Rect wholeAnimationBounds) {
357         final Animation animation = animationProvider.get(info, change, wholeAnimationBounds);
358         return new ActivityEmbeddingAnimationAdapter(animation, change, change.getLeash(),
359                 wholeAnimationBounds, TransitionUtil.getRootFor(change, info));
360     }
361 
362     @NonNull
createChangeAnimationAdapters( @onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)363     private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
364             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
365         if (shouldUseJumpCutForChangeTransition(info)) {
366             return new ArrayList<>();
367         }
368 
369         final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
370         final Set<TransitionInfo.Change> handledChanges = new ArraySet<>();
371 
372         // For the first iteration, we prepare the animation for the change type windows. This is
373         // needed because there may be window that is reparented while resizing. In such case, we
374         // will do the following:
375         // 1. Capture a screenshot from the Activity surface.
376         // 2. Attach the screenshot surface to the top of TaskFragment (Activity's parent) surface.
377         // 3. Animate the TaskFragment using Activity Change info (start/end bounds).
378         // This is because the TaskFragment surface/change won't contain the Activity's before its
379         // reparent.
380         Animation changeAnimation = null;
381         Rect parentBounds = new Rect();
382         for (TransitionInfo.Change change : info.getChanges()) {
383             if (change.getMode() != TRANSIT_CHANGE
384                     || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
385                 continue;
386             }
387 
388             // This is the window with bounds change.
389             handledChanges.add(change);
390             final WindowContainerToken parentToken = change.getParent();
391             TransitionInfo.Change boundsAnimationChange = change;
392             if (parentToken != null) {
393                 // When the parent window is also included in the transition as an opening window,
394                 // we would like to animate the parent window instead.
395                 final TransitionInfo.Change parentChange = info.getChange(parentToken);
396                 if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) {
397                     // We won't create a separate animation for the parent, but to animate the
398                     // parent for the child resizing.
399                     handledChanges.add(parentChange);
400                     boundsAnimationChange = parentChange;
401                 }
402             }
403 
404             // The TaskFragment may be enter/exit split, so we take the union of both as the parent
405             // size.
406             parentBounds.union(boundsAnimationChange.getStartAbsBounds());
407             parentBounds.union(boundsAnimationChange.getEndAbsBounds());
408             if (boundsAnimationChange != change) {
409                 // Union the change starting bounds in case the activity is resized and reparented
410                 // to a TaskFragment. In that case, the TaskFragment may not cover the activity's
411                 // starting bounds.
412                 parentBounds.union(change.getStartAbsBounds());
413             }
414 
415             // There are two animations in the array. The first one is for the start leash
416             // (snapshot), and the second one is for the end leash (TaskFragment).
417             final Animation[] animations = mAnimationSpec.createChangeBoundsChangeAnimations(change,
418                     parentBounds);
419             // Keep track as we might need to add background color for the animation.
420             // Although there may be multiple change animation, record one of them is sufficient
421             // because the background color will be added to the root leash for the whole animation.
422             changeAnimation = animations[1];
423 
424             // Create a screenshot based on change, but attach it to the top of the
425             // boundsAnimationChange.
426             final SurfaceControl screenshotLeash = getOrCreateScreenshot(change,
427                     boundsAnimationChange, startTransaction);
428             final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info);
429             if (screenshotLeash != null) {
430                 // Adapter for the starting screenshot leash.
431                 // The screenshot leash will be removed in SnapshotAdapter#onAnimationEnd
432                 adapters.add(new ActivityEmbeddingAnimationAdapter.SnapshotAdapter(
433                         animations[0], change, screenshotLeash, root));
434             } else {
435                 Log.e(TAG, "Failed to take screenshot for change=" + change);
436             }
437             // Adapter for the ending bounds changed leash.
438             adapters.add(new ActivityEmbeddingAnimationAdapter.BoundsChangeAdapter(
439                     animations[1], boundsAnimationChange, root));
440         }
441 
442         if (parentBounds.isEmpty()) {
443             throw new IllegalStateException(
444                     "There should be at least one changing window to play the change animation");
445         }
446 
447         // If there is no corresponding open/close window with the change, we should show background
448         // color to cover the empty part of the screen.
449         boolean shouldShouldBackgroundColor = true;
450         // Handle the other windows that don't have bounds change in the same transition.
451         for (TransitionInfo.Change change : info.getChanges()) {
452             if (handledChanges.contains(change)) {
453                 // Skip windows that we have already handled in the previous iteration.
454                 continue;
455             }
456 
457             final Animation animation;
458             if ((change.getParent() != null
459                     && handledChanges.contains(info.getChange(change.getParent())))
460                     || change.getMode() == TRANSIT_CHANGE) {
461                 // No-op if it will be covered by the changing parent window, or it is a changing
462                 // window without bounds change.
463                 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
464             } else if (TransitionUtil.isClosingType(change.getMode())) {
465                 animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
466                 shouldShouldBackgroundColor = false;
467             } else {
468                 animation = mAnimationSpec.createChangeBoundsOpenAnimation(change, parentBounds);
469                 shouldShouldBackgroundColor = false;
470             }
471             adapters.add(new ActivityEmbeddingAnimationAdapter(animation, change,
472                     TransitionUtil.getRootFor(change, info)));
473         }
474 
475         if (shouldShouldBackgroundColor && changeAnimation != null) {
476             // Change animation may leave part of the screen empty. Show background color to cover
477             // that.
478             changeAnimation.setShowBackdrop(true);
479         }
480 
481         return adapters;
482     }
483 
484     /**
485      * Takes a screenshot of the given {@code screenshotChange} surface if WM Core hasn't taken one.
486      * The screenshot leash should be attached to the {@code animationChange} surface which we will
487      * animate later.
488      */
489     @Nullable
getOrCreateScreenshot(@onNull TransitionInfo.Change screenshotChange, @NonNull TransitionInfo.Change animationChange, @NonNull SurfaceControl.Transaction t)490     private SurfaceControl getOrCreateScreenshot(@NonNull TransitionInfo.Change screenshotChange,
491             @NonNull TransitionInfo.Change animationChange,
492             @NonNull SurfaceControl.Transaction t) {
493         final SurfaceControl screenshotLeash = screenshotChange.getSnapshot();
494         if (screenshotLeash != null) {
495             // If WM Core has already taken a screenshot, make sure it is reparented to the
496             // animation leash.
497             t.reparent(screenshotLeash, animationChange.getLeash());
498             return screenshotLeash;
499         }
500 
501         // If WM Core hasn't taken a screenshot, take a screenshot now.
502         final Rect cropBounds = new Rect(screenshotChange.getStartAbsBounds());
503         cropBounds.offsetTo(0, 0);
504         return ScreenshotUtils.takeScreenshot(t, screenshotChange.getLeash(),
505                 animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
506     }
507 
508     /**
509      * Whether we should use jump cut for the change transition.
510      * This normally happens when opening a new secondary with the existing primary using a
511      * different split layout. This can be complicated, like from horizontal to vertical split with
512      * new split pairs.
513      * Uses a jump cut animation to simplify.
514      */
shouldUseJumpCutForChangeTransition(@onNull TransitionInfo info)515     private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
516         // There can be reparenting of changing Activity to new open TaskFragment, so we need to
517         // exclude both in the first iteration.
518         final List<TransitionInfo.Change> changingChanges = new ArrayList<>();
519         for (TransitionInfo.Change change : info.getChanges()) {
520             if (change.getMode() != TRANSIT_CHANGE
521                     || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
522                 continue;
523             }
524             changingChanges.add(change);
525             final WindowContainerToken parentToken = change.getParent();
526             if (parentToken != null) {
527                 // When the parent window is also included in the transition as an opening window,
528                 // we would like to animate the parent window instead.
529                 final TransitionInfo.Change parentChange = info.getChange(parentToken);
530                 if (parentChange != null && TransitionUtil.isOpeningType(parentChange.getMode())) {
531                     changingChanges.add(parentChange);
532                 }
533             }
534         }
535         if (changingChanges.isEmpty()) {
536             // No changing target found.
537             return true;
538         }
539 
540         // Check if the transition contains both opening and closing windows.
541         boolean hasOpeningWindow = false;
542         boolean hasClosingWindow = false;
543         for (TransitionInfo.Change change : info.getChanges()) {
544             if (changingChanges.contains(change)) {
545                 continue;
546             }
547             if (change.getParent() != null
548                     && changingChanges.contains(info.getChange(change.getParent()))) {
549                 // No-op if it will be covered by the changing parent window.
550                 continue;
551             }
552             hasOpeningWindow |= TransitionUtil.isOpeningType(change.getMode());
553             hasClosingWindow |= TransitionUtil.isClosingType(change.getMode());
554         }
555         return hasOpeningWindow && hasClosingWindow;
556     }
557 
558     /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
prepareForJumpCut(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction)559     private void prepareForJumpCut(@NonNull TransitionInfo info,
560             @NonNull SurfaceControl.Transaction startTransaction) {
561         for (TransitionInfo.Change change : info.getChanges()) {
562             final SurfaceControl leash = change.getLeash();
563             if (change.getParent() != null) {
564                 startTransaction.setPosition(leash,
565                         change.getEndRelOffset().x, change.getEndRelOffset().y);
566             } else {
567                 // Change leash has been reparented to the root if its parent is not in the
568                 // transition.
569                 // Because it is reparented to the root, the actual offset should be its relative
570                 // position to the root instead. See Transitions#setupAnimHierarchy.
571                 final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info);
572                 startTransaction.setPosition(leash,
573                         change.getEndAbsBounds().left - root.getOffset().x,
574                         change.getEndAbsBounds().top - root.getOffset().y);
575             }
576             startTransaction.setWindowCrop(leash,
577                     change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
578             if (change.getMode() == TRANSIT_CLOSE) {
579                 startTransaction.hide(leash);
580             } else {
581                 startTransaction.show(leash);
582                 startTransaction.setAlpha(leash, 1f);
583             }
584         }
585     }
586 
587     /** To provide an {@link Animation} based on the transition infos. */
588     private interface AnimationProvider {
get(@onNull TransitionInfo info, @NonNull TransitionInfo.Change change, @NonNull Rect animationBounds)589         Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
590                 @NonNull Rect animationBounds);
591     }
592 }
593