1 /*
2  * Copyright (C) 2021 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.pip;
18 
19 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
22 import static android.util.RotationUtils.deltaRotation;
23 import static android.util.RotationUtils.rotateBounds;
24 import static android.view.Surface.ROTATION_270;
25 import static android.view.Surface.ROTATION_90;
26 import static android.view.WindowManager.TRANSIT_CHANGE;
27 import static android.view.WindowManager.TRANSIT_OPEN;
28 import static android.view.WindowManager.TRANSIT_PIP;
29 import static android.view.WindowManager.TRANSIT_TO_BACK;
30 import static android.view.WindowManager.transitTypeToString;
31 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
32 
33 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
34 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
35 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
36 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
37 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
38 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
39 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
40 import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP;
41 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
42 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
43 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
44 
45 import android.animation.Animator;
46 import android.app.ActivityManager;
47 import android.app.TaskInfo;
48 import android.content.Context;
49 import android.graphics.Point;
50 import android.graphics.Rect;
51 import android.os.IBinder;
52 import android.view.Surface;
53 import android.view.SurfaceControl;
54 import android.view.WindowManager;
55 import android.window.TaskSnapshot;
56 import android.window.TransitionInfo;
57 import android.window.TransitionRequestInfo;
58 import android.window.WindowContainerToken;
59 import android.window.WindowContainerTransaction;
60 
61 import androidx.annotation.NonNull;
62 import androidx.annotation.Nullable;
63 
64 import com.android.internal.protolog.common.ProtoLog;
65 import com.android.wm.shell.R;
66 import com.android.wm.shell.ShellTaskOrganizer;
67 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
68 import com.android.wm.shell.common.pip.PipBoundsState;
69 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
70 import com.android.wm.shell.common.pip.PipUtils;
71 import com.android.wm.shell.protolog.ShellProtoLogGroup;
72 import com.android.wm.shell.splitscreen.SplitScreenController;
73 import com.android.wm.shell.sysui.ShellInit;
74 import com.android.wm.shell.transition.CounterRotatorHelper;
75 import com.android.wm.shell.transition.Transitions;
76 import com.android.wm.shell.util.TransitionUtil;
77 
78 import java.io.PrintWriter;
79 import java.util.Optional;
80 
81 /**
82  * Implementation of transitions for PiP on phone. Responsible for enter (alpha, bounds) and
83  * exit animation.
84  */
85 public class PipTransition extends PipTransitionController {
86 
87     private static final String TAG = PipTransition.class.getSimpleName();
88 
89     private final Context mContext;
90     private final PipTransitionState mPipTransitionState;
91     private final PipDisplayLayoutState mPipDisplayLayoutState;
92     private final int mEnterExitAnimationDuration;
93     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
94     private final Optional<SplitScreenController> mSplitScreenOptional;
95     private final PipAnimationController mPipAnimationController;
96     private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS;
97     private Transitions.TransitionFinishCallback mFinishCallback;
98     private SurfaceControl.Transaction mFinishTransaction;
99     private final Rect mExitDestinationBounds = new Rect();
100     @Nullable
101     private IBinder mExitTransition;
102     @Nullable
103     private IBinder mMoveToBackTransition;
104     private IBinder mRequestedEnterTransition;
105     private WindowContainerToken mRequestedEnterTask;
106     /** The Task window that is currently in PIP windowing mode. */
107     @Nullable
108     private WindowContainerToken mCurrentPipTaskToken;
109     /** Whether display is in fixed rotation. */
110     private boolean mInFixedRotation;
111     /**
112      * The rotation that the display will apply after expanding PiP to fullscreen. This is only
113      * meaningful if {@link #mInFixedRotation} is true.
114      */
115     @Surface.Rotation
116     private int mEndFixedRotation;
117     /** Whether the PIP window has fade out for fixed rotation. */
118     private boolean mHasFadeOut;
119 
120     /** Used for setting transform to a transaction from animator. */
121     private final PipAnimationController.PipTransactionHandler mTransactionConsumer =
122             new PipAnimationController.PipTransactionHandler() {
123                 @Override
124                 public boolean handlePipTransaction(SurfaceControl leash,
125                         SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) {
126                     // Only set the operation to transaction but do not apply.
127                     return true;
128                 }
129             };
130 
PipTransition(Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState, PipTransitionState pipTransitionState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreenController> splitScreenOptional)131     public PipTransition(Context context,
132             @NonNull ShellInit shellInit,
133             @NonNull ShellTaskOrganizer shellTaskOrganizer,
134             @NonNull Transitions transitions,
135             PipBoundsState pipBoundsState,
136             PipDisplayLayoutState pipDisplayLayoutState,
137             PipTransitionState pipTransitionState,
138             PipMenuController pipMenuController,
139             PipBoundsAlgorithm pipBoundsAlgorithm,
140             PipAnimationController pipAnimationController,
141             PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
142             Optional<SplitScreenController> splitScreenOptional) {
143         super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
144                 pipBoundsAlgorithm);
145         mContext = context;
146         mPipTransitionState = pipTransitionState;
147         mPipDisplayLayoutState = pipDisplayLayoutState;
148         mPipAnimationController = pipAnimationController;
149         mEnterExitAnimationDuration = context.getResources()
150                 .getInteger(R.integer.config_pipResizeAnimationDuration);
151         mSurfaceTransactionHelper = pipSurfaceTransactionHelper;
152         mSplitScreenOptional = splitScreenOptional;
153     }
154 
155     @Override
onInit()156     protected void onInit() {
157         if (!PipUtils.isPip2ExperimentEnabled()) {
158             mTransitions.addHandler(this);
159         }
160     }
161 
162     @Override
startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds)163     public void startExitTransition(int type, WindowContainerTransaction out,
164             @Nullable Rect destinationBounds) {
165         if (destinationBounds != null) {
166             mExitDestinationBounds.set(destinationBounds);
167         }
168         final PipAnimationController.PipTransitionAnimator animator =
169                 mPipAnimationController.getCurrentAnimator();
170         if (animator != null && animator.isRunning()) {
171             animator.cancel();
172         }
173         mExitTransition = mTransitions.startTransition(type, out, this);
174     }
175 
176     @Override
startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)177     public boolean startAnimation(@NonNull IBinder transition,
178             @NonNull TransitionInfo info,
179             @NonNull SurfaceControl.Transaction startTransaction,
180             @NonNull SurfaceControl.Transaction finishTransaction,
181             @NonNull Transitions.TransitionFinishCallback finishCallback) {
182         final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info);
183         final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
184         mInFixedRotation = fixedRotationChange != null;
185         mEndFixedRotation = mInFixedRotation
186                 ? fixedRotationChange.getEndFixedRotation()
187                 : ROTATION_UNDEFINED;
188 
189         // Exiting PIP.
190         final int type = info.getType();
191         if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)) {
192             mExitDestinationBounds.setEmpty();
193             mExitTransition = null;
194             mMoveToBackTransition = null;
195             mHasFadeOut = false;
196             if (mFinishCallback != null) {
197                 callFinishCallback(null /* wct */);
198                 mFinishTransaction = null;
199                 throw new RuntimeException("Previous callback not called, aborting exit PIP.");
200             }
201 
202             // PipTaskChange can be null if the PIP task has been detached, for example, when the
203             // task contains multiple activities, the PIP will be moved to a new PIP task when
204             // entering, and be moved back when exiting. In that case, the PIP task will be removed
205             // immediately.
206             final TaskInfo pipTaskInfo = currentPipTaskChange != null
207                     ? currentPipTaskChange.getTaskInfo()
208                     : mPipOrganizer.getTaskInfo();
209             if (pipTaskInfo == null) {
210                 throw new RuntimeException("Cannot find the pip task for exit-pip transition.");
211             }
212 
213             switch (type) {
214                 case TRANSIT_EXIT_PIP:
215                     startExitAnimation(info, startTransaction, finishTransaction, finishCallback,
216                             pipTaskInfo, currentPipTaskChange);
217                     break;
218                 case TRANSIT_EXIT_PIP_TO_SPLIT:
219                     startExitToSplitAnimation(info, startTransaction, finishTransaction,
220                             finishCallback, pipTaskInfo);
221                     break;
222                 case TRANSIT_TO_BACK:
223                     // pass through here is intended
224                 case TRANSIT_REMOVE_PIP:
225                     removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
226                             pipTaskInfo);
227                     break;
228                 default:
229                     throw new IllegalStateException("mExitTransition with unexpected transit type="
230                             + transitTypeToString(type));
231             }
232             mCurrentPipTaskToken = null;
233             return true;
234         } else if (transition == mRequestedEnterTransition) {
235             mRequestedEnterTransition = null;
236             mRequestedEnterTask = null;
237         }
238 
239         // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can
240         // happen when a new activity requests enter PIP). In this case, we just show this Task in
241         // its end state, and play other animation as normal.
242         if (currentPipTaskChange != null
243                 && currentPipTaskChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) {
244             resetPrevPip(currentPipTaskChange, startTransaction);
245         }
246 
247         // Entering PIP.
248         if (isEnteringPip(info)) {
249             startEnterAnimation(info, startTransaction, finishTransaction, finishCallback);
250             return true;
251         }
252 
253         // For transition that we don't animate, but contains the PIP leash, we need to update the
254         // PIP surface, otherwise it will be reset after the transition.
255         if (currentPipTaskChange != null) {
256             // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is
257             // changing the *finish*Transaction, we need to use the end bounds. This will also
258             // make sure that the fade-in animation (below) uses the end bounds as well.
259             if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) {
260                 mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds());
261             }
262             updatePipForUnhandledTransition(currentPipTaskChange, startTransaction,
263                     finishTransaction);
264         }
265 
266         return false;
267     }
268 
269     @Override
mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)270     public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
271             @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
272             @NonNull Transitions.TransitionFinishCallback finishCallback) {
273         end();
274     }
275 
276     /** Helper to identify whether this handler is currently the one playing an animation */
isAnimatingLocally()277     private boolean isAnimatingLocally() {
278         return mFinishTransaction != null;
279     }
280 
281     @Nullable
282     @Override
handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)283     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
284             @NonNull TransitionRequestInfo request) {
285         if (requestHasPipEnter(request)) {
286             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
287                     "%s: handle PiP enter request", TAG);
288             WindowContainerTransaction wct = new WindowContainerTransaction();
289             augmentRequest(transition, request, wct);
290             return wct;
291         } else if (request.getType() == TRANSIT_TO_BACK && request.getTriggerTask() != null
292                 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_PINNED) {
293             // if we receive a TRANSIT_TO_BACK type of request while in PiP
294             mMoveToBackTransition = transition;
295             // update the transition state to avoid {@link PipTaskOrganizer#onTaskVanished()} calls
296             mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
297 
298             // return an empty WindowContainerTransaction so that we don't check other handlers
299             return new WindowContainerTransaction();
300         } else {
301             return null;
302         }
303     }
304 
305     @Override
augmentRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT)306     public void augmentRequest(@NonNull IBinder transition,
307             @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) {
308         if (!requestHasPipEnter(request)) {
309             throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
310         }
311         if (mEnterAnimationType == ANIM_TYPE_ALPHA) {
312             mRequestedEnterTransition = transition;
313             mRequestedEnterTask = request.getTriggerTask().token;
314             outWCT.setActivityWindowingMode(request.getTriggerTask().token,
315                     WINDOWING_MODE_UNDEFINED);
316             final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
317             outWCT.setBounds(request.getTriggerTask().token, destinationBounds);
318         }
319     }
320 
321     @Override
end()322     public void end() {
323         Animator animator = mPipAnimationController.getCurrentAnimator();
324         if (animator != null && animator.isRunning()) {
325             animator.end();
326         }
327     }
328 
329     @Override
handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct)330     public boolean handleRotateDisplay(int startRotation, int endRotation,
331             WindowContainerTransaction wct) {
332         if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) {
333             // A fade-in was requested but not-yet started. In this case, just recalculate the
334             // initial state under the new rotation.
335             int rotationDelta = deltaRotation(startRotation, endRotation);
336             if (rotationDelta != Surface.ROTATION_0) {
337                 mPipDisplayLayoutState.rotateTo(endRotation);
338 
339                 final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
340                 wct.setBounds(mRequestedEnterTask, destinationBounds);
341                 return true;
342             }
343         }
344         return false;
345     }
346 
347     @Override
onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)348     public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
349             @Nullable SurfaceControl.Transaction finishT) {
350         if (transition != mExitTransition) {
351             return;
352         }
353         // This means an expand happened before enter-pip finished and we are now "merging" a
354         // no-op transition that happens to match our exit-pip.
355         // Or that the keyguard is up and preventing the transition from applying, in which case we
356         // want to manually reset pip. (b/283783868)
357         boolean cancelled = false;
358         if (mPipAnimationController.getCurrentAnimator() != null) {
359             mPipAnimationController.getCurrentAnimator().cancel();
360             mPipAnimationController.resetAnimatorState();
361             cancelled = true;
362         }
363 
364         // Unset exitTransition AFTER cancel so that finishResize knows we are merging.
365         mExitTransition = null;
366         if (!cancelled) return;
367         final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
368         if (taskInfo != null) {
369             if (aborted) {
370                 // keyguard case - the transition got aborted, so we want to reset state and
371                 // windowing mode before reapplying the resize transaction
372                 sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP);
373                 mPipOrganizer.onExitPipFinished(taskInfo);
374 
375                 WindowContainerTransaction wct = new WindowContainerTransaction();
376                 mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
377                 wct.setBounds(taskInfo.token, null);
378                 mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false);
379             } else {
380                 // merge case
381                 startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
382                         mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
383                         new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */);
384             }
385         }
386         mExitDestinationBounds.setEmpty();
387         mCurrentPipTaskToken = null;
388     }
389 
390     @Override
onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, @NonNull SurfaceControl.Transaction tx)391     public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds,
392             @PipAnimationController.TransitionDirection int direction,
393             @NonNull SurfaceControl.Transaction tx) {
394         final boolean enteringPip = isInPipDirection(direction);
395         if (enteringPip) {
396             mPipTransitionState.setTransitionState(ENTERED_PIP);
397         }
398         // If we have an exit transition, but aren't playing a transition locally, it
399         // means we're expecting the exit transition will be "merged" into another transition
400         // (likely a remote like launcher), so don't fire the finish-callback here -- wait until
401         // the exit transition is merged.
402         if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) {
403             final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
404             final boolean hasValidLeash = leash != null && leash.isValid();
405             WindowContainerTransaction wct = null;
406             if (isOutPipDirection(direction)) {
407                 // Only need to reset surface properties. The server-side operations were already
408                 // done at the start. But if it is running fixed rotation, there will be a seamless
409                 // display transition later. So the last rotation transform needs to be kept to
410                 // avoid flickering, and then the display transition will reset the transform.
411                 if (!mInFixedRotation && mFinishTransaction != null) {
412                     mFinishTransaction.merge(tx);
413                 }
414             } else {
415                 wct = new WindowContainerTransaction();
416                 if (isInPipDirection(direction)) {
417                     // If we are animating from fullscreen using a bounds animation, then reset the
418                     // activity windowing mode, and set the task bounds to the final bounds
419                     wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
420                     wct.setBounds(taskInfo.token, destinationBounds);
421                 } else {
422                     wct.setBounds(taskInfo.token, null /* bounds */);
423                 }
424                 // Reset the scale with bounds change synchronously.
425                 if (hasValidLeash) {
426                     mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
427                             .resetScale(tx, leash, destinationBounds)
428                             .round(tx, leash, true /* applyCornerRadius */);
429                 }
430                 wct.setBoundsChangeTransaction(taskInfo.token, tx);
431             }
432             final int displayRotation = taskInfo.getConfiguration().windowConfiguration
433                     .getDisplayRotation();
434             if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation
435                     && hasValidLeash) {
436                 // Launcher may update the Shelf height during the animation, which will update the
437                 // destination bounds. Because this is in fixed rotation, We need to make sure the
438                 // finishTransaction is using the updated bounds in the display rotation.
439                 final PipAnimationController.PipTransitionAnimator<?> animator =
440                         mPipAnimationController.getCurrentAnimator();
441                 final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
442                 final Rect finishBounds = new Rect(destinationBounds);
443                 rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
444                 if (!finishBounds.equals(animator.getEndValue())) {
445                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
446                             "%s: Destination bounds were changed during animation", TAG);
447                     rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation);
448                     mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds);
449                 }
450             }
451             mFinishTransaction = null;
452             callFinishCallback(wct);
453         }
454         finishResizeForMenu(destinationBounds);
455     }
456 
callFinishCallback(WindowContainerTransaction wct)457     private void callFinishCallback(WindowContainerTransaction wct) {
458         // Need to unset mFinishCallback first because onTransitionFinished can re-enter this
459         // handler if there is a pending PiP animation.
460         final Transitions.TransitionFinishCallback finishCallback = mFinishCallback;
461         mFinishCallback = null;
462         finishCallback.onTransitionFinished(wct);
463     }
464 
465     @Override
forceFinishTransition()466     public void forceFinishTransition() {
467         // mFinishCallback might be null with an outdated mCurrentPipTaskToken
468         // for example, when app crashes while in PiP and exit transition has not started
469         mCurrentPipTaskToken = null;
470         if (mFinishCallback == null) return;
471         mFinishCallback.onTransitionFinished(null /* wct */);
472         mFinishCallback = null;
473         mFinishTransaction = null;
474     }
475 
476     @Override
onFixedRotationStarted()477     public void onFixedRotationStarted() {
478         fadeEnteredPipIfNeed(false /* show */);
479     }
480 
481     @Override
onFixedRotationFinished()482     public void onFixedRotationFinished() {
483         fadeEnteredPipIfNeed(true /* show */);
484     }
485 
fadeEnteredPipIfNeed(boolean show)486     private void fadeEnteredPipIfNeed(boolean show) {
487         // The transition with this fixed rotation may be handled by other handler before reaching
488         // PipTransition, so we cannot do this in #startAnimation.
489         if (!mPipTransitionState.hasEnteredPip()) {
490             return;
491         }
492         if (show && mHasFadeOut) {
493             // If there is a pending transition, then let startAnimation handle it. And if it is
494             // handled, mHasFadeOut will be set to false and this runnable will be no-op. Otherwise
495             // make sure the PiP will reshow, e.g. swipe-up with fixed rotation (fade-out) but
496             // return to the current app (only finish the recent transition).
497             mTransitions.runOnIdle(() -> {
498                 if (mHasFadeOut && mPipTransitionState.hasEnteredPip()) {
499                     fadeExistingPip(true /* show */);
500                 }
501             });
502         } else if (!show && !mHasFadeOut) {
503             // Fade out the existing PiP to avoid jump cut during seamless rotation.
504             fadeExistingPip(false /* show */);
505         }
506     }
507 
508     @Nullable
findCurrentPipTaskChange(@onNull TransitionInfo info)509     private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) {
510         if (mCurrentPipTaskToken == null) {
511             return null;
512         }
513         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
514             final TransitionInfo.Change change = info.getChanges().get(i);
515             if (mCurrentPipTaskToken.equals(change.getContainer())) {
516                 return change;
517             }
518         }
519         return null;
520     }
521 
522     @Nullable
findFixedRotationChange(@onNull TransitionInfo info)523     private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) {
524         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
525             final TransitionInfo.Change change = info.getChanges().get(i);
526             if (change.getEndFixedRotation() != ROTATION_UNDEFINED) {
527                 return change;
528             }
529         }
530         return null;
531     }
532 
startExitAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange)533     private void startExitAnimation(@NonNull TransitionInfo info,
534             @NonNull SurfaceControl.Transaction startTransaction,
535             @NonNull SurfaceControl.Transaction finishTransaction,
536             @NonNull Transitions.TransitionFinishCallback finishCallback,
537             @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) {
538         TransitionInfo.Change pipChange = pipTaskChange;
539         SurfaceControl activitySc = null;
540         if (mCurrentPipTaskToken == null) {
541             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
542                     "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG);
543         } else if (pipChange == null) {
544             // The pipTaskChange is null, this can happen if we are reparenting the PIP activity
545             // back to its original Task. In that case, we should animate the activity leash
546             // instead, which should be the change whose last parent is the recorded PiP Task.
547             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
548                 final TransitionInfo.Change change = info.getChanges().get(i);
549                 if (mCurrentPipTaskToken.equals(change.getLastParent())) {
550                     // Find the activity that is exiting PiP.
551                     pipChange = change;
552                     activitySc = change.getLeash();
553                     break;
554                 }
555             }
556         }
557         if (pipChange == null) {
558             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
559                     "%s: No window of exiting PIP is found. Can't play expand animation", TAG);
560             removePipImmediately(info, startTransaction, finishTransaction, finishCallback,
561                     taskInfo);
562             return;
563         }
564 
565         // When exiting PiP, the PiP leash may be an Activity of a multi-windowing Task, for which
566         // case it may not be in the screen coordinate.
567         // Reparent the pip leash to the root with max layer so that we can animate it outside of
568         // parent crop, and make sure it is not covered by other windows.
569         final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info);
570         final SurfaceControl pipLeash;
571         if (activitySc != null) {
572             // Use a local leash to animate activity in case the activity has letterbox which may
573             // be broken by PiP animation, e.g. always end at 0,0 in parent and unable to include
574             // letterbox area in crop bounds.
575             final SurfaceControl activitySurface = pipChange.getLeash();
576             pipLeash = new SurfaceControl.Builder()
577                     .setName(activitySc + "_pip-leash")
578                     .setContainerLayer()
579                     .setHidden(false)
580                     .setParent(root.getLeash())
581                     .build();
582             startTransaction.reparent(activitySurface, pipLeash);
583             // Put the activity at local position with offset in case it is letterboxed.
584             final Point activityOffset = pipChange.getEndRelOffset();
585             startTransaction.setPosition(activitySc, activityOffset.x, activityOffset.y);
586         } else {
587             pipLeash = pipChange.getLeash();
588             startTransaction.reparent(pipLeash, root.getLeash());
589         }
590         startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
591         // Note: because of this, the bounds to animate should be translated to the root coordinate.
592         final Point offset = root.getOffset();
593         final Rect currentBounds = mPipBoundsState.getBounds();
594         currentBounds.offset(-offset.x, -offset.y);
595         startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
596 
597         final WindowContainerToken pipTaskToken = pipChange.getContainer();
598         final boolean useLocalLeash = activitySc != null;
599         final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
600                 mPipBoundsState.getDisplayBounds());
601         mFinishCallback = (wct) -> {
602             mPipOrganizer.onExitPipFinished(taskInfo);
603 
604             // TODO(b/286346098): remove the OPEN app flicker completely
605             // not checking if we go to fullscreen helps avoid getting pip into an inconsistent
606             // state after the flicker occurs. This is a temp solution until flicker is removed.
607             if (!Transitions.SHELL_TRANSITIONS_ROTATION) {
608                 // will help to debug the case when we are not exiting to fullscreen
609                 if (!toFullscreen) {
610                     ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
611                             "%s: startExitAnimation() not exiting to fullscreen", TAG);
612                 }
613                 wct = wct != null ? wct : new WindowContainerTransaction();
614                 wct.setBounds(pipTaskToken, null);
615                 mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP);
616             }
617             if (useLocalLeash) {
618                 if (mPipAnimationController.isAnimating()) {
619                     mPipAnimationController.getCurrentAnimator().end();
620                 }
621                 // Make sure the animator don't use the released leash, e.g. mergeAnimation.
622                 mPipAnimationController.resetAnimatorState();
623                 finishTransaction.remove(pipLeash);
624             }
625             finishCallback.onTransitionFinished(wct);
626         };
627         mFinishTransaction = finishTransaction;
628 
629         // Check if it is Shell rotation.
630         if (Transitions.SHELL_TRANSITIONS_ROTATION) {
631             TransitionInfo.Change displayRotationChange = null;
632             for (int i = info.getChanges().size() - 1; i >= 0; --i) {
633                 final TransitionInfo.Change change = info.getChanges().get(i);
634                 if (change.getMode() == TRANSIT_CHANGE
635                         && (change.getFlags() & FLAG_IS_DISPLAY) != 0
636                         && change.getStartRotation() != change.getEndRotation()) {
637                     displayRotationChange = change;
638                     break;
639                 }
640             }
641             if (displayRotationChange != null) {
642                 // Exiting PIP to fullscreen with orientation change.
643                 startExpandAndRotationAnimation(info, startTransaction, finishTransaction,
644                         displayRotationChange, taskInfo, pipChange, offset);
645                 return;
646             }
647         }
648 
649         final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
650         destinationBounds.offset(-offset.x, -offset.y);
651 
652         // Check if it is fixed rotation.
653         final int rotationDelta;
654         if (mInFixedRotation) {
655             final int startRotation = pipChange.getStartRotation();
656             final int endRotation = mEndFixedRotation;
657             rotationDelta = deltaRotation(startRotation, endRotation);
658             final Rect endBounds = new Rect(destinationBounds);
659 
660             // Set the end frame since the display won't rotate until fixed rotation is finished
661             // in the next display change transition.
662             rotateBounds(endBounds, destinationBounds, rotationDelta);
663             final int degree, x, y;
664             if (rotationDelta == ROTATION_90) {
665                 degree = 90;
666                 x = destinationBounds.right;
667                 y = destinationBounds.top;
668             } else {
669                 degree = -90;
670                 x = destinationBounds.left;
671                 y = destinationBounds.bottom;
672             }
673             mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction,
674                     pipLeash, endBounds, endBounds, new Rect(), degree, x, y,
675                     true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */);
676         } else {
677             rotationDelta = Surface.ROTATION_0;
678         }
679         startExpandAnimation(taskInfo, pipLeash, currentBounds, currentBounds, destinationBounds,
680                 rotationDelta, startTransaction);
681     }
682 
startExpandAndRotationAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionInfo.Change displayRotationChange, @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange, @NonNull Point offset)683     private void startExpandAndRotationAnimation(@NonNull TransitionInfo info,
684             @NonNull SurfaceControl.Transaction startTransaction,
685             @NonNull SurfaceControl.Transaction finishTransaction,
686             @NonNull TransitionInfo.Change displayRotationChange,
687             @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange,
688             @NonNull Point offset) {
689         final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(),
690                 displayRotationChange.getEndRotation());
691 
692         // Counter-rotate all "going-away" things since they are still in the old orientation.
693         final CounterRotatorHelper rotator = new CounterRotatorHelper();
694         rotator.handleClosingChanges(info, startTransaction, displayRotationChange);
695 
696         // Get the start bounds in new orientation.
697         final Rect startBounds = new Rect(pipChange.getStartAbsBounds());
698         rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta);
699         final Rect endBounds = new Rect(pipChange.getEndAbsBounds());
700         startBounds.offset(-offset.x, -offset.y);
701         endBounds.offset(-offset.x, -offset.y);
702 
703         // Reverse the rotation direction for expansion.
704         final int pipRotateDelta = deltaRotation(rotateDelta, 0);
705 
706         // Set the start frame.
707         final int degree, x, y;
708         if (pipRotateDelta == ROTATION_90) {
709             degree = 90;
710             x = startBounds.right;
711             y = startBounds.top;
712         } else {
713             degree = -90;
714             x = startBounds.left;
715             y = startBounds.bottom;
716         }
717         mSurfaceTransactionHelper.rotateAndScaleWithCrop(startTransaction, pipChange.getLeash(),
718                 endBounds, startBounds, new Rect(), degree, x, y, true /* isExpanding */,
719                 pipRotateDelta == ROTATION_270 /* clockwise */);
720         startTransaction.apply();
721         rotator.cleanUp(finishTransaction);
722 
723         // Expand and rotate the pip window to fullscreen.
724         final PipAnimationController.PipTransitionAnimator animator =
725                 mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(),
726                         startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP,
727                         0 /* startingAngle */, pipRotateDelta);
728         animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
729                 .setPipAnimationCallback(mPipAnimationCallback)
730                 .setDuration(mEnterExitAnimationDuration)
731                 .start();
732     }
733 
startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, final Rect baseBounds, final Rect startBounds, final Rect endBounds, final int rotationDelta, @Nullable SurfaceControl.Transaction startTransaction)734     private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
735             final Rect baseBounds, final Rect startBounds, final Rect endBounds,
736             final int rotationDelta, @Nullable SurfaceControl.Transaction startTransaction) {
737         final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
738                 taskInfo.pictureInPictureParams, endBounds);
739         final PipAnimationController.PipTransitionAnimator animator =
740                 mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
741                         endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP,
742                         0 /* startingAngle */, rotationDelta);
743         animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
744                 .setDuration(mEnterExitAnimationDuration);
745         if (startTransaction != null) {
746             animator.setPipTransactionHandler(mTransactionConsumer).applySurfaceControlTransaction(
747                     leash, startTransaction, PipAnimationController.FRACTION_START);
748             startTransaction.apply();
749         }
750         animator.setPipAnimationCallback(mPipAnimationCallback)
751                 .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler())
752                 .start();
753     }
754 
755     /** For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task. */
removePipImmediately(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo)756     private void removePipImmediately(@NonNull TransitionInfo info,
757             @NonNull SurfaceControl.Transaction startTransaction,
758             @NonNull SurfaceControl.Transaction finishTransaction,
759             @NonNull Transitions.TransitionFinishCallback finishCallback,
760             @NonNull TaskInfo taskInfo) {
761         startTransaction.apply();
762         finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
763                 mPipDisplayLayoutState.getDisplayBounds());
764         mPipOrganizer.onExitPipFinished(taskInfo);
765         finishCallback.onTransitionFinished(null);
766     }
767 
768     /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
isEnteringPip(@onNull TransitionInfo info)769     private boolean isEnteringPip(@NonNull TransitionInfo info) {
770         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
771             final TransitionInfo.Change change = info.getChanges().get(i);
772             if (isEnteringPip(change, info.getType())) return true;
773         }
774         return false;
775     }
776 
777     /** Whether a particular change is a window that is entering pip. */
778     @Override
isEnteringPip(@onNull TransitionInfo.Change change, @WindowManager.TransitionType int transitType)779     public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
780             @WindowManager.TransitionType int transitType) {
781         if (change.getTaskInfo() != null
782                 && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
783                 && !change.getContainer().equals(mCurrentPipTaskToken)) {
784             // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
785             // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
786             if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN) {
787                 return true;
788             }
789             // This can happen if the request to enter PIP happens when we are collecting for
790             // another transition, such as TRANSIT_CHANGE (display rotation).
791             if (transitType == TRANSIT_CHANGE) {
792                 return true;
793             }
794 
795             // Please file a bug to handle the unexpected transition type.
796             android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type="
797                     + transitTypeToString(transitType), new Throwable());
798         }
799         return false;
800     }
801 
802     @Override
setEnterAnimationType(@ipAnimationController.AnimationType int type)803     public void setEnterAnimationType(@PipAnimationController.AnimationType int type) {
804         mEnterAnimationType = type;
805     }
806 
startEnterAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)807     private void startEnterAnimation(@NonNull TransitionInfo info,
808             @NonNull SurfaceControl.Transaction startTransaction,
809             @NonNull SurfaceControl.Transaction finishTransaction,
810             @NonNull Transitions.TransitionFinishCallback finishCallback) {
811         // Search for an Enter PiP transition
812         TransitionInfo.Change enterPip = null;
813         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
814             final TransitionInfo.Change change = info.getChanges().get(i);
815             if (change.getTaskInfo() != null
816                     && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
817                 enterPip = change;
818             }
819         }
820         if (enterPip == null) {
821             throw new IllegalStateException("Trying to start PiP animation without a pip"
822                     + "participant");
823         }
824 
825         // Make sure other open changes are visible as entering PIP. Some may be hidden in
826         // Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
827         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
828             final TransitionInfo.Change change = info.getChanges().get(i);
829             if (change == enterPip) continue;
830             if (TransitionUtil.isOpeningType(change.getMode())) {
831                 final SurfaceControl leash = change.getLeash();
832                 startTransaction.show(leash).setAlpha(leash, 1.f);
833             }
834         }
835 
836         startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback);
837     }
838 
839     @Override
startEnterAnimation(@onNull final TransitionInfo.Change pipChange, @NonNull final SurfaceControl.Transaction startTransaction, @NonNull final SurfaceControl.Transaction finishTransaction, @NonNull final Transitions.TransitionFinishCallback finishCallback)840     public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange,
841             @NonNull final SurfaceControl.Transaction startTransaction,
842             @NonNull final SurfaceControl.Transaction finishTransaction,
843             @NonNull final Transitions.TransitionFinishCallback finishCallback) {
844         if (mFinishCallback != null) {
845             callFinishCallback(null /* wct */);
846             mFinishTransaction = null;
847             throw new RuntimeException("Previous callback not called, aborting entering PIP.");
848         }
849 
850         // Keep track of the PIP task and animation.
851         mCurrentPipTaskToken = pipChange.getContainer();
852         mHasFadeOut = false;
853         mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
854         mFinishCallback = finishCallback;
855         mFinishTransaction = finishTransaction;
856 
857         final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
858         final SurfaceControl leash = pipChange.getLeash();
859         final int startRotation = pipChange.getStartRotation();
860         // Check again in case some callers use startEnterAnimation directly so the flag was not
861         // set in startAnimation, e.g. from DefaultMixedHandler.
862         if (!mInFixedRotation) {
863             mEndFixedRotation = pipChange.getEndFixedRotation();
864             mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED;
865         }
866         final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
867 
868         setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
869                 taskInfo.topActivityInfo);
870 
871         if (mPipOrganizer.shouldAttachMenuEarly()) {
872             mPipMenuController.attach(leash);
873         }
874 
875         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
876         final Rect currentBounds = pipChange.getStartAbsBounds();
877         int rotationDelta = deltaRotation(startRotation, endRotation);
878         Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
879                 taskInfo.pictureInPictureParams, currentBounds, destinationBounds);
880         if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
881             // Need to get the bounds of new rotation in old rotation for fixed rotation,
882             computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
883                     destinationBounds, sourceHintRect);
884         }
885         if (!mPipOrganizer.shouldAttachMenuEarly()) {
886             mTransitions.getMainExecutor().executeDelayed(
887                     () -> mPipMenuController.attach(leash), 0);
888         }
889 
890         if (taskInfo.pictureInPictureParams != null
891                 && taskInfo.pictureInPictureParams.isAutoEnterEnabled()
892                 && mPipTransitionState.getInSwipePipToHomeTransition()) {
893             handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash,
894                     sourceHintRect, destinationBounds, taskInfo);
895             return;
896         }
897 
898         final int enterAnimationType = mEnterAnimationType;
899         if (enterAnimationType == ANIM_TYPE_ALPHA) {
900             startTransaction.setAlpha(leash, 0f);
901         } else {
902             // set alpha to 1, because for multi-activity PiP it will create a new task with alpha 0
903             startTransaction.setAlpha(leash, 1f);
904         }
905         startTransaction.apply();
906 
907         PipAnimationController.PipTransitionAnimator animator;
908         if (enterAnimationType == ANIM_TYPE_BOUNDS) {
909             animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
910                     currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
911                     0 /* startingAngle */, rotationDelta);
912             if (sourceHintRect == null) {
913                 // We use content overlay when there is no source rect hint to enter PiP use bounds
914                 // animation.
915                 // TODO(b/272819817): cleanup the null-check and extra logging.
916                 final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null;
917                 if (hasTopActivityInfo) {
918                     animator.setAppIconContentOverlay(
919                             mContext, currentBounds, taskInfo.topActivityInfo,
920                             mPipBoundsState.getLauncherState().getAppIconSizePx());
921                 } else {
922                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
923                             "%s: TaskInfo.topActivityInfo is null", TAG);
924                     animator.setColorContentOverlay(mContext);
925                 }
926             } else {
927                 final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
928                         taskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);
929                 if (snapshot != null) {
930                     // use the task snapshot during the animation, this is for
931                     // launch-into-pip aka. content-pip use case.
932                     animator.setSnapshotContentOverlay(snapshot, sourceHintRect);
933                 }
934             }
935         } else if (enterAnimationType == ANIM_TYPE_ALPHA) {
936             animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
937                     0f, 1f);
938             mSurfaceTransactionHelper
939                     .crop(finishTransaction, leash, destinationBounds)
940                     .round(finishTransaction, leash, true /* applyCornerRadius */);
941         } else {
942             throw new RuntimeException("Unrecognized animation type: " + enterAnimationType);
943         }
944         animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
945                 .setPipAnimationCallback(mPipAnimationCallback)
946                 .setDuration(mEnterExitAnimationDuration);
947         if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) {
948             // For fixed rotation, the animation destination bounds is in old rotation coordinates.
949             // Set the destination bounds to new coordinates after the animation is finished.
950             // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation.
951             animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
952         }
953         // Keep the last appearance when finishing the transition. The transform will be reset when
954         // setting bounds.
955         animator.setPipTransactionHandler(mTransactionConsumer).applySurfaceControlTransaction(
956                 leash, finishTransaction, PipAnimationController.FRACTION_END);
957         // Start to animate enter PiP.
958         animator.setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()).start();
959     }
960 
961     /** Computes destination bounds in old rotation and updates source hint rect if available. */
computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation, TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect)962     private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation,
963             TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) {
964         mPipDisplayLayoutState.rotateTo(endRotation);
965 
966         final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
967         outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
968         // Transform the destination bounds to current display coordinates.
969         rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation);
970         // When entering PiP (from button navigation mode), adjust the source rect hint by
971         // display cutout if applicable.
972         if (outSourceHintRect != null && taskInfo.displayCutoutInsets != null) {
973             if (rotationDelta == Surface.ROTATION_270) {
974                 outSourceHintRect.offset(taskInfo.displayCutoutInsets.left,
975                         taskInfo.displayCutoutInsets.top);
976             }
977         }
978     }
979 
handleSwipePipToHomeTransition( @onNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect, @NonNull Rect destinationBounds, @NonNull ActivityManager.RunningTaskInfo pipTaskInfo)980     private void handleSwipePipToHomeTransition(
981             @NonNull SurfaceControl.Transaction startTransaction,
982             @NonNull SurfaceControl.Transaction finishTransaction,
983             @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect,
984             @NonNull Rect destinationBounds,
985             @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) {
986         if (mInFixedRotation) {
987             // If rotation changes when returning to home, the transition should contain both the
988             // entering PiP and the display change (PipController#startSwipePipToHome has updated
989             // the display layout to new rotation). So it is not expected to see fixed rotation.
990             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
991                     "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation);
992         }
993         final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
994         if (swipePipToHomeOverlay != null) {
995             // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
996             // reparent the PIP activity to a new PIP task (in case there are other activities
997             // in the original Task, in other words multi-activity apps), so we should also reparent
998             // the overlay to the final PIP task.
999             startTransaction.reparent(swipePipToHomeOverlay, leash)
1000                     .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE);
1001             mPipOrganizer.mSwipePipToHomeOverlay = null;
1002         }
1003 
1004         final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds();
1005         final PipAnimationController.PipTransitionAnimator animator =
1006                 mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds,
1007                         destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
1008                         0 /* startingAngle */, 0 /* rotationDelta */)
1009                         .setPipTransactionHandler(mTransactionConsumer)
1010                         .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP);
1011         // The start state is the end state for swipe-auto-pip.
1012         startTransaction.merge(finishTransaction);
1013         animator.applySurfaceControlTransaction(leash, startTransaction,
1014                 PipAnimationController.FRACTION_END);
1015         startTransaction.apply();
1016 
1017         mPipBoundsState.setBounds(destinationBounds);
1018         final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
1019         onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx);
1020         sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
1021         if (swipePipToHomeOverlay != null) {
1022             mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
1023                     null /* callback */, false /* withStartDelay */);
1024         }
1025         mPipTransitionState.setInSwipePipToHomeTransition(false);
1026     }
1027 
startExitToSplitAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo)1028     private void startExitToSplitAnimation(@NonNull TransitionInfo info,
1029             @NonNull SurfaceControl.Transaction startTransaction,
1030             @NonNull SurfaceControl.Transaction finishTransaction,
1031             @NonNull Transitions.TransitionFinishCallback finishCallback,
1032             @NonNull TaskInfo taskInfo) {
1033         for (int i = info.getChanges().size() - 1; i >= 0; i--) {
1034             final TransitionInfo.Change change = info.getChanges().get(i);
1035             final int mode = change.getMode();
1036 
1037             if (mode == TRANSIT_CHANGE && change.getParent() != null) {
1038                 // TODO: perform resize/expand animation for reparented child task.
1039                 continue;
1040             }
1041 
1042             if (TransitionUtil.isOpeningType(mode) && change.getParent() == null) {
1043                 final SurfaceControl leash = change.getLeash();
1044                 final Rect endBounds = change.getEndAbsBounds();
1045                 startTransaction
1046                         .show(leash)
1047                         .setAlpha(leash, 1f)
1048                         .setPosition(leash, endBounds.left, endBounds.top)
1049                         .setWindowCrop(leash, endBounds.width(), endBounds.height());
1050             }
1051         }
1052         mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction);
1053         startTransaction.apply();
1054 
1055         mPipOrganizer.onExitPipFinished(taskInfo);
1056         finishCallback.onTransitionFinished(null);
1057     }
1058 
resetPrevPip(@onNull TransitionInfo.Change prevPipTaskChange, @NonNull SurfaceControl.Transaction startTransaction)1059     private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange,
1060             @NonNull SurfaceControl.Transaction startTransaction) {
1061         final SurfaceControl leash = prevPipTaskChange.getLeash();
1062         final Rect bounds = prevPipTaskChange.getEndAbsBounds();
1063         final Point offset = prevPipTaskChange.getEndRelOffset();
1064         bounds.offset(-offset.x, -offset.y);
1065 
1066         startTransaction.setWindowCrop(leash, null);
1067         startTransaction.setMatrix(leash, 1, 0, 0, 1);
1068         startTransaction.setCornerRadius(leash, 0);
1069         startTransaction.setPosition(leash, bounds.left, bounds.top);
1070 
1071         if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) {
1072             if (mPipAnimationController.getCurrentAnimator() != null) {
1073                 mPipAnimationController.getCurrentAnimator().cancel();
1074             }
1075             startTransaction.setAlpha(leash, 1);
1076         }
1077 
1078         mHasFadeOut = false;
1079         mCurrentPipTaskToken = null;
1080 
1081         // clean-up the state in PipTaskOrganizer if the PipTaskOrganizer#onTaskAppeared() hasn't
1082         // been called yet with its leash reference now pointing to a new SurfaceControl not
1083         // matching the leash of the pip we are removing.
1084         if (mPipOrganizer.getSurfaceControl() == leash) {
1085             mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo());
1086         }
1087     }
1088 
1089     @Override
syncPipSurfaceState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)1090     public boolean syncPipSurfaceState(@NonNull TransitionInfo info,
1091             @NonNull SurfaceControl.Transaction startTransaction,
1092             @NonNull SurfaceControl.Transaction finishTransaction) {
1093         final TransitionInfo.Change pipChange = findCurrentPipTaskChange(info);
1094         if (pipChange == null) return false;
1095         updatePipForUnhandledTransition(pipChange, startTransaction, finishTransaction);
1096         return true;
1097     }
1098 
updatePipForUnhandledTransition(@onNull TransitionInfo.Change pipChange, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)1099     private void updatePipForUnhandledTransition(@NonNull TransitionInfo.Change pipChange,
1100             @NonNull SurfaceControl.Transaction startTransaction,
1101             @NonNull SurfaceControl.Transaction finishTransaction) {
1102         // When the PIP window is visible and being a part of the transition, such as display
1103         // rotation, we need to update its bounds and rounded corner.
1104         final SurfaceControl leash = pipChange.getLeash();
1105         final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds();
1106         final boolean isInPip = mPipTransitionState.isInPip();
1107         mSurfaceTransactionHelper
1108                 .crop(startTransaction, leash, destBounds)
1109                 .round(startTransaction, leash, isInPip)
1110                 .shadow(startTransaction, leash, isInPip);
1111         mSurfaceTransactionHelper
1112                 .crop(finishTransaction, leash, destBounds)
1113                 .round(finishTransaction, leash, isInPip)
1114                 .shadow(finishTransaction, leash, isInPip);
1115         // Make sure the PiP keeps invisible if it was faded out. If it needs to fade in, that will
1116         // be handled by onFixedRotationFinished().
1117         if (isInPip && mHasFadeOut) {
1118             startTransaction.setAlpha(leash, 0f);
1119             finishTransaction.setAlpha(leash, 0f);
1120         }
1121     }
1122 
1123     /** Hides and shows the existing PIP during fixed rotation transition of other activities. */
fadeExistingPip(boolean show)1124     private void fadeExistingPip(boolean show) {
1125         final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
1126         final TaskInfo taskInfo = mPipOrganizer.getTaskInfo();
1127         if (leash == null || !leash.isValid() || taskInfo == null) {
1128             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1129                     "%s: Invalid leash on fadeExistingPip: %s", TAG, leash);
1130             return;
1131         }
1132         final float alphaStart = show ? 0 : 1;
1133         final float alphaEnd = show ? 1 : 0;
1134         final PipAnimationController.PipTransactionHandler transactionHandler =
1135                 new PipAnimationController.PipTransactionHandler() {
1136             @Override
1137             public boolean handlePipTransaction(SurfaceControl leash,
1138                     SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) {
1139                 if (alpha == 0) {
1140                     if (show) {
1141                         tx.setPosition(leash, destinationBounds.left, destinationBounds.top);
1142                     } else {
1143                         // Put PiP out of the display so it won't block touch when it is hidden.
1144                         final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds();
1145                         final int max = Math.max(displayBounds.width(), displayBounds.height());
1146                         tx.setPosition(leash, max, max);
1147                     }
1148                 }
1149                 return false;
1150             }
1151         };
1152         mPipAnimationController
1153                 .getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), alphaStart, alphaEnd)
1154                 .setTransitionDirection(TRANSITION_DIRECTION_SAME)
1155                 .setPipTransactionHandler(transactionHandler)
1156                 .setDuration(mEnterExitAnimationDuration)
1157                 .start();
1158         mHasFadeOut = !show;
1159     }
1160 
finishResizeForMenu(Rect destinationBounds)1161     private void finishResizeForMenu(Rect destinationBounds) {
1162         mPipMenuController.movePipMenu(null, null, destinationBounds,
1163                 PipMenuController.ALPHA_NO_CHANGE);
1164         mPipMenuController.updateMenuBounds(destinationBounds);
1165     }
1166 
1167     @Override
dump(PrintWriter pw, String prefix)1168     public void dump(PrintWriter pw, String prefix) {
1169         final String innerPrefix = prefix + "  ";
1170         pw.println(prefix + TAG);
1171         pw.println(innerPrefix + "mCurrentPipTaskToken=" + mCurrentPipTaskToken);
1172         pw.println(innerPrefix + "mFinishCallback=" + mFinishCallback);
1173     }
1174 }
1175