1 /*
2  * Copyright (C) 2020 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.WINDOWING_MODE_FULLSCREEN;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
23 import static android.util.RotationUtils.deltaRotation;
24 import static android.util.RotationUtils.rotateBounds;
25 
26 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
27 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
28 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
29 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
30 import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
31 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
32 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
33 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
34 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
35 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
36 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
37 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SNAP_AFTER_RESIZE;
38 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
39 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
40 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
41 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
42 import static com.android.wm.shell.pip.PipAnimationController.isRemovePipDirection;
43 
44 import android.animation.Animator;
45 import android.animation.AnimatorListenerAdapter;
46 import android.animation.ValueAnimator;
47 import android.annotation.NonNull;
48 import android.annotation.Nullable;
49 import android.app.ActivityManager;
50 import android.app.ActivityTaskManager;
51 import android.app.PictureInPictureParams;
52 import android.app.TaskInfo;
53 import android.content.ComponentName;
54 import android.content.Context;
55 import android.content.pm.ActivityInfo;
56 import android.content.res.Configuration;
57 import android.graphics.Rect;
58 import android.os.RemoteException;
59 import android.os.SystemClock;
60 import android.util.Log;
61 import android.util.Rational;
62 import android.view.Display;
63 import android.view.Surface;
64 import android.view.SurfaceControl;
65 import android.window.TaskOrganizer;
66 import android.window.WindowContainerToken;
67 import android.window.WindowContainerTransaction;
68 
69 import com.android.internal.annotations.VisibleForTesting;
70 import com.android.wm.shell.R;
71 import com.android.wm.shell.ShellTaskOrganizer;
72 import com.android.wm.shell.animation.Interpolators;
73 import com.android.wm.shell.common.DisplayController;
74 import com.android.wm.shell.common.ScreenshotUtils;
75 import com.android.wm.shell.common.ShellExecutor;
76 import com.android.wm.shell.common.SyncTransactionQueue;
77 import com.android.wm.shell.common.annotations.ShellMainThread;
78 import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
79 import com.android.wm.shell.pip.phone.PipMotionHelper;
80 import com.android.wm.shell.splitscreen.SplitScreenController;
81 import com.android.wm.shell.transition.Transitions;
82 
83 import java.io.PrintWriter;
84 import java.util.Objects;
85 import java.util.Optional;
86 import java.util.function.Consumer;
87 import java.util.function.IntConsumer;
88 
89 /**
90  * Manages PiP tasks such as resize and offset.
91  *
92  * This class listens on {@link TaskOrganizer} callbacks for windowing mode change
93  * both to and from PiP and issues corresponding animation if applicable.
94  * Normally, we apply series of {@link SurfaceControl.Transaction} when the animator is running
95  * and files a final {@link WindowContainerTransaction} at the end of the transition.
96  *
97  * This class is also responsible for general resize/offset PiP operations within SysUI component,
98  * see also {@link PipMotionHelper}.
99  */
100 public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
101         DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
102     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
103     private static final boolean DEBUG = false;
104     /**
105      * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if
106      * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button
107      * navigation, then the alpha type is unexpected.
108      */
109     private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000;
110 
111     /**
112      * The fixed start delay in ms when fading out the content overlay from bounds animation.
113      * This is to overcome the flicker caused by configuration change when rotating from landscape
114      * to portrait PiP in button navigation mode.
115      */
116     private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;
117 
118     private final Context mContext;
119     private final SyncTransactionQueue mSyncTransactionQueue;
120     private final PipBoundsState mPipBoundsState;
121     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
122     private final @NonNull PipMenuController mPipMenuController;
123     private final PipAnimationController mPipAnimationController;
124     private final PipTransitionController mPipTransitionController;
125     private final PipUiEventLogger mPipUiEventLoggerLogger;
126     private final int mEnterAnimationDuration;
127     private final int mExitAnimationDuration;
128     private final int mCrossFadeAnimationDuration;
129     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
130     private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
131     private final Optional<SplitScreenController> mSplitScreenOptional;
132     protected final ShellTaskOrganizer mTaskOrganizer;
133     protected final ShellExecutor mMainExecutor;
134 
135     // These callbacks are called on the update thread
136     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
137             new PipAnimationController.PipAnimationCallback() {
138         @Override
139         public void onPipAnimationStart(TaskInfo taskInfo,
140                 PipAnimationController.PipTransitionAnimator animator) {
141             final int direction = animator.getTransitionDirection();
142             sendOnPipTransitionStarted(direction);
143         }
144 
145         @Override
146         public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
147                 PipAnimationController.PipTransitionAnimator animator) {
148             final int direction = animator.getTransitionDirection();
149             final int animationType = animator.getAnimationType();
150             final Rect destinationBounds = animator.getDestinationBounds();
151             if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
152                 fadeOutAndRemoveOverlay(animator.getContentOverlay(),
153                         animator::clearContentOverlay, true /* withStartDelay*/);
154             }
155             if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
156                     && direction == TRANSITION_DIRECTION_TO_PIP) {
157                 // Notify the display to continue the deferred orientation change.
158                 final WindowContainerTransaction wct = new WindowContainerTransaction();
159                 wct.scheduleFinishEnterPip(mToken, destinationBounds);
160                 mTaskOrganizer.applyTransaction(wct);
161                 // The final task bounds will be applied by onFixedRotationFinished so that all
162                 // coordinates are in new rotation.
163                 mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
164                 mDeferredAnimEndTransaction = tx;
165                 return;
166             }
167             final boolean isExitPipDirection = isOutPipDirection(direction)
168                     || isRemovePipDirection(direction);
169             if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
170                     || isExitPipDirection) {
171                 // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
172                 // the end of an exit PIP animation.
173                 // This is necessary in case there was a resize animation ongoing when exit PIP
174                 // started, in which case the first resize will be skipped to let the exit
175                 // operation handle the final resize out of PIP mode. See b/185306679.
176                 finishResize(tx, destinationBounds, direction, animationType);
177                 sendOnPipTransitionFinished(direction);
178             }
179         }
180 
181         @Override
182         public void onPipAnimationCancel(TaskInfo taskInfo,
183                 PipAnimationController.PipTransitionAnimator animator) {
184             final int direction = animator.getTransitionDirection();
185             if (isInPipDirection(direction) && animator.getContentOverlay() != null) {
186                 fadeOutAndRemoveOverlay(animator.getContentOverlay(),
187                         animator::clearContentOverlay, true /* withStartDelay */);
188             }
189             sendOnPipTransitionCancelled(direction);
190         }
191     };
192 
193     private final PipAnimationController.PipTransactionHandler mPipTransactionHandler =
194             new PipAnimationController.PipTransactionHandler() {
195                 @Override
196                 public boolean handlePipTransaction(SurfaceControl leash,
197                         SurfaceControl.Transaction tx, Rect destinationBounds) {
198                     if (mPipMenuController.isMenuVisible()) {
199                         mPipMenuController.movePipMenu(leash, tx, destinationBounds);
200                         return true;
201                     }
202                     return false;
203                 }
204             };
205 
206     private ActivityManager.RunningTaskInfo mTaskInfo;
207     // To handle the edge case that onTaskInfoChanged callback is received during the entering
208     // PiP transition, where we do not want to intercept the transition but still want to apply the
209     // changed RunningTaskInfo when it finishes.
210     private ActivityManager.RunningTaskInfo mDeferredTaskInfo;
211     private WindowContainerToken mToken;
212     private SurfaceControl mLeash;
213     private PipTransitionState mPipTransitionState;
214     private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
215     private long mLastOneShotAlphaAnimationTime;
216     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
217             mSurfaceControlTransactionFactory;
218     private PictureInPictureParams mPictureInPictureParams;
219     private IntConsumer mOnDisplayIdChangeCallback;
220     /**
221      * The end transaction of PiP animation for switching between PiP and fullscreen with
222      * orientation change. The transaction should be applied after the display is rotated.
223      */
224     private SurfaceControl.Transaction mDeferredAnimEndTransaction;
225     /** Whether the existing PiP is hidden by alpha. */
226     private boolean mHasFadeOut;
227 
228     /**
229      * If set to {@code true}, the entering animation will be skipped and we will wait for
230      * {@link #onFixedRotationFinished(int)} callback to actually enter PiP.
231      */
232     private boolean mWaitForFixedRotation;
233 
234     /**
235      * The rotation that the display will apply after expanding PiP to fullscreen. This is only
236      * meaningful if {@link #mWaitForFixedRotation} is true.
237      */
238     private @Surface.Rotation int mNextRotation;
239 
240     private @Surface.Rotation int mCurrentRotation;
241 
242     /**
243      * An optional overlay used to mask content changing between an app in/out of PiP, only set if
244      * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true.
245      */
246     private SurfaceControl mSwipePipToHomeOverlay;
247 
PipTaskOrganizer(Context context, @NonNull SyncTransactionQueue syncTransactionQueue, @NonNull PipTransitionState pipTransitionState, @NonNull PipBoundsState pipBoundsState, @NonNull PipBoundsAlgorithm boundsHandler, @NonNull PipMenuController pipMenuController, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, @NonNull PipTransitionController pipTransitionController, Optional<LegacySplitScreenController> legacySplitScreenOptional, Optional<SplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor)248     public PipTaskOrganizer(Context context,
249             @NonNull SyncTransactionQueue syncTransactionQueue,
250             @NonNull PipTransitionState pipTransitionState,
251             @NonNull PipBoundsState pipBoundsState,
252             @NonNull PipBoundsAlgorithm boundsHandler,
253             @NonNull PipMenuController pipMenuController,
254             @NonNull PipAnimationController pipAnimationController,
255             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
256             @NonNull PipTransitionController pipTransitionController,
257             Optional<LegacySplitScreenController> legacySplitScreenOptional,
258             Optional<SplitScreenController> splitScreenOptional,
259             @NonNull DisplayController displayController,
260             @NonNull PipUiEventLogger pipUiEventLogger,
261             @NonNull ShellTaskOrganizer shellTaskOrganizer,
262             @ShellMainThread ShellExecutor mainExecutor) {
263         mContext = context;
264         mSyncTransactionQueue = syncTransactionQueue;
265         mPipTransitionState = pipTransitionState;
266         mPipBoundsState = pipBoundsState;
267         mPipBoundsAlgorithm = boundsHandler;
268         mPipMenuController = pipMenuController;
269         mPipTransitionController = pipTransitionController;
270         mEnterAnimationDuration = context.getResources()
271                 .getInteger(R.integer.config_pipEnterAnimationDuration);
272         mExitAnimationDuration = context.getResources()
273                 .getInteger(R.integer.config_pipExitAnimationDuration);
274         mCrossFadeAnimationDuration = context.getResources()
275                 .getInteger(R.integer.config_pipCrossfadeAnimationDuration);
276         mSurfaceTransactionHelper = surfaceTransactionHelper;
277         mPipAnimationController = pipAnimationController;
278         mPipUiEventLoggerLogger = pipUiEventLogger;
279         mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new;
280         mLegacySplitScreenOptional = legacySplitScreenOptional;
281         mSplitScreenOptional = splitScreenOptional;
282         mTaskOrganizer = shellTaskOrganizer;
283         mMainExecutor = mainExecutor;
284 
285         // TODO: Can be removed once wm components are created on the shell-main thread
286         mMainExecutor.execute(() -> {
287             mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
288         });
289         mTaskOrganizer.addFocusListener(this);
290         mPipTransitionController.setPipOrganizer(this);
291         displayController.addDisplayWindowListener(this);
292     }
293 
getCurrentOrAnimatingBounds()294     public Rect getCurrentOrAnimatingBounds() {
295         PipAnimationController.PipTransitionAnimator animator =
296                 mPipAnimationController.getCurrentAnimator();
297         if (animator != null && animator.isRunning()) {
298             return new Rect(animator.getDestinationBounds());
299         }
300         return mPipBoundsState.getBounds();
301     }
302 
isInPip()303     public boolean isInPip() {
304         return mPipTransitionState.isInPip();
305     }
306 
307     /**
308      * Returns whether the entry animation is waiting to be started.
309      */
isEntryScheduled()310     public boolean isEntryScheduled() {
311         return mPipTransitionState.getTransitionState() == PipTransitionState.ENTRY_SCHEDULED;
312     }
313 
314     /**
315      * Registers a callback when a display change has been detected when we enter PiP.
316      */
registerOnDisplayIdChangeCallback(IntConsumer onDisplayIdChangeCallback)317     public void registerOnDisplayIdChangeCallback(IntConsumer onDisplayIdChangeCallback) {
318         mOnDisplayIdChangeCallback = onDisplayIdChangeCallback;
319     }
320 
321     /**
322      * Sets the preferred animation type for one time.
323      * This is typically used to set the animation type to
324      * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
325      */
setOneShotAnimationType(@ipAnimationController.AnimationType int animationType)326     public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) {
327         mOneShotAnimationType = animationType;
328         if (animationType == ANIM_TYPE_ALPHA) {
329             mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis();
330         }
331     }
332 
333     /**
334      * Callback when Launcher starts swipe-pip-to-home operation.
335      * @return {@link Rect} for destination bounds.
336      */
startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, PictureInPictureParams pictureInPictureParams)337     public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
338             PictureInPictureParams pictureInPictureParams) {
339         mPipTransitionState.setInSwipePipToHomeTransition(true);
340         sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
341         setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
342         return mPipBoundsAlgorithm.getEntryDestinationBounds();
343     }
344 
345     /**
346      * Callback when launcher finishes swipe-pip-to-home operation.
347      * Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
348      */
stopSwipePipToHome(ComponentName componentName, Rect destinationBounds, SurfaceControl overlay)349     public void stopSwipePipToHome(ComponentName componentName, Rect destinationBounds,
350             SurfaceControl overlay) {
351         // do nothing if there is no startSwipePipToHome being called before
352         if (mPipTransitionState.getInSwipePipToHomeTransition()) {
353             mPipBoundsState.setBounds(destinationBounds);
354             mSwipePipToHomeOverlay = overlay;
355         }
356     }
357 
getTaskInfo()358     public ActivityManager.RunningTaskInfo getTaskInfo() {
359         return mTaskInfo;
360     }
361 
getSurfaceControl()362     public SurfaceControl getSurfaceControl() {
363         return mLeash;
364     }
365 
setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params, ActivityInfo activityInfo)366     private void setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params,
367             ActivityInfo activityInfo) {
368         mPipBoundsState.setBoundsStateForEntry(componentName,
369                 mPipBoundsAlgorithm.getAspectRatioOrDefault(params),
370                 mPipBoundsAlgorithm.getMinimalSize(activityInfo));
371     }
372 
373     /**
374      * Expands PiP to the previous bounds, this is done in two phases using
375      * {@link WindowContainerTransaction}
376      * - setActivityWindowingMode to either fullscreen or split-secondary at beginning of the
377      *   transaction. without changing the windowing mode of the Task itself. This makes sure the
378      *   activity render it's final configuration while the Task is still in PiP.
379      * - setWindowingMode to undefined at the end of transition
380      * @param animationDurationMs duration in millisecond for the exiting PiP transition
381      * @param requestEnterSplit whether the enterSplit button is pressed on PiP or not.
382      *                             Indicate the user wishes to directly put PiP into split screen
383      *                             mode.
384      */
exitPip(int animationDurationMs, boolean requestEnterSplit)385     public void exitPip(int animationDurationMs, boolean requestEnterSplit) {
386         if (!mPipTransitionState.isInPip()
387                 || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP
388                 || mToken == null) {
389             Log.wtf(TAG, "Not allowed to exitPip in current state"
390                     + " mState=" + mPipTransitionState.getTransitionState() + " mToken=" + mToken);
391             return;
392         }
393 
394         mPipUiEventLoggerLogger.log(
395                 PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_EXPAND_TO_FULLSCREEN);
396         final WindowContainerTransaction wct = new WindowContainerTransaction();
397         final Rect destinationBounds = mPipBoundsState.getDisplayBounds();
398         final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
399                 ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
400                 : TRANSITION_DIRECTION_LEAVE_PIP;
401         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
402         mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds, mPipBoundsState.getBounds());
403         tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
404         // We set to fullscreen here for now, but later it will be set to UNDEFINED for
405         // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
406         wct.setActivityWindowingMode(mToken,
407                 direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN && !requestEnterSplit
408                         ? WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
409                         : WINDOWING_MODE_FULLSCREEN);
410         wct.setBounds(mToken, destinationBounds);
411         wct.setBoundsChangeTransaction(mToken, tx);
412         // Set the exiting state first so if there is fixed rotation later, the running animation
413         // won't be interrupted by alpha animation for existing PiP.
414         mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
415 
416         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
417             mPipTransitionController.startTransition(destinationBounds, wct);
418             return;
419         }
420         mSyncTransactionQueue.queue(wct);
421         mSyncTransactionQueue.runInSync(t -> {
422             // Make sure to grab the latest source hint rect as it could have been
423             // updated right after applying the windowing mode change.
424             final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
425                     mPictureInPictureParams, destinationBounds);
426             final PipAnimationController.PipTransitionAnimator<?> animator =
427                     animateResizePip(mPipBoundsState.getBounds(), destinationBounds, sourceHintRect,
428                             direction, animationDurationMs, 0 /* startingAngle */);
429             if (animator != null) {
430                 // Even though the animation was started above, re-apply the transaction for the
431                 // first frame using the SurfaceControl.Transaction supplied by the
432                 // SyncTransactionQueue. This is necessary because the initial surface transform
433                 // may not be applied until the next frame if a different Transaction than the one
434                 // supplied is used, resulting in 1 frame not being cropped to the source rect
435                 // hint during expansion that causes a visible jank/flash. See b/184166183.
436                 animator.applySurfaceControlTransaction(mLeash, t, FRACTION_START);
437             }
438         });
439     }
440 
applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction)441     private void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
442         // Reset the final windowing mode.
443         wct.setWindowingMode(mToken, getOutPipWindowingMode());
444         // Simply reset the activity mode set prior to the animation running.
445         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
446         mLegacySplitScreenOptional.ifPresent(splitScreen -> {
447             if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
448                 wct.reparent(mToken, splitScreen.getSecondaryRoot(), true /* onTop */);
449             }
450         });
451     }
452 
453     /**
454      * Removes PiP immediately.
455      */
removePip()456     public void removePip() {
457         if (!mPipTransitionState.isInPip() ||  mToken == null) {
458             Log.wtf(TAG, "Not allowed to removePip in current state"
459                     + " mState=" + mPipTransitionState.getTransitionState() + " mToken=" + mToken);
460             return;
461         }
462 
463         // removePipImmediately is expected when the following animation finishes.
464         ValueAnimator animator = mPipAnimationController
465                 .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(),
466                         1f /* alphaStart */, 0f /* alphaEnd */)
467                 .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
468                 .setPipTransactionHandler(mPipTransactionHandler)
469                 .setPipAnimationCallback(mPipAnimationCallback);
470         animator.setDuration(mExitAnimationDuration);
471         animator.setInterpolator(Interpolators.ALPHA_OUT);
472         animator.start();
473         mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
474     }
475 
removePipImmediately()476     private void removePipImmediately() {
477         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
478             final WindowContainerTransaction wct = new WindowContainerTransaction();
479             wct.setBounds(mToken, null);
480             wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
481             wct.reorder(mToken, false);
482             mPipTransitionController.startTransition(null, wct);
483             return;
484         }
485 
486         try {
487             // Reset the task bounds first to ensure the activity configuration is reset as well
488             final WindowContainerTransaction wct = new WindowContainerTransaction();
489             wct.setBounds(mToken, null);
490             mTaskOrganizer.applyTransaction(wct);
491 
492             ActivityTaskManager.getService().removeRootTasksInWindowingModes(
493                     new int[]{ WINDOWING_MODE_PINNED });
494         } catch (RemoteException e) {
495             Log.e(TAG, "Failed to remove PiP", e);
496         }
497     }
498 
499     @Override
onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash)500     public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
501         Objects.requireNonNull(info, "Requires RunningTaskInfo");
502         mTaskInfo = info;
503         mToken = mTaskInfo.token;
504         mPipTransitionState.setTransitionState(PipTransitionState.TASK_APPEARED);
505         mLeash = leash;
506         mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
507         setBoundsStateForEntry(mTaskInfo.topActivity, mPictureInPictureParams,
508                 mTaskInfo.topActivityInfo);
509 
510         mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
511         mPipUiEventLoggerLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
512 
513         // If the displayId of the task is different than what PipBoundsHandler has, then update
514         // it. This is possible if we entered PiP on an external display.
515         if (info.displayId != mPipBoundsState.getDisplayId()
516                 && mOnDisplayIdChangeCallback != null) {
517             mOnDisplayIdChangeCallback.accept(info.displayId);
518         }
519 
520         if (mPipTransitionState.getInSwipePipToHomeTransition()) {
521             if (!mWaitForFixedRotation) {
522                 onEndOfSwipePipToHomeTransition();
523             } else {
524                 Log.d(TAG, "Defer onTaskAppeared-SwipePipToHome until end of fixed rotation.");
525             }
526             return;
527         }
528 
529         if (mOneShotAnimationType == ANIM_TYPE_ALPHA
530                 && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime
531                 > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) {
532             Log.d(TAG, "Alpha animation is expired. Use bounds animation.");
533             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
534         }
535         if (mWaitForFixedRotation) {
536             onTaskAppearedWithFixedRotation();
537             return;
538         }
539 
540         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
541         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
542         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
543 
544         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
545             if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
546                 mPipMenuController.attach(mLeash);
547             } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
548                 mOneShotAnimationType = ANIM_TYPE_BOUNDS;
549             }
550             return;
551         }
552 
553         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
554             mPipMenuController.attach(mLeash);
555             final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
556                     info.pictureInPictureParams, currentBounds);
557             scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
558                     sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
559                     null /* updateBoundsCallback */);
560             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
561         } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
562             enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
563             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
564         } else {
565             throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType);
566         }
567     }
568 
onTaskAppearedWithFixedRotation()569     private void onTaskAppearedWithFixedRotation() {
570         if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
571             Log.d(TAG, "Defer entering PiP alpha animation, fixed rotation is ongoing");
572             // If deferred, hide the surface till fixed rotation is completed.
573             final SurfaceControl.Transaction tx =
574                     mSurfaceControlTransactionFactory.getTransaction();
575             tx.setAlpha(mLeash, 0f);
576             tx.show(mLeash);
577             tx.apply();
578             mOneShotAnimationType = ANIM_TYPE_BOUNDS;
579             return;
580         }
581         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
582         final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
583                 mPictureInPictureParams, currentBounds);
584         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
585         animateResizePip(currentBounds, destinationBounds, sourceHintRect,
586                 TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, 0 /* startingAngle */);
587         mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
588     }
589 
590     /**
591      * Called when the display rotation handling is skipped (e.g. when rotation happens while in
592      * the middle of an entry transition).
593      */
onDisplayRotationSkipped()594     public void onDisplayRotationSkipped() {
595         if (isEntryScheduled()) {
596             // The PIP animation is scheduled to start with the previous orientation's bounds,
597             // re-calculate the entry bounds and restart the alpha animation.
598             final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
599             enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
600         }
601     }
602 
603     @VisibleForTesting
enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs)604     void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
605         // If we are fading the PIP in, then we should move the pip to the final location as
606         // soon as possible, but set the alpha immediately since the transaction can take a
607         // while to process
608         final SurfaceControl.Transaction tx =
609                 mSurfaceControlTransactionFactory.getTransaction();
610         tx.setAlpha(mLeash, 0f);
611         tx.apply();
612         mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
613         applyEnterPipSyncTransaction(destinationBounds, () -> {
614             mPipAnimationController
615                     .getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
616                     .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
617                     .setPipAnimationCallback(mPipAnimationCallback)
618                     .setPipTransactionHandler(mPipTransactionHandler)
619                     .setDuration(durationMs)
620                     .start();
621             // mState is set right after the animation is kicked off to block any resize
622             // requests such as offsetPip that may have been called prior to the transition.
623             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
624         }, null /* boundsChangeTransaction */);
625     }
626 
onEndOfSwipePipToHomeTransition()627     private void onEndOfSwipePipToHomeTransition() {
628         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
629             mSwipePipToHomeOverlay = null;
630             return;
631         }
632 
633         final Rect destinationBounds = mPipBoundsState.getBounds();
634         final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay;
635         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
636         mSurfaceTransactionHelper
637                 .resetScale(tx, mLeash, destinationBounds)
638                 .crop(tx, mLeash, destinationBounds)
639                 .round(tx, mLeash, isInPip());
640         // The animation is finished in the Launcher and here we directly apply the final touch.
641         applyEnterPipSyncTransaction(destinationBounds, () -> {
642             // Ensure menu's settled in its final bounds first.
643             finishResizeForMenu(destinationBounds);
644             sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
645 
646             // Remove the swipe to home overlay
647             if (swipeToHomeOverlay != null) {
648                 fadeOutAndRemoveOverlay(swipeToHomeOverlay,
649                         null /* callback */, false /* withStartDelay */);
650             }
651         }, tx);
652         mPipTransitionState.setInSwipePipToHomeTransition(false);
653         mSwipePipToHomeOverlay = null;
654     }
655 
applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable, @Nullable SurfaceControl.Transaction boundsChangeTransaction)656     private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable,
657             @Nullable SurfaceControl.Transaction boundsChangeTransaction) {
658         // PiP menu is attached late in the process here to avoid any artifacts on the leash
659         // caused by addShellRoot when in gesture navigation mode.
660         mPipMenuController.attach(mLeash);
661         final WindowContainerTransaction wct = new WindowContainerTransaction();
662         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
663         wct.setBounds(mToken, destinationBounds);
664         if (boundsChangeTransaction != null) {
665             wct.setBoundsChangeTransaction(mToken, boundsChangeTransaction);
666         }
667         mSyncTransactionQueue.queue(wct);
668         if (runnable != null) {
669             mSyncTransactionQueue.runInSync(t -> runnable.run());
670         }
671     }
672 
sendOnPipTransitionStarted( @ipAnimationController.TransitionDirection int direction)673     private void sendOnPipTransitionStarted(
674             @PipAnimationController.TransitionDirection int direction) {
675         if (direction == TRANSITION_DIRECTION_TO_PIP) {
676             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
677         }
678         mPipTransitionController.sendOnPipTransitionStarted(direction);
679     }
680 
681     @VisibleForTesting
sendOnPipTransitionFinished( @ipAnimationController.TransitionDirection int direction)682     void sendOnPipTransitionFinished(
683             @PipAnimationController.TransitionDirection int direction) {
684         if (direction == TRANSITION_DIRECTION_TO_PIP) {
685             mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
686         }
687         mPipTransitionController.sendOnPipTransitionFinished(direction);
688         // Apply the deferred RunningTaskInfo if applicable after all proper callbacks are sent.
689         if (direction == TRANSITION_DIRECTION_TO_PIP && mDeferredTaskInfo != null) {
690             onTaskInfoChanged(mDeferredTaskInfo);
691             mDeferredTaskInfo = null;
692         }
693     }
694 
sendOnPipTransitionCancelled( @ipAnimationController.TransitionDirection int direction)695     private void sendOnPipTransitionCancelled(
696             @PipAnimationController.TransitionDirection int direction) {
697         mPipTransitionController.sendOnPipTransitionCancelled(direction);
698     }
699 
700     /**
701      * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int)}.
702      * Meanwhile this callback is invoked whenever the task is removed. For instance:
703      *   - as a result of removeRootTasksInWindowingModes from WM
704      *   - activity itself is died
705      * Nevertheless, we simply update the internal state here as all the heavy lifting should
706      * have been done in WM.
707      */
708     @Override
onTaskVanished(ActivityManager.RunningTaskInfo info)709     public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
710         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
711             return;
712         }
713         final WindowContainerToken token = info.token;
714         Objects.requireNonNull(token, "Requires valid WindowContainerToken");
715         if (token.asBinder() != mToken.asBinder()) {
716             Log.wtf(TAG, "Unrecognized token: " + token);
717             return;
718         }
719         clearWaitForFixedRotation();
720         mPipTransitionState.setInSwipePipToHomeTransition(false);
721         mPictureInPictureParams = null;
722         mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED);
723         // Re-set the PIP bounds to none.
724         mPipBoundsState.setBounds(new Rect());
725         mPipUiEventLoggerLogger.setTaskInfo(null);
726         mPipMenuController.detach();
727 
728         if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) {
729             mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
730         }
731 
732         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
733             mPipTransitionController.forceFinishTransition();
734         }
735         final PipAnimationController.PipTransitionAnimator<?> animator =
736                 mPipAnimationController.getCurrentAnimator();
737         if (animator != null) {
738             if (animator.getContentOverlay() != null) {
739                 removeContentOverlay(animator.getContentOverlay(), animator::clearContentOverlay);
740             }
741             animator.removeAllUpdateListeners();
742             animator.removeAllListeners();
743             animator.cancel();
744         }
745     }
746 
747     @Override
onTaskInfoChanged(ActivityManager.RunningTaskInfo info)748     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
749         Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
750         if (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP
751                 && mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP) {
752             Log.d(TAG, "Defer onTaskInfoChange in current state: "
753                     + mPipTransitionState.getTransitionState());
754             // Defer applying PiP parameters if the task is entering PiP to avoid disturbing
755             // the animation.
756             mDeferredTaskInfo = info;
757             return;
758         }
759         mPipBoundsState.setLastPipComponentName(info.topActivity);
760         mPipBoundsState.setOverrideMinSize(
761                 mPipBoundsAlgorithm.getMinimalSize(info.topActivityInfo));
762         final PictureInPictureParams newParams = info.pictureInPictureParams;
763         if (newParams == null || !applyPictureInPictureParams(newParams)) {
764             Log.d(TAG, "Ignored onTaskInfoChanged with PiP param: " + newParams);
765             return;
766         }
767         // Aspect ratio changed, re-calculate bounds if valid.
768         final Rect destinationBounds = mPipBoundsAlgorithm.getAdjustedDestinationBounds(
769                 mPipBoundsState.getBounds(), mPipBoundsState.getAspectRatio());
770         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
771         scheduleAnimateResizePip(destinationBounds, mEnterAnimationDuration,
772                 null /* updateBoundsCallback */);
773     }
774 
775     @Override
onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo)776     public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
777         mPipMenuController.onFocusTaskChanged(taskInfo);
778     }
779 
780     @Override
supportCompatUI()781     public boolean supportCompatUI() {
782         // PIP doesn't support compat.
783         return false;
784     }
785 
786     @Override
onFixedRotationStarted(int displayId, int newRotation)787     public void onFixedRotationStarted(int displayId, int newRotation) {
788         mNextRotation = newRotation;
789         mWaitForFixedRotation = true;
790 
791         if (mPipTransitionState.isInPip()) {
792             // Fade out the existing PiP to avoid jump cut during seamless rotation.
793             fadeExistingPip(false /* show */);
794         }
795     }
796 
797     @Override
onFixedRotationFinished(int displayId)798     public void onFixedRotationFinished(int displayId) {
799         if (!mWaitForFixedRotation) {
800             return;
801         }
802         if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) {
803             if (mPipTransitionState.getInSwipePipToHomeTransition()) {
804                 onEndOfSwipePipToHomeTransition();
805             } else {
806                 // Schedule a regular animation to ensure all the callbacks are still being sent.
807                 enterPipWithAlphaAnimation(mPipBoundsAlgorithm.getEntryDestinationBounds(),
808                         mEnterAnimationDuration);
809             }
810         } else if (mPipTransitionState.getTransitionState() == PipTransitionState.ENTERED_PIP
811                 && mHasFadeOut) {
812             fadeExistingPip(true /* show */);
813         } else if (mPipTransitionState.getTransitionState() == PipTransitionState.ENTERING_PIP
814                 && mDeferredAnimEndTransaction != null) {
815             final PipAnimationController.PipTransitionAnimator<?> animator =
816                     mPipAnimationController.getCurrentAnimator();
817             final Rect destinationBounds = animator.getDestinationBounds();
818             mPipBoundsState.setBounds(destinationBounds);
819             applyEnterPipSyncTransaction(destinationBounds, () -> {
820                 finishResizeForMenu(destinationBounds);
821                 sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
822             }, mDeferredAnimEndTransaction);
823         }
824         clearWaitForFixedRotation();
825     }
826 
fadeExistingPip(boolean show)827     private void fadeExistingPip(boolean show) {
828         if (mLeash == null || !mLeash.isValid()) {
829             Log.w(TAG, "Invalid leash on fadeExistingPip: " + mLeash);
830             return;
831         }
832         final float alphaStart = show ? 0 : 1;
833         final float alphaEnd = show ? 1 : 0;
834         mPipAnimationController
835                 .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), alphaStart, alphaEnd)
836                 .setTransitionDirection(TRANSITION_DIRECTION_SAME)
837                 .setPipTransactionHandler(mPipTransactionHandler)
838                 .setDuration(show ? mEnterAnimationDuration : mExitAnimationDuration)
839                 .start();
840         mHasFadeOut = !show;
841     }
842 
clearWaitForFixedRotation()843     private void clearWaitForFixedRotation() {
844         mWaitForFixedRotation = false;
845         mDeferredAnimEndTransaction = null;
846     }
847 
848     @Override
onDisplayConfigurationChanged(int displayId, Configuration newConfig)849     public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
850         mCurrentRotation = newConfig.windowConfiguration.getRotation();
851     }
852 
853     /**
854      * Called when display size or font size of settings changed
855      */
onDensityOrFontScaleChanged(Context context)856     public void onDensityOrFontScaleChanged(Context context) {
857         mSurfaceTransactionHelper.onDensityOrFontScaleChanged(context);
858     }
859 
860     /**
861      * TODO(b/152809058): consolidate the display info handling logic in SysUI
862      *
863      * @param destinationBoundsOut the current destination bounds will be populated to this param
864      */
865     @SuppressWarnings("unchecked")
onMovementBoundsChanged(Rect destinationBoundsOut, boolean fromRotation, boolean fromImeAdjustment, boolean fromShelfAdjustment, WindowContainerTransaction wct)866     public void onMovementBoundsChanged(Rect destinationBoundsOut, boolean fromRotation,
867             boolean fromImeAdjustment, boolean fromShelfAdjustment,
868             WindowContainerTransaction wct) {
869         // note that this can be called when swipe-to-home or fixed-rotation is happening.
870         // Skip this entirely if that's the case.
871         final boolean waitForFixedRotationOnEnteringPip = mWaitForFixedRotation
872                 && (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP);
873         if ((mPipTransitionState.getInSwipePipToHomeTransition()
874                 || waitForFixedRotationOnEnteringPip) && fromRotation) {
875             if (DEBUG) {
876                 Log.d(TAG, "Skip onMovementBoundsChanged on rotation change"
877                         + " InSwipePipToHomeTransition="
878                         + mPipTransitionState.getInSwipePipToHomeTransition()
879                         + " mWaitForFixedRotation=" + mWaitForFixedRotation
880                         + " getTransitionState=" + mPipTransitionState.getTransitionState());
881             }
882             return;
883         }
884         final PipAnimationController.PipTransitionAnimator animator =
885                 mPipAnimationController.getCurrentAnimator();
886         if (animator == null || !animator.isRunning()
887                 || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
888             final boolean rotatingPip = mPipTransitionState.isInPip() && fromRotation;
889             if (rotatingPip && mWaitForFixedRotation && mHasFadeOut) {
890                 // The position will be used by fade-in animation when the fixed rotation is done.
891                 mPipBoundsState.setBounds(destinationBoundsOut);
892             } else if (rotatingPip) {
893                 // Update bounds state to final destination first. It's important to do this
894                 // before finishing & cancelling the transition animation so that the MotionHelper
895                 // bounds are synchronized to the destination bounds when the animation ends.
896                 mPipBoundsState.setBounds(destinationBoundsOut);
897                 // If we are rotating while there is a current animation, immediately cancel the
898                 // animation (remove the listeners so we don't trigger the normal finish resize
899                 // call that should only happen on the update thread)
900                 int direction = TRANSITION_DIRECTION_NONE;
901                 if (animator != null) {
902                     direction = animator.getTransitionDirection();
903                     animator.removeAllUpdateListeners();
904                     animator.removeAllListeners();
905                     animator.cancel();
906                     // Do notify the listeners that this was canceled
907                     sendOnPipTransitionCancelled(direction);
908                     sendOnPipTransitionFinished(direction);
909                 }
910 
911                 // Create a reset surface transaction for the new bounds and update the window
912                 // container transaction
913                 final SurfaceControl.Transaction tx = createFinishResizeSurfaceTransaction(
914                         destinationBoundsOut);
915                 prepareFinishResizeTransaction(destinationBoundsOut, direction, tx, wct);
916             } else  {
917                 // There could be an animation on-going. If there is one on-going, last-reported
918                 // bounds isn't yet updated. We'll use the animator's bounds instead.
919                 if (animator != null && animator.isRunning()) {
920                     if (!animator.getDestinationBounds().isEmpty()) {
921                         destinationBoundsOut.set(animator.getDestinationBounds());
922                     }
923                 } else {
924                     if (!mPipBoundsState.getBounds().isEmpty()) {
925                         destinationBoundsOut.set(mPipBoundsState.getBounds());
926                     }
927                 }
928             }
929             return;
930         }
931 
932         final Rect currentDestinationBounds = animator.getDestinationBounds();
933         destinationBoundsOut.set(currentDestinationBounds);
934         if (!fromImeAdjustment && !fromShelfAdjustment
935                 && mPipBoundsState.getDisplayBounds().contains(currentDestinationBounds)) {
936             // no need to update the destination bounds, bail early
937             return;
938         }
939 
940         final Rect newDestinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
941         if (newDestinationBounds.equals(currentDestinationBounds)) return;
942         if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
943             if (mWaitForFixedRotation) {
944                 // The new destination bounds are in next rotation (DisplayLayout has been rotated
945                 // in computeRotatedBounds). The animation runs in previous rotation so the end
946                 // bounds need to be transformed.
947                 final Rect displayBounds = mPipBoundsState.getDisplayBounds();
948                 final Rect rotatedEndBounds = new Rect(newDestinationBounds);
949                 rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
950                 animator.updateEndValue(rotatedEndBounds);
951             } else {
952                 animator.updateEndValue(newDestinationBounds);
953             }
954         }
955         animator.setDestinationBounds(newDestinationBounds);
956         destinationBoundsOut.set(newDestinationBounds);
957     }
958 
959     /**
960      * @return {@code true} if the aspect ratio is changed since no other parameters within
961      * {@link PictureInPictureParams} would affect the bounds.
962      */
applyPictureInPictureParams(@onNull PictureInPictureParams params)963     private boolean applyPictureInPictureParams(@NonNull PictureInPictureParams params) {
964         final Rational currentAspectRatio =
965                 mPictureInPictureParams != null ? mPictureInPictureParams.getAspectRatioRational()
966                         : null;
967         final boolean aspectRatioChanged = !Objects.equals(currentAspectRatio,
968                 params.getAspectRatioRational());
969         mPictureInPictureParams = params;
970         if (aspectRatioChanged) {
971             mPipBoundsState.setAspectRatio(params.getAspectRatio());
972         }
973         return aspectRatioChanged;
974     }
975 
976     /**
977      * Animates resizing of the pinned stack given the duration.
978      */
scheduleAnimateResizePip(Rect toBounds, int duration, Consumer<Rect> updateBoundsCallback)979     public void scheduleAnimateResizePip(Rect toBounds, int duration,
980             Consumer<Rect> updateBoundsCallback) {
981         scheduleAnimateResizePip(toBounds, duration, TRANSITION_DIRECTION_NONE,
982                 updateBoundsCallback);
983     }
984 
985     /**
986      * Animates resizing of the pinned stack given the duration.
987      */
scheduleAnimateResizePip(Rect toBounds, int duration, @PipAnimationController.TransitionDirection int direction, Consumer<Rect> updateBoundsCallback)988     public void scheduleAnimateResizePip(Rect toBounds, int duration,
989             @PipAnimationController.TransitionDirection int direction,
990             Consumer<Rect> updateBoundsCallback) {
991         if (mWaitForFixedRotation) {
992             Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
993             return;
994         }
995         scheduleAnimateResizePip(mPipBoundsState.getBounds(), toBounds, 0 /* startingAngle */,
996                 null /* sourceHintRect */, direction, duration, updateBoundsCallback);
997     }
998 
999     /**
1000      * Animates resizing of the pinned stack given the duration and start bounds.
1001      * This is used when the starting bounds is not the current PiP bounds.
1002      */
scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration, float startingAngle, Consumer<Rect> updateBoundsCallback)1003     public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
1004             float startingAngle, Consumer<Rect> updateBoundsCallback) {
1005         if (mWaitForFixedRotation) {
1006             Log.d(TAG, "skip scheduleAnimateResizePip, entering pip deferred");
1007             return;
1008         }
1009         scheduleAnimateResizePip(fromBounds, toBounds, startingAngle, null /* sourceHintRect */,
1010                 TRANSITION_DIRECTION_SNAP_AFTER_RESIZE, duration, updateBoundsCallback);
1011     }
1012 
1013     /**
1014      * Animates resizing of the pinned stack given the duration and start bounds.
1015      * This always animates the angle to zero from the starting angle.
1016      */
scheduleAnimateResizePip( Rect currentBounds, Rect destinationBounds, float startingAngle, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, int durationMs, Consumer<Rect> updateBoundsCallback)1017     private @Nullable PipAnimationController.PipTransitionAnimator<?> scheduleAnimateResizePip(
1018             Rect currentBounds, Rect destinationBounds, float startingAngle, Rect sourceHintRect,
1019             @PipAnimationController.TransitionDirection int direction, int durationMs,
1020             Consumer<Rect> updateBoundsCallback) {
1021         if (!mPipTransitionState.isInPip()) {
1022             // TODO: tend to use shouldBlockResizeRequest here as well but need to consider
1023             // the fact that when in exitPip, scheduleAnimateResizePip is executed in the window
1024             // container transaction callback and we want to set the mState immediately.
1025             return null;
1026         }
1027 
1028         final PipAnimationController.PipTransitionAnimator<?> animator = animateResizePip(
1029                 currentBounds, destinationBounds, sourceHintRect, direction, durationMs,
1030                 startingAngle);
1031         if (updateBoundsCallback != null) {
1032             updateBoundsCallback.accept(destinationBounds);
1033         }
1034         return animator;
1035     }
1036 
1037     /**
1038      * Directly perform manipulation/resize on the leash. This will not perform any
1039      * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
1040      */
scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback)1041     public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) {
1042         // Could happen when exitPip
1043         if (mToken == null || mLeash == null) {
1044             Log.w(TAG, "Abort animation, invalid leash");
1045             return;
1046         }
1047         mPipBoundsState.setBounds(toBounds);
1048         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
1049         mSurfaceTransactionHelper
1050                 .crop(tx, mLeash, toBounds)
1051                 .round(tx, mLeash, mPipTransitionState.isInPip());
1052         if (mPipMenuController.isMenuVisible()) {
1053             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
1054         } else {
1055             tx.apply();
1056         }
1057         if (updateBoundsCallback != null) {
1058             updateBoundsCallback.accept(toBounds);
1059         }
1060     }
1061 
1062     /**
1063      * Directly perform manipulation/resize on the leash, along with rotation. This will not perform
1064      * any {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
1065      */
scheduleUserResizePip(Rect startBounds, Rect toBounds, Consumer<Rect> updateBoundsCallback)1066     public void scheduleUserResizePip(Rect startBounds, Rect toBounds,
1067             Consumer<Rect> updateBoundsCallback) {
1068         scheduleUserResizePip(startBounds, toBounds, 0 /* degrees */, updateBoundsCallback);
1069     }
1070 
1071     /**
1072      * Directly perform a scaled matrix transformation on the leash. This will not perform any
1073      * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
1074      */
scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees, Consumer<Rect> updateBoundsCallback)1075     public void scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees,
1076             Consumer<Rect> updateBoundsCallback) {
1077         // Could happen when exitPip
1078         if (mToken == null || mLeash == null) {
1079             Log.w(TAG, "Abort animation, invalid leash");
1080             return;
1081         }
1082 
1083         if (startBounds.isEmpty() || toBounds.isEmpty()) {
1084             Log.w(TAG, "Attempted to user resize PIP to or from empty bounds, aborting.");
1085             return;
1086         }
1087 
1088         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
1089         mSurfaceTransactionHelper
1090                 .scale(tx, mLeash, startBounds, toBounds, degrees)
1091                 .round(tx, mLeash, startBounds, toBounds);
1092         if (mPipMenuController.isMenuVisible()) {
1093             mPipMenuController.movePipMenu(mLeash, tx, toBounds);
1094         } else {
1095             tx.apply();
1096         }
1097         if (updateBoundsCallback != null) {
1098             updateBoundsCallback.accept(toBounds);
1099         }
1100     }
1101 
1102     /**
1103      * Finish an intermediate resize operation. This is expected to be called after
1104      * {@link #scheduleResizePip}.
1105      */
scheduleFinishResizePip(Rect destinationBounds)1106     public void scheduleFinishResizePip(Rect destinationBounds) {
1107         scheduleFinishResizePip(destinationBounds, null /* updateBoundsCallback */);
1108     }
1109 
1110     /**
1111      * Same as {@link #scheduleFinishResizePip} but with a callback.
1112      */
scheduleFinishResizePip(Rect destinationBounds, Consumer<Rect> updateBoundsCallback)1113     public void scheduleFinishResizePip(Rect destinationBounds,
1114             Consumer<Rect> updateBoundsCallback) {
1115         scheduleFinishResizePip(destinationBounds, TRANSITION_DIRECTION_NONE, updateBoundsCallback);
1116     }
1117 
1118     /**
1119      * Finish an intermediate resize operation. This is expected to be called after
1120      * {@link #scheduleResizePip}.
1121      *
1122      * @param destinationBounds the final bounds of the PIP after resizing
1123      * @param direction the transition direction
1124      * @param updateBoundsCallback a callback to invoke after finishing the resize
1125      */
scheduleFinishResizePip(Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, Consumer<Rect> updateBoundsCallback)1126     public void scheduleFinishResizePip(Rect destinationBounds,
1127             @PipAnimationController.TransitionDirection int direction,
1128             Consumer<Rect> updateBoundsCallback) {
1129         if (mPipTransitionState.shouldBlockResizeRequest()) {
1130             return;
1131         }
1132 
1133         finishResize(createFinishResizeSurfaceTransaction(destinationBounds), destinationBounds,
1134                 direction, -1);
1135         if (updateBoundsCallback != null) {
1136             updateBoundsCallback.accept(destinationBounds);
1137         }
1138     }
1139 
createFinishResizeSurfaceTransaction( Rect destinationBounds)1140     private SurfaceControl.Transaction createFinishResizeSurfaceTransaction(
1141             Rect destinationBounds) {
1142         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
1143         mSurfaceTransactionHelper
1144                 .crop(tx, mLeash, destinationBounds)
1145                 .resetScale(tx, mLeash, destinationBounds)
1146                 .round(tx, mLeash, mPipTransitionState.isInPip());
1147         return tx;
1148     }
1149 
1150     /**
1151      * Offset the PiP window by a given offset on Y-axis, triggered also from screen rotation.
1152      */
scheduleOffsetPip(Rect originalBounds, int offset, int duration, Consumer<Rect> updateBoundsCallback)1153     public void scheduleOffsetPip(Rect originalBounds, int offset, int duration,
1154             Consumer<Rect> updateBoundsCallback) {
1155         if (mPipTransitionState.shouldBlockResizeRequest()) {
1156             return;
1157         }
1158         if (mWaitForFixedRotation) {
1159             Log.d(TAG, "skip scheduleOffsetPip, entering pip deferred");
1160             return;
1161         }
1162         offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
1163         Rect toBounds = new Rect(originalBounds);
1164         toBounds.offset(0, offset);
1165         if (updateBoundsCallback != null) {
1166             updateBoundsCallback.accept(toBounds);
1167         }
1168     }
1169 
offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs)1170     private void offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs) {
1171         if (mTaskInfo == null) {
1172             Log.w(TAG, "mTaskInfo is not set");
1173             return;
1174         }
1175         final Rect destinationBounds = new Rect(originalBounds);
1176         destinationBounds.offset(xOffset, yOffset);
1177         animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */,
1178                 TRANSITION_DIRECTION_SAME, durationMs, 0);
1179     }
1180 
finishResize(SurfaceControl.Transaction tx, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, @PipAnimationController.AnimationType int type)1181     private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds,
1182             @PipAnimationController.TransitionDirection int direction,
1183             @PipAnimationController.AnimationType int type) {
1184         final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds());
1185         final boolean isPipTopLeft = isPipTopLeft();
1186         mPipBoundsState.setBounds(destinationBounds);
1187         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
1188             removePipImmediately();
1189             return;
1190         } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
1191             // TODO: Synchronize this correctly in #applyEnterPipSyncTransaction
1192             finishResizeForMenu(destinationBounds);
1193             return;
1194         }
1195 
1196         WindowContainerTransaction wct = new WindowContainerTransaction();
1197         prepareFinishResizeTransaction(destinationBounds, direction, tx, wct);
1198 
1199         // Only corner drag, pinch or expand/un-expand resizing may lead to animating the finish
1200         // resize operation.
1201         final boolean mayAnimateFinishResize = direction == TRANSITION_DIRECTION_USER_RESIZE
1202                 || direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
1203                 || direction == TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
1204         // Animate with a cross-fade if enabled and seamless resize is disables by the app.
1205         final boolean animateCrossFadeResize = mayAnimateFinishResize
1206                 && mPictureInPictureParams != null
1207                 && !mPictureInPictureParams.isSeamlessResizeEnabled();
1208         if (animateCrossFadeResize) {
1209             // Take a snapshot of the PIP task and show it. We'll fade it out after the wct
1210             // transaction is applied and the activity is laid out again.
1211             preResizeBounds.offsetTo(0, 0);
1212             final Rect snapshotDest = new Rect(0, 0, destinationBounds.width(),
1213                     destinationBounds.height());
1214             // Note: Put this at layer=MAX_VALUE-2 since the input consumer for PIP is placed at
1215             //       MAX_VALUE-1
1216             final SurfaceControl snapshotSurface = ScreenshotUtils.takeScreenshot(
1217                     mSurfaceControlTransactionFactory.getTransaction(), mLeash, preResizeBounds,
1218                     Integer.MAX_VALUE - 2);
1219             if (snapshotSurface != null) {
1220                 mSyncTransactionQueue.queue(wct);
1221                 mSyncTransactionQueue.runInSync(t -> {
1222                     // Scale the snapshot from its pre-resize bounds to the post-resize bounds.
1223                     mSurfaceTransactionHelper.scale(t, snapshotSurface, preResizeBounds,
1224                             snapshotDest);
1225 
1226                     // Start animation to fade out the snapshot.
1227                     fadeOutAndRemoveOverlay(snapshotSurface,
1228                             null /* callback */, false /* withStartDelay */);
1229                 });
1230             } else {
1231                 applyFinishBoundsResize(wct, direction, isPipTopLeft);
1232             }
1233         } else {
1234             applyFinishBoundsResize(wct, direction, isPipTopLeft);
1235         }
1236 
1237         finishResizeForMenu(destinationBounds);
1238     }
1239 
1240     /** Moves the PiP menu to the destination bounds. */
finishResizeForMenu(Rect destinationBounds)1241     public void finishResizeForMenu(Rect destinationBounds) {
1242         if (!isInPip()) {
1243             return;
1244         }
1245         mPipMenuController.movePipMenu(null, null, destinationBounds);
1246         mPipMenuController.updateMenuBounds(destinationBounds);
1247     }
1248 
prepareFinishResizeTransaction(Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, SurfaceControl.Transaction tx, WindowContainerTransaction wct)1249     private void prepareFinishResizeTransaction(Rect destinationBounds,
1250             @PipAnimationController.TransitionDirection int direction,
1251             SurfaceControl.Transaction tx,
1252             WindowContainerTransaction wct) {
1253         final Rect taskBounds;
1254         if (isInPipDirection(direction)) {
1255             // If we are animating from fullscreen using a bounds animation, then reset the
1256             // activity windowing mode set by WM, and set the task bounds to the final bounds
1257             taskBounds = destinationBounds;
1258             wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
1259         } else if (isOutPipDirection(direction)) {
1260             // If we are animating to fullscreen or split screen, then we need to reset the
1261             // override bounds on the task to ensure that the task "matches" the parent's bounds.
1262             taskBounds = null;
1263             applyWindowingModeChangeOnExit(wct, direction);
1264         } else {
1265             // Just a resize in PIP
1266             taskBounds = destinationBounds;
1267         }
1268         mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
1269 
1270         wct.setBounds(mToken, taskBounds);
1271         wct.setBoundsChangeTransaction(mToken, tx);
1272     }
1273 
1274     /**
1275      * Applies the window container transaction to finish a bounds resize.
1276      *
1277      * Called by {@link #finishResize(SurfaceControl.Transaction, Rect, int, int)}} once it has
1278      * finished preparing the transaction. It allows subclasses to modify the transaction before
1279      * applying it.
1280      */
applyFinishBoundsResize(@onNull WindowContainerTransaction wct, @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft)1281     public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct,
1282             @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) {
1283         if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
1284             mSplitScreenOptional.get().enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct);
1285         } else {
1286             mTaskOrganizer.applyTransaction(wct);
1287         }
1288     }
1289 
isPipTopLeft()1290     private boolean isPipTopLeft() {
1291         if (!mSplitScreenOptional.isPresent()) {
1292             return false;
1293         }
1294         final Rect topLeft = new Rect();
1295         final Rect bottomRight = new Rect();
1296         mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
1297 
1298         return topLeft.contains(mPipBoundsState.getBounds());
1299     }
1300 
1301     /**
1302      * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
1303      * and can be overridden to restore to an alternate windowing mode.
1304      */
getOutPipWindowingMode()1305     public int getOutPipWindowingMode() {
1306         // By default, simply reset the windowing mode to undefined.
1307         return WINDOWING_MODE_UNDEFINED;
1308     }
1309 
animateResizePip( Rect currentBounds, Rect destinationBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, int durationMs, float startingAngle)1310     private @Nullable PipAnimationController.PipTransitionAnimator<?> animateResizePip(
1311             Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
1312             @PipAnimationController.TransitionDirection int direction, int durationMs,
1313             float startingAngle) {
1314         // Could happen when exitPip
1315         if (mToken == null || mLeash == null) {
1316             Log.w(TAG, "Abort animation, invalid leash");
1317             return null;
1318         }
1319         final int rotationDelta = mWaitForFixedRotation
1320                 ? deltaRotation(mCurrentRotation, mNextRotation)
1321                 : Surface.ROTATION_0;
1322         if (rotationDelta != Surface.ROTATION_0) {
1323             sourceHintRect = computeRotatedBounds(rotationDelta, direction, destinationBounds,
1324                     sourceHintRect);
1325         }
1326         Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
1327                 ? mPipBoundsState.getBounds() : currentBounds;
1328         final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
1329                 && mPipAnimationController.getCurrentAnimator().isRunning();
1330         final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
1331                 .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
1332                         sourceHintRect, direction, startingAngle, rotationDelta);
1333         animator.setTransitionDirection(direction)
1334                 .setPipTransactionHandler(mPipTransactionHandler)
1335                 .setDuration(durationMs);
1336         if (!existingAnimatorRunning) {
1337             animator.setPipAnimationCallback(mPipAnimationCallback);
1338         }
1339         if (isInPipDirection(direction)) {
1340             // Similar to auto-enter-pip transition, we use content overlay when there is no
1341             // source rect hint to enter PiP use bounds animation.
1342             if (sourceHintRect == null) animator.setUseContentOverlay(mContext);
1343             // The destination bounds are used for the end rect of animation and the final bounds
1344             // after animation finishes. So after the animation is started, the destination bounds
1345             // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
1346             // without affecting the animation.
1347             if (rotationDelta != Surface.ROTATION_0) {
1348                 animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
1349             }
1350         }
1351         animator.start();
1352         return animator;
1353     }
1354 
1355     /** Computes destination bounds in old rotation and returns source hint rect if available. */
computeRotatedBounds(int rotationDelta, int direction, Rect outDestinationBounds, Rect sourceHintRect)1356     private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
1357             Rect outDestinationBounds, Rect sourceHintRect) {
1358         if (direction == TRANSITION_DIRECTION_TO_PIP) {
1359             mPipBoundsState.getDisplayLayout().rotateTo(mContext.getResources(), mNextRotation);
1360             final Rect displayBounds = mPipBoundsState.getDisplayBounds();
1361             outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
1362             // Transform the destination bounds to current display coordinates.
1363             rotateBounds(outDestinationBounds, displayBounds, mNextRotation, mCurrentRotation);
1364             // When entering PiP (from button navigation mode), adjust the source rect hint by
1365             // display cutout if applicable.
1366             if (sourceHintRect != null && mTaskInfo.displayCutoutInsets != null) {
1367                 if (rotationDelta == Surface.ROTATION_270) {
1368                     sourceHintRect.offset(mTaskInfo.displayCutoutInsets.left,
1369                             mTaskInfo.displayCutoutInsets.top);
1370                 }
1371             }
1372         } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
1373             final Rect rotatedDestinationBounds = new Rect(outDestinationBounds);
1374             rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(),
1375                     rotationDelta);
1376             return PipBoundsAlgorithm.getValidSourceHintRect(mPictureInPictureParams,
1377                     rotatedDestinationBounds);
1378         }
1379         return sourceHintRect;
1380     }
1381 
1382     /**
1383      * Sync with {@link LegacySplitScreenController} or {@link SplitScreenController} on destination
1384      * bounds if PiP is going to split screen.
1385      *
1386      * @param destinationBoundsOut contain the updated destination bounds if applicable
1387      * @return {@code true} if destinationBounds is altered for split screen
1388      */
syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit)1389     private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) {
1390         if (enterSplit && mSplitScreenOptional.isPresent()) {
1391             final Rect topLeft = new Rect();
1392             final Rect bottomRight = new Rect();
1393             mSplitScreenOptional.get().getStageBounds(topLeft, bottomRight);
1394             final boolean isPipTopLeft = isPipTopLeft();
1395             destinationBoundsOut.set(isPipTopLeft ? topLeft : bottomRight);
1396             return true;
1397         }
1398 
1399         if (!mLegacySplitScreenOptional.isPresent()) {
1400             return false;
1401         }
1402 
1403         LegacySplitScreenController legacySplitScreen = mLegacySplitScreenOptional.get();
1404         if (!legacySplitScreen.isDividerVisible()) {
1405             // fail early if system is not in split screen mode
1406             return false;
1407         }
1408 
1409         // PiP window will go to split-secondary mode instead of fullscreen, populates the
1410         // split screen bounds here.
1411         destinationBoundsOut.set(legacySplitScreen.getDividerView()
1412                 .getNonMinimizedSplitScreenSecondaryBounds());
1413         return true;
1414     }
1415 
1416     /**
1417      * Fades out and removes an overlay surface.
1418      */
fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback, boolean withStartDelay)1419     private void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
1420             boolean withStartDelay) {
1421         if (surface == null) {
1422             return;
1423         }
1424 
1425         final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
1426         animator.setDuration(mCrossFadeAnimationDuration);
1427         animator.addUpdateListener(animation -> {
1428             if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
1429                 // Could happen if onTaskVanished happens during the animation since we may have
1430                 // set a start delay on this animation.
1431                 Log.d(TAG, "Task vanished, skip fadeOutAndRemoveOverlay");
1432                 animation.removeAllListeners();
1433                 animation.removeAllUpdateListeners();
1434                 animation.cancel();
1435             } else {
1436                 final float alpha = (float) animation.getAnimatedValue();
1437                 final SurfaceControl.Transaction transaction =
1438                         mSurfaceControlTransactionFactory.getTransaction();
1439                 transaction.setAlpha(surface, alpha);
1440                 transaction.apply();
1441             }
1442         });
1443         animator.addListener(new AnimatorListenerAdapter() {
1444             @Override
1445             public void onAnimationEnd(Animator animation) {
1446                 removeContentOverlay(surface, callback);
1447             }
1448         });
1449         animator.setStartDelay(withStartDelay ? CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
1450         animator.start();
1451     }
1452 
removeContentOverlay(SurfaceControl surface, Runnable callback)1453     private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
1454         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
1455             // Avoid double removal, which is fatal.
1456             return;
1457         }
1458         final SurfaceControl.Transaction tx =
1459                 mSurfaceControlTransactionFactory.getTransaction();
1460         tx.remove(surface);
1461         tx.apply();
1462         if (callback != null) callback.run();
1463     }
1464 
1465     /**
1466      * Dumps internal states.
1467      */
1468     @Override
dump(PrintWriter pw, String prefix)1469     public void dump(PrintWriter pw, String prefix) {
1470         final String innerPrefix = prefix + "  ";
1471         pw.println(prefix + TAG);
1472         pw.println(innerPrefix + "mTaskInfo=" + mTaskInfo);
1473         pw.println(innerPrefix + "mToken=" + mToken
1474                 + " binder=" + (mToken != null ? mToken.asBinder() : null));
1475         pw.println(innerPrefix + "mLeash=" + mLeash);
1476         pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
1477         pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType);
1478         pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
1479     }
1480 
1481     @Override
toString()1482     public String toString() {
1483         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_PIP);
1484     }
1485 }
1486