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