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.ActivityTaskManager.INVALID_TASK_ID;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
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.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
29 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
30 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
31 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
32 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
33 import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
34 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
35 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
36 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN;
37 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_NONE;
38 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
39 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME;
40 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SNAP_AFTER_RESIZE;
41 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
42 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
43 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
44 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
45 import static com.android.wm.shell.pip.PipAnimationController.isRemovePipDirection;
46 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
47 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
48 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
49 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
50 
51 import android.animation.Animator;
52 import android.animation.AnimatorListenerAdapter;
53 import android.animation.ValueAnimator;
54 import android.annotation.NonNull;
55 import android.annotation.Nullable;
56 import android.app.ActivityManager;
57 import android.app.ActivityTaskManager;
58 import android.app.PictureInPictureParams;
59 import android.app.TaskInfo;
60 import android.content.ComponentName;
61 import android.content.Context;
62 import android.content.pm.ActivityInfo;
63 import android.content.res.Configuration;
64 import android.graphics.Rect;
65 import android.os.RemoteException;
66 import android.view.Choreographer;
67 import android.view.Display;
68 import android.view.Surface;
69 import android.view.SurfaceControl;
70 import android.window.TaskOrganizer;
71 import android.window.TaskSnapshot;
72 import android.window.WindowContainerToken;
73 import android.window.WindowContainerTransaction;
74 
75 import com.android.internal.annotations.VisibleForTesting;
76 import com.android.internal.protolog.common.ProtoLog;
77 import com.android.wm.shell.R;
78 import com.android.wm.shell.ShellTaskOrganizer;
79 import com.android.wm.shell.animation.Interpolators;
80 import com.android.wm.shell.common.DisplayController;
81 import com.android.wm.shell.common.ScreenshotUtils;
82 import com.android.wm.shell.common.ShellExecutor;
83 import com.android.wm.shell.common.SyncTransactionQueue;
84 import com.android.wm.shell.common.annotations.ShellMainThread;
85 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
86 import com.android.wm.shell.common.pip.PipBoundsState;
87 import com.android.wm.shell.common.pip.PipDisplayLayoutState;
88 import com.android.wm.shell.common.pip.PipUiEventLogger;
89 import com.android.wm.shell.common.pip.PipUtils;
90 import com.android.wm.shell.pip.phone.PipMotionHelper;
91 import com.android.wm.shell.protolog.ShellProtoLogGroup;
92 import com.android.wm.shell.splitscreen.SplitScreenController;
93 import com.android.wm.shell.transition.Transitions;
94 
95 import java.io.PrintWriter;
96 import java.util.Objects;
97 import java.util.Optional;
98 import java.util.function.Consumer;
99 import java.util.function.IntConsumer;
100 
101 /**
102  * Manages PiP tasks such as resize and offset.
103  *
104  * This class listens on {@link TaskOrganizer} callbacks for windowing mode change
105  * both to and from PiP and issues corresponding animation if applicable.
106  * Normally, we apply series of {@link SurfaceControl.Transaction} when the animator is running
107  * and files a final {@link WindowContainerTransaction} at the end of the transition.
108  *
109  * This class is also responsible for general resize/offset PiP operations within SysUI component,
110  * see also {@link PipMotionHelper}.
111  */
112 public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
113         DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener {
114     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
115 
116     /**
117      * The fixed start delay in ms when fading out the content overlay from bounds animation.
118      * This is to overcome the flicker caused by configuration change when rotating from landscape
119      * to portrait PiP in button navigation mode.
120      */
121     private static final int CONTENT_OVERLAY_FADE_OUT_DELAY_MS = 500;
122 
123     private final Context mContext;
124     private final SyncTransactionQueue mSyncTransactionQueue;
125     private final PipBoundsState mPipBoundsState;
126     private final PipDisplayLayoutState mPipDisplayLayoutState;
127     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
128     private final @NonNull PipMenuController mPipMenuController;
129     private final PipAnimationController mPipAnimationController;
130     protected final PipTransitionController mPipTransitionController;
131     protected final PipParamsChangedForwarder mPipParamsChangedForwarder;
132     private final PipUiEventLogger mPipUiEventLoggerLogger;
133     private final int mEnterAnimationDuration;
134     private final int mExitAnimationDuration;
135     private final int mCrossFadeAnimationDuration;
136     private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
137     private final Optional<SplitScreenController> mSplitScreenOptional;
138     protected final ShellTaskOrganizer mTaskOrganizer;
139     protected final ShellExecutor mMainExecutor;
140 
141     // the runnable to execute after WindowContainerTransactions is applied to finish resizing pip
142     private Runnable mPipFinishResizeWCTRunnable;
143 
maybePerformFinishResizeCallback()144     private void maybePerformFinishResizeCallback() {
145         if (mPipFinishResizeWCTRunnable != null) {
146             mPipFinishResizeWCTRunnable.run();
147             mPipFinishResizeWCTRunnable = null;
148         }
149     }
150 
151     // These callbacks are called on the update thread
152     private final PipAnimationController.PipAnimationCallback mPipAnimationCallback =
153             new PipAnimationController.PipAnimationCallback() {
154         private boolean mIsCancelled;
155         @Override
156         public void onPipAnimationStart(TaskInfo taskInfo,
157                 PipAnimationController.PipTransitionAnimator animator) {
158             final int direction = animator.getTransitionDirection();
159             mIsCancelled = false;
160             sendOnPipTransitionStarted(direction);
161         }
162 
163         @Override
164         public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx,
165                 PipAnimationController.PipTransitionAnimator animator) {
166             final int direction = animator.getTransitionDirection();
167             if (mIsCancelled) {
168                 sendOnPipTransitionFinished(direction);
169                 maybePerformFinishResizeCallback();
170                 return;
171             }
172             final int animationType = animator.getAnimationType();
173             final Rect destinationBounds = animator.getDestinationBounds();
174             if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
175                 fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
176                         animator::clearContentOverlay, true /* withStartDelay*/);
177             }
178             if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS
179                     && direction == TRANSITION_DIRECTION_TO_PIP) {
180                 // Notify the display to continue the deferred orientation change.
181                 final WindowContainerTransaction wct = new WindowContainerTransaction();
182                 wct.scheduleFinishEnterPip(mToken, destinationBounds);
183                 mTaskOrganizer.applyTransaction(wct);
184                 // The final task bounds will be applied by onFixedRotationFinished so that all
185                 // coordinates are in new rotation.
186                 mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
187                 mDeferredAnimEndTransaction = tx;
188                 return;
189             }
190             final boolean isExitPipDirection = isOutPipDirection(direction)
191                     || isRemovePipDirection(direction);
192             if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP
193                     || isExitPipDirection) {
194                 // execute the finish resize callback if needed after the transaction is committed
195                 tx.addTransactionCommittedListener(mMainExecutor,
196                         PipTaskOrganizer.this::maybePerformFinishResizeCallback);
197 
198                 // Finish resize as long as we're not exiting PIP, or, if we are, only if this is
199                 // the end of an exit PIP animation.
200                 // This is necessary in case there was a resize animation ongoing when exit PIP
201                 // started, in which case the first resize will be skipped to let the exit
202                 // operation handle the final resize out of PIP mode. See b/185306679.
203                 finishResizeDelayedIfNeeded(() -> {
204                     finishResize(tx, destinationBounds, direction, animationType);
205                     sendOnPipTransitionFinished(direction);
206                 });
207             }
208         }
209 
210         @Override
211         public void onPipAnimationCancel(TaskInfo taskInfo,
212                 PipAnimationController.PipTransitionAnimator animator) {
213             final int direction = animator.getTransitionDirection();
214             mIsCancelled = true;
215             if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) {
216                 fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(),
217                         animator::clearContentOverlay, true /* withStartDelay */);
218             }
219             sendOnPipTransitionCancelled(direction);
220         }
221     };
222 
223     /**
224      * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
225      *
226      * This is done to avoid a race condition between the last transaction applied in
227      * onPipAnimationUpdate and the finishResize in onPipAnimationEnd. The transaction in
228      * onPipAnimationUpdate is applied directly from WmShell, while onPipAnimationEnd creates a
229      * WindowContainerTransaction in finishResize, which is to be applied by WmCore later. Normally,
230      * the WCT should be the last transaction to finish the animation. However, it  may happen that
231      * it gets applied *before* the transaction created by the last onPipAnimationUpdate. This
232      * happens only when the PiP surface transaction has to be synced with the PiP menu due to the
233      * necessity for a delay when syncing the PiP surface animation with the PiP menu surface
234      * animation and redrawing the PiP menu contents. As a result, the PiP surface gets scaled after
235      * the new bounds are applied by WmCore, which makes the PiP surface have unexpected bounds.
236      *
237      * To avoid this, we delay the finishResize operation until
238      * the next frame. This aligns the last onAnimationUpdate transaction with the WCT application.
239      */
finishResizeDelayedIfNeeded(Runnable finishResizeRunnable)240     private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
241         if (!shouldSyncPipTransactionWithMenu()) {
242             finishResizeRunnable.run();
243             return;
244         }
245 
246         // Delay the finishResize to the next frame
247         Choreographer.getInstance().postCallback(Choreographer.CALLBACK_COMMIT, () -> {
248             mMainExecutor.execute(finishResizeRunnable);
249         }, null);
250     }
251 
shouldSyncPipTransactionWithMenu()252     protected boolean shouldSyncPipTransactionWithMenu() {
253         return mPipMenuController.isMenuVisible();
254     }
255 
256     @VisibleForTesting
257     final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
258             new PipTransitionController.PipTransitionCallback() {
259                 @Override
260                 public void onPipTransitionStarted(int direction, Rect pipBounds) {}
261 
262                 @Override
263                 public void onPipTransitionFinished(int direction) {
264                     // Apply the deferred RunningTaskInfo if applicable after all proper callbacks
265                     // are sent.
266                     if (direction == TRANSITION_DIRECTION_TO_PIP && mDeferredTaskInfo != null) {
267                         onTaskInfoChanged(mDeferredTaskInfo);
268                         mDeferredTaskInfo = null;
269                     }
270                 }
271 
272                 @Override
273                 public void onPipTransitionCanceled(int direction) {}
274             };
275 
276     private final PipAnimationController.PipTransactionHandler mPipTransactionHandler =
277             new PipAnimationController.PipTransactionHandler() {
278                 @Override
279                 public boolean handlePipTransaction(SurfaceControl leash,
280                         SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) {
281                     if (shouldSyncPipTransactionWithMenu()) {
282                         mPipMenuController.movePipMenu(leash, tx, destinationBounds, alpha);
283                         return true;
284                     }
285                     return false;
286                 }
287             };
288 
289     private ActivityManager.RunningTaskInfo mTaskInfo;
290     // To handle the edge case that onTaskInfoChanged callback is received during the entering
291     // PiP transition, where we do not want to intercept the transition but still want to apply the
292     // changed RunningTaskInfo when it finishes.
293     private ActivityManager.RunningTaskInfo mDeferredTaskInfo;
294     private WindowContainerToken mToken;
295     private SurfaceControl mLeash;
296     protected PipTransitionState mPipTransitionState;
297     private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
298             mSurfaceControlTransactionFactory;
299     protected PictureInPictureParams mPictureInPictureParams;
300     private IntConsumer mOnDisplayIdChangeCallback;
301     /**
302      * The end transaction of PiP animation for switching between PiP and fullscreen with
303      * orientation change. The transaction should be applied after the display is rotated.
304      */
305     private SurfaceControl.Transaction mDeferredAnimEndTransaction;
306     /** Whether the existing PiP is hidden by alpha. */
307     private boolean mHasFadeOut;
308 
309     /**
310      * If set to {@code true}, the entering animation will be skipped and we will wait for
311      * {@link #onFixedRotationFinished(int)} callback to actually enter PiP.
312      */
313     private boolean mWaitForFixedRotation;
314 
315     /**
316      * The rotation that the display will apply after expanding PiP to fullscreen. This is only
317      * meaningful if {@link #mWaitForFixedRotation} is true.
318      */
319     private @Surface.Rotation int mNextRotation;
320 
321     private @Surface.Rotation int mCurrentRotation;
322 
323     /**
324      * An optional overlay used to mask content changing between an app in/out of PiP, only set if
325      * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true.
326      */
327     @Nullable
328     SurfaceControl mSwipePipToHomeOverlay;
329 
PipTaskOrganizer(Context context, @NonNull SyncTransactionQueue syncTransactionQueue, @NonNull PipTransitionState pipTransitionState, @NonNull PipBoundsState pipBoundsState, @NonNull PipDisplayLayoutState pipDisplayLayoutState, @NonNull PipBoundsAlgorithm boundsHandler, @NonNull PipMenuController pipMenuController, @NonNull PipAnimationController pipAnimationController, @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper, @NonNull PipTransitionController pipTransitionController, @NonNull PipParamsChangedForwarder pipParamsChangedForwarder, Optional<SplitScreenController> splitScreenOptional, @NonNull DisplayController displayController, @NonNull PipUiEventLogger pipUiEventLogger, @NonNull ShellTaskOrganizer shellTaskOrganizer, @ShellMainThread ShellExecutor mainExecutor)330     public PipTaskOrganizer(Context context,
331             @NonNull SyncTransactionQueue syncTransactionQueue,
332             @NonNull PipTransitionState pipTransitionState,
333             @NonNull PipBoundsState pipBoundsState,
334             @NonNull PipDisplayLayoutState pipDisplayLayoutState,
335             @NonNull PipBoundsAlgorithm boundsHandler,
336             @NonNull PipMenuController pipMenuController,
337             @NonNull PipAnimationController pipAnimationController,
338             @NonNull PipSurfaceTransactionHelper surfaceTransactionHelper,
339             @NonNull PipTransitionController pipTransitionController,
340             @NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
341             Optional<SplitScreenController> splitScreenOptional,
342             @NonNull DisplayController displayController,
343             @NonNull PipUiEventLogger pipUiEventLogger,
344             @NonNull ShellTaskOrganizer shellTaskOrganizer,
345             @ShellMainThread ShellExecutor mainExecutor) {
346         mContext = context;
347         mSyncTransactionQueue = syncTransactionQueue;
348         mPipTransitionState = pipTransitionState;
349         mPipBoundsState = pipBoundsState;
350         mPipDisplayLayoutState = pipDisplayLayoutState;
351         mPipBoundsAlgorithm = boundsHandler;
352         mPipMenuController = pipMenuController;
353         mPipTransitionController = pipTransitionController;
354         mPipParamsChangedForwarder = pipParamsChangedForwarder;
355         mEnterAnimationDuration = context.getResources()
356                 .getInteger(R.integer.config_pipEnterAnimationDuration);
357         mExitAnimationDuration = context.getResources()
358                 .getInteger(R.integer.config_pipExitAnimationDuration);
359         mCrossFadeAnimationDuration = context.getResources()
360                 .getInteger(R.integer.config_pipCrossfadeAnimationDuration);
361         mSurfaceTransactionHelper = surfaceTransactionHelper;
362         mPipAnimationController = pipAnimationController;
363         mPipUiEventLoggerLogger = pipUiEventLogger;
364         mSurfaceControlTransactionFactory =
365                 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
366         mSplitScreenOptional = splitScreenOptional;
367         mTaskOrganizer = shellTaskOrganizer;
368         mMainExecutor = mainExecutor;
369 
370         // TODO: Can be removed once wm components are created on the shell-main thread
371         if (!PipUtils.isPip2ExperimentEnabled()) {
372             mMainExecutor.execute(() -> {
373                 mTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_PIP);
374             });
375             mTaskOrganizer.addFocusListener(this);
376             mPipTransitionController.setPipOrganizer(this);
377             displayController.addDisplayWindowListener(this);
378             pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
379         }
380     }
381 
getTransitionController()382     public PipTransitionController getTransitionController() {
383         return mPipTransitionController;
384     }
385 
getPipTransactionHandler()386     PipAnimationController.PipTransactionHandler getPipTransactionHandler() {
387         return mPipTransactionHandler;
388     }
389 
getCurrentOrAnimatingBounds()390     public Rect getCurrentOrAnimatingBounds() {
391         PipAnimationController.PipTransitionAnimator animator =
392                 mPipAnimationController.getCurrentAnimator();
393         if (animator != null && animator.isRunning()) {
394             return new Rect(animator.getDestinationBounds());
395         }
396         return mPipBoundsState.getBounds();
397     }
398 
isInPip()399     public boolean isInPip() {
400         return mPipTransitionState.isInPip();
401     }
402 
isLaunchIntoPipTask()403     private boolean isLaunchIntoPipTask() {
404         return mPictureInPictureParams != null && mPictureInPictureParams.isLaunchIntoPip();
405     }
406 
407     /**
408      * Returns whether the entry animation is waiting to be started.
409      */
isEntryScheduled()410     public boolean isEntryScheduled() {
411         return mPipTransitionState.getTransitionState() == PipTransitionState.ENTRY_SCHEDULED;
412     }
413 
414     /**
415      * Registers a callback when a display change has been detected when we enter PiP.
416      */
registerOnDisplayIdChangeCallback(IntConsumer onDisplayIdChangeCallback)417     public void registerOnDisplayIdChangeCallback(IntConsumer onDisplayIdChangeCallback) {
418         mOnDisplayIdChangeCallback = onDisplayIdChangeCallback;
419     }
420 
421     /**
422      * Override if the PiP should always use a fade-in animation during PiP entry.
423      *
424      * @return true if the mOneShotAnimationType should always be
425      * {@link PipAnimationController#ANIM_TYPE_ALPHA}.
426      */
shouldAlwaysFadeIn()427     protected boolean shouldAlwaysFadeIn() {
428         return false;
429     }
430 
431     /**
432      * Whether the menu should get attached as early as possible when entering PiP.
433      *
434      * @return whether the menu should be attached before
435      * {@link PipBoundsAlgorithm#getEntryDestinationBounds()} is called.
436      */
shouldAttachMenuEarly()437     protected boolean shouldAttachMenuEarly() {
438         return false;
439     }
440 
441     /**
442      * Callback when Launcher starts swipe-pip-to-home operation.
443      * @return {@link Rect} for destination bounds.
444      */
startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo, PictureInPictureParams pictureInPictureParams)445     public Rect startSwipePipToHome(ComponentName componentName, ActivityInfo activityInfo,
446             PictureInPictureParams pictureInPictureParams) {
447         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
448                 "startSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
449         mPipTransitionState.setInSwipePipToHomeTransition(true);
450         sendOnPipTransitionStarted(TRANSITION_DIRECTION_TO_PIP);
451         setBoundsStateForEntry(componentName, pictureInPictureParams, activityInfo);
452         return mPipBoundsAlgorithm.getEntryDestinationBounds();
453     }
454 
455     /**
456      * Callback when launcher finishes preparation of swipe-pip-to-home operation.
457      * Expect {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)} afterwards.
458      */
stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds, SurfaceControl overlay)459     public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
460             SurfaceControl overlay) {
461         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
462                 "stopSwipePipToHome: %s, state=%s", componentName, mPipTransitionState);
463         // do nothing if there is no startSwipePipToHome being called before
464         if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
465             return;
466         }
467         mPipBoundsState.setBounds(destinationBounds);
468         mSwipePipToHomeOverlay = overlay;
469         if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
470             // With Shell transition, the overlay was attached to the remote transition leash, which
471             // will be removed when the current transition is finished, so we need to reparent it
472             // to the actual Task surface now.
473             // PipTransition is responsible to fade it out and cleanup when finishing the enter PIP
474             // transition.
475             final SurfaceControl.Transaction t = mSurfaceControlTransactionFactory.getTransaction();
476             mTaskOrganizer.reparentChildSurfaceToTask(taskId, overlay, t);
477             t.setLayer(overlay, Integer.MAX_VALUE);
478             t.apply();
479         }
480     }
481 
482     /**
483      * Callback when launcher aborts swipe-pip-to-home operation.
484      */
abortSwipePipToHome(int taskId, ComponentName componentName)485     public void abortSwipePipToHome(int taskId, ComponentName componentName) {
486         if (!mPipTransitionState.getInSwipePipToHomeTransition()) {
487             return;
488         }
489         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
490                 "Abort swipe-pip-to-home for %s", componentName);
491         sendOnPipTransitionCancelled(TRANSITION_DIRECTION_TO_PIP);
492         // Cleanup internal states
493         mPipTransitionState.setInSwipePipToHomeTransition(false);
494         mPictureInPictureParams = null;
495         mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED);
496     }
497 
getTaskInfo()498     public ActivityManager.RunningTaskInfo getTaskInfo() {
499         return mTaskInfo;
500     }
501 
getSurfaceControl()502     public SurfaceControl getSurfaceControl() {
503         return mLeash;
504     }
505 
setBoundsStateForEntry(ComponentName componentName, PictureInPictureParams params, ActivityInfo activityInfo)506     private void setBoundsStateForEntry(ComponentName componentName,
507             PictureInPictureParams params, ActivityInfo activityInfo) {
508         mPipBoundsState.setBoundsStateForEntry(componentName, activityInfo, params,
509                 mPipBoundsAlgorithm);
510     }
511 
512     /**
513      * Expands PiP to the previous bounds, this is done in two phases using
514      * {@link WindowContainerTransaction}
515      * - setActivityWindowingMode to either fullscreen or split-secondary at beginning of the
516      *   transaction. without changing the windowing mode of the Task itself. This makes sure the
517      *   activity render it's final configuration while the Task is still in PiP.
518      * - setWindowingMode to undefined at the end of transition
519      * @param animationDurationMs duration in millisecond for the exiting PiP transition
520      * @param requestEnterSplit whether the enterSplit button is pressed on PiP or not.
521      *                             Indicate the user wishes to directly put PiP into split screen
522      *                             mode.
523      */
exitPip(int animationDurationMs, boolean requestEnterSplit)524     public void exitPip(int animationDurationMs, boolean requestEnterSplit) {
525         if (!mPipTransitionState.isInPip()
526                 || mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP
527                 || mToken == null) {
528             ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
529                     "%s: Not allowed to exitPip in current state"
530                             + " mState=%d mToken=%s", TAG, mPipTransitionState.getTransitionState(),
531                     mToken);
532             return;
533         }
534 
535         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
536                 "exitPip: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
537         final WindowContainerTransaction wct = new WindowContainerTransaction();
538         if (isLaunchIntoPipTask()) {
539             exitLaunchIntoPipTask(wct);
540             return;
541         }
542 
543         final Rect destinationBounds = new Rect(getExitDestinationBounds());
544         final int direction = syncWithSplitScreenBounds(destinationBounds, requestEnterSplit)
545                 ? TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN
546                 : TRANSITION_DIRECTION_LEAVE_PIP;
547         // For exiting to fullscreen, the windowing mode of task will be changed to fullscreen
548         // until the animation is finished. Otherwise if the activity is resumed and focused at the
549         // begin of aniamtion, the app may do something too early to distub the animation.
550 
551         if (Transitions.SHELL_TRANSITIONS_ROTATION) {
552             // When exit to fullscreen with Shell transition enabled, we update the Task windowing
553             // mode directly so that it can also trigger display rotation and visibility update in
554             // the same transition if there will be any.
555             wct.setWindowingMode(mToken, getOutPipWindowingMode());
556             // We can inherit the parent bounds as it is going to be fullscreen. The
557             // destinationBounds calculated above will be incorrect if this is with rotation.
558             wct.setBounds(mToken, null);
559         } else {
560             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
561                     "exitPip: %s, dest=%s", mTaskInfo.topActivity, destinationBounds);
562             final SurfaceControl.Transaction tx =
563                     mSurfaceControlTransactionFactory.getTransaction();
564             mSurfaceTransactionHelper.scale(tx, mLeash, destinationBounds,
565                     mPipBoundsState.getBounds());
566             tx.setWindowCrop(mLeash, destinationBounds.width(), destinationBounds.height());
567             // We set to fullscreen here for now, but later it will be set to UNDEFINED for
568             // the proper windowing mode to take place. See #applyWindowingModeChangeOnExit.
569             wct.setActivityWindowingMode(mToken, WINDOWING_MODE_FULLSCREEN);
570             wct.setBounds(mToken, destinationBounds);
571             wct.setBoundsChangeTransaction(mToken, tx);
572         }
573 
574         // Cancel the existing animator if there is any.
575         // TODO(b/232439933): this is disabled temporarily to unblock b/234502692.
576         // cancelCurrentAnimator();
577 
578         // Set the exiting state first so if there is fixed rotation later, the running animation
579         // won't be interrupted by alpha animation for existing PiP.
580         mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
581 
582         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
583             if (requestEnterSplit && mSplitScreenOptional.isPresent()) {
584                 wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
585                 mSplitScreenOptional.get().prepareEnterSplitScreen(wct, mTaskInfo,
586                         isPipToTopLeft()
587                                 ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
588                 mPipTransitionController.startExitTransition(
589                         TRANSIT_EXIT_PIP_TO_SPLIT, wct, destinationBounds);
590                 return;
591             }
592 
593             if (mSplitScreenOptional.isPresent()) {
594                 // If pip activity will reparent to origin task case and if the origin task still
595                 // under split root, apply exit split transaction to make it expand to fullscreen.
596                 SplitScreenController split = mSplitScreenOptional.get();
597                 if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
598                     split.prepareExitSplitScreen(wct, split.getStageOfTask(
599                             mTaskInfo.lastParentTaskIdBeforePip),
600                             SplitScreenController.EXIT_REASON_APP_FINISHED);
601                 }
602             }
603             mPipTransitionController.startExitTransition(TRANSIT_EXIT_PIP, wct, destinationBounds);
604             return;
605         }
606 
607         if (mSplitScreenOptional.isPresent()) {
608             // If pip activity will reparent to origin task case and if the origin task still under
609             // split root, just exit split screen here to ensure it could expand to fullscreen.
610             SplitScreenController split = mSplitScreenOptional.get();
611             if (split.isTaskInSplitScreen(mTaskInfo.lastParentTaskIdBeforePip)) {
612                 split.exitSplitScreen(INVALID_TASK_ID,
613                         SplitScreenController.EXIT_REASON_APP_FINISHED);
614             }
615         }
616         mSyncTransactionQueue.queue(wct);
617         mSyncTransactionQueue.runInSync(t -> {
618             // Make sure to grab the latest source hint rect as it could have been
619             // updated right after applying the windowing mode change.
620             final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
621                     mPictureInPictureParams, destinationBounds);
622             final PipAnimationController.PipTransitionAnimator<?> animator =
623                     animateResizePip(mPipBoundsState.getBounds(), destinationBounds, sourceHintRect,
624                             direction, animationDurationMs, 0 /* startingAngle */);
625             if (animator != null) {
626                 // Even though the animation was started above, re-apply the transaction for the
627                 // first frame using the SurfaceControl.Transaction supplied by the
628                 // SyncTransactionQueue. This is necessary because the initial surface transform
629                 // may not be applied until the next frame if a different Transaction than the one
630                 // supplied is used, resulting in 1 frame not being cropped to the source rect
631                 // hint during expansion that causes a visible jank/flash. See b/184166183.
632                 animator.applySurfaceControlTransaction(mLeash, t, FRACTION_START);
633             }
634         });
635     }
636 
637     /** Returns the bounds to restore to when exiting PIP mode. */
getExitDestinationBounds()638     public Rect getExitDestinationBounds() {
639         return mPipBoundsState.getDisplayBounds();
640     }
641 
exitLaunchIntoPipTask(WindowContainerTransaction wct)642     private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
643         wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */);
644         mTaskOrganizer.applyTransaction(wct);
645 
646         // Remove the PiP with fade-out animation right after the host Task is brought to front.
647         removePip();
648     }
649 
applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction)650     void applyWindowingModeChangeOnExit(WindowContainerTransaction wct, int direction) {
651         // Reset the final windowing mode.
652         wct.setWindowingMode(mToken, getOutPipWindowingMode());
653         // Simply reset the activity mode set prior to the animation running.
654         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
655     }
656 
657     /**
658      * Removes PiP immediately.
659      */
removePip()660     public void removePip() {
661         if (!mPipTransitionState.isInPip() || mToken == null || mLeash == null) {
662             ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
663                     "%s: Not allowed to removePip in current state"
664                             + " mState=%d mToken=%s mLeash=%s", TAG,
665                     mPipTransitionState.getTransitionState(), mToken, mLeash);
666             return;
667         }
668 
669         // removePipImmediately is expected when the following animation finishes.
670         ValueAnimator animator = mPipAnimationController
671                 .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(),
672                         1f /* alphaStart */, 0f /* alphaEnd */)
673                 .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK)
674                 .setPipTransactionHandler(mPipTransactionHandler)
675                 .setPipAnimationCallback(mPipAnimationCallback);
676         animator.setDuration(mExitAnimationDuration);
677         animator.setInterpolator(Interpolators.ALPHA_OUT);
678         animator.start();
679         mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP);
680         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
681                 "removePip: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
682     }
683 
removePipImmediately()684     private void removePipImmediately() {
685         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
686                 "removePipImmediately: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
687         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
688             final WindowContainerTransaction wct = new WindowContainerTransaction();
689             wct.setBounds(mToken, null);
690             wct.setWindowingMode(mToken, getOutPipWindowingMode());
691             wct.reorder(mToken, false);
692             mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
693                     null /* destinationBounds */);
694             return;
695         }
696 
697         try {
698             // Reset the task bounds first to ensure the activity configuration is reset as well
699             final WindowContainerTransaction wct = new WindowContainerTransaction();
700             wct.setBounds(mToken, null);
701             mTaskOrganizer.applyTransaction(wct);
702 
703             ActivityTaskManager.getService().removeRootTasksInWindowingModes(
704                     new int[]{ WINDOWING_MODE_PINNED });
705         } catch (RemoteException e) {
706             ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
707                     "%s: Failed to remove PiP, %s",
708                     TAG, e);
709         }
710     }
711 
712     @Override
onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash)713     public void onTaskAppeared(ActivityManager.RunningTaskInfo info, SurfaceControl leash) {
714         Objects.requireNonNull(info, "Requires RunningTaskInfo");
715         mTaskInfo = info;
716         mToken = mTaskInfo.token;
717         mPipTransitionState.setTransitionState(PipTransitionState.TASK_APPEARED);
718         mLeash = leash;
719         mPictureInPictureParams = mTaskInfo.pictureInPictureParams;
720         setBoundsStateForEntry(mTaskInfo.topActivity, mPictureInPictureParams,
721                 mTaskInfo.topActivityInfo);
722         if (mPictureInPictureParams != null) {
723             mPipParamsChangedForwarder.notifyActionsChanged(mPictureInPictureParams.getActions(),
724                     mPictureInPictureParams.getCloseAction());
725             mPipParamsChangedForwarder.notifyTitleChanged(
726                     mPictureInPictureParams.getTitle());
727             mPipParamsChangedForwarder.notifySubtitleChanged(
728                     mPictureInPictureParams.getSubtitle());
729         }
730 
731         mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo);
732 
733         // If the displayId of the task is different than what PipBoundsHandler has, then update
734         // it. This is possible if we entered PiP on an external display.
735         if (info.displayId != mPipDisplayLayoutState.getDisplayId()
736                 && mOnDisplayIdChangeCallback != null) {
737             mOnDisplayIdChangeCallback.accept(info.displayId);
738         }
739 
740         // UiEvent logging.
741         final PipUiEventLogger.PipUiEventEnum uiEventEnum;
742         if (isLaunchIntoPipTask()) {
743             uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER_CONTENT_PIP;
744         } else if (mPipTransitionState.getInSwipePipToHomeTransition()) {
745             uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER;
746         } else {
747             uiEventEnum = PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER;
748         }
749         mPipUiEventLoggerLogger.log(uiEventEnum);
750 
751         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
752                 "onTaskAppeared: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
753         if (mPipTransitionState.getInSwipePipToHomeTransition()) {
754             if (!mWaitForFixedRotation) {
755                 onEndOfSwipePipToHomeTransition();
756             } else {
757                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
758                         "%s: Defer onTaskAppeared-SwipePipToHome until end of fixed rotation.",
759                         TAG);
760             }
761             return;
762         }
763 
764         final int animationType = shouldAlwaysFadeIn()
765                 ? ANIM_TYPE_ALPHA
766                 : mPipAnimationController.takeOneShotEnterAnimationType();
767         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
768             mPipTransitionController.setEnterAnimationType(animationType);
769             // For Shell transition, we will animate the window in PipTransition#startAnimation
770             // instead of #onTaskAppeared.
771             return;
772         }
773 
774         if (mWaitForFixedRotation) {
775             onTaskAppearedWithFixedRotation(animationType);
776             return;
777         }
778 
779         if (shouldAttachMenuEarly()) {
780             mPipMenuController.attach(mLeash);
781         }
782         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
783         Objects.requireNonNull(destinationBounds, "Missing destination bounds");
784         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
785 
786         if (animationType == ANIM_TYPE_BOUNDS) {
787             if (!shouldAttachMenuEarly()) {
788                 mPipMenuController.attach(mLeash);
789             }
790             final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
791                     info.pictureInPictureParams, currentBounds);
792             scheduleAnimateResizePip(currentBounds, destinationBounds, 0 /* startingAngle */,
793                     sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration,
794                     null /* updateBoundsCallback */);
795             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
796         } else if (animationType == ANIM_TYPE_ALPHA) {
797             enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
798         } else {
799             throw new RuntimeException("Unrecognized animation type: " + animationType);
800         }
801     }
802 
onTaskAppearedWithFixedRotation(int animationType)803     private void onTaskAppearedWithFixedRotation(int animationType) {
804         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
805                 "onTaskAppearedWithFixedRotation: %s, state=%s animationType=%d",
806                 mTaskInfo.topActivity, mPipTransitionState, animationType);
807         if (animationType == ANIM_TYPE_ALPHA) {
808             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
809                     "%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG);
810             // If deferred, hside the surface till fixed rotation is completed.
811             final SurfaceControl.Transaction tx =
812                     mSurfaceControlTransactionFactory.getTransaction();
813             tx.setAlpha(mLeash, 0f);
814             tx.show(mLeash);
815             tx.apply();
816             return;
817         }
818         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
819         final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect(
820                 mPictureInPictureParams, currentBounds);
821         final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
822         animateResizePip(currentBounds, destinationBounds, sourceHintRect,
823                 TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, 0 /* startingAngle */);
824         mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
825     }
826 
827     /**
828      * Called when the display rotation handling is skipped (e.g. when rotation happens while in
829      * the middle of an entry transition).
830      */
onDisplayRotationSkipped()831     public void onDisplayRotationSkipped() {
832         if (isEntryScheduled()) {
833             // The PIP animation is scheduled to start with the previous orientation's bounds,
834             // re-calculate the entry bounds and restart the alpha animation.
835             final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
836             enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration);
837         }
838     }
839 
840     @VisibleForTesting
enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs)841     void enterPipWithAlphaAnimation(Rect destinationBounds, long durationMs) {
842         // If we are fading the PIP in, then we should move the pip to the final location as
843         // soon as possible, but set the alpha immediately since the transaction can take a
844         // while to process
845         final SurfaceControl.Transaction tx =
846                 mSurfaceControlTransactionFactory.getTransaction();
847         tx.setAlpha(mLeash, 0f);
848         tx.apply();
849 
850         // When entering PiP this transaction will be applied within WindowContainerTransaction and
851         // ensure that the PiP has rounded corners.
852         final SurfaceControl.Transaction boundsChangeTx =
853                 mSurfaceControlTransactionFactory.getTransaction();
854         mSurfaceTransactionHelper
855                 .crop(boundsChangeTx, mLeash, destinationBounds)
856                 .round(boundsChangeTx, mLeash, true /* applyCornerRadius */);
857 
858         mPipTransitionState.setTransitionState(PipTransitionState.ENTRY_SCHEDULED);
859         applyEnterPipSyncTransaction(destinationBounds, () -> {
860             mPipAnimationController
861                     .getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f)
862                     .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
863                     .setPipAnimationCallback(mPipAnimationCallback)
864                     .setPipTransactionHandler(mPipTransactionHandler)
865                     .setDuration(durationMs)
866                     .start();
867             // mState is set right after the animation is kicked off to block any resize
868             // requests such as offsetPip that may have been called prior to the transition.
869             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
870         }, boundsChangeTx);
871     }
872 
onEndOfSwipePipToHomeTransition()873     private void onEndOfSwipePipToHomeTransition() {
874         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
875             mPipTransitionController.setEnterAnimationType(ANIM_TYPE_BOUNDS);
876             return;
877         }
878 
879         final Rect destinationBounds = mPipBoundsState.getBounds();
880         final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay;
881         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
882         mSurfaceTransactionHelper
883                 .resetScale(tx, mLeash, destinationBounds)
884                 .crop(tx, mLeash, destinationBounds)
885                 .round(tx, mLeash, isInPip());
886         // The animation is finished in the Launcher and here we directly apply the final touch.
887         applyEnterPipSyncTransaction(destinationBounds, () -> {
888             // Ensure menu's settled in its final bounds first.
889             finishResizeForMenu(destinationBounds);
890             sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
891 
892             // Remove the swipe to home overlay
893             if (swipeToHomeOverlay != null) {
894                 fadeOutAndRemoveOverlay(swipeToHomeOverlay,
895                         null /* callback */, false /* withStartDelay */);
896             }
897         }, tx);
898         mPipTransitionState.setInSwipePipToHomeTransition(false);
899         mSwipePipToHomeOverlay = null;
900     }
901 
applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable, @Nullable SurfaceControl.Transaction boundsChangeTransaction)902     private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable,
903             @Nullable SurfaceControl.Transaction boundsChangeTransaction) {
904         // PiP menu is attached late in the process here to avoid any artifacts on the leash
905         // caused by addShellRoot when in gesture navigation mode.
906         if (!shouldAttachMenuEarly()) {
907             mPipMenuController.attach(mLeash);
908         }
909         final WindowContainerTransaction wct = new WindowContainerTransaction();
910         wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
911         wct.setBounds(mToken, destinationBounds);
912         if (boundsChangeTransaction != null) {
913             wct.setBoundsChangeTransaction(mToken, boundsChangeTransaction);
914         }
915         mSyncTransactionQueue.queue(wct);
916         if (runnable != null) {
917             mSyncTransactionQueue.runInSync(t -> runnable.run());
918         }
919     }
920 
sendOnPipTransitionStarted( @ipAnimationController.TransitionDirection int direction)921     private void sendOnPipTransitionStarted(
922             @PipAnimationController.TransitionDirection int direction) {
923         if (direction == TRANSITION_DIRECTION_TO_PIP) {
924             mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
925         }
926         mPipTransitionController.sendOnPipTransitionStarted(direction);
927     }
928 
929     @VisibleForTesting
sendOnPipTransitionFinished( @ipAnimationController.TransitionDirection int direction)930     void sendOnPipTransitionFinished(
931             @PipAnimationController.TransitionDirection int direction) {
932         if (direction == TRANSITION_DIRECTION_TO_PIP) {
933             mPipTransitionState.setTransitionState(PipTransitionState.ENTERED_PIP);
934         }
935         mPipTransitionController.sendOnPipTransitionFinished(direction);
936     }
937 
sendOnPipTransitionCancelled( @ipAnimationController.TransitionDirection int direction)938     private void sendOnPipTransitionCancelled(
939             @PipAnimationController.TransitionDirection int direction) {
940         mPipTransitionController.sendOnPipTransitionCancelled(direction);
941     }
942 
943     /**
944      * Note that dismissing PiP is now originated from SystemUI, see {@link #exitPip(int, boolean)}.
945      * Meanwhile this callback is invoked whenever the task is removed. For instance:
946      *   - as a result of removeRootTasksInWindowingModes from WM
947      *   - activity itself is died
948      * Nevertheless, we simply update the internal state here as all the heavy lifting should
949      * have been done in WM.
950      */
951     @Override
onTaskVanished(ActivityManager.RunningTaskInfo info)952     public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
953         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
954                 "onTaskVanished: %s, state=%s", mTaskInfo.topActivity, mPipTransitionState);
955         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
956             return;
957         }
958         if (Transitions.ENABLE_SHELL_TRANSITIONS
959                 && mPipTransitionState.getTransitionState() == PipTransitionState.EXITING_PIP) {
960             // With Shell transition, we do the cleanup in PipTransition after exiting PIP.
961             return;
962         }
963         final WindowContainerToken token = info.token;
964         Objects.requireNonNull(token, "Requires valid WindowContainerToken");
965         if (token.asBinder() != mToken.asBinder()) {
966             ProtoLog.wtf(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
967                     "%s: Unrecognized token: %s", TAG, token);
968             return;
969         }
970 
971         cancelCurrentAnimator();
972         onExitPipFinished(info);
973 
974         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
975             mPipTransitionController.forceFinishTransition();
976         }
977     }
978 
979     @Override
onTaskInfoChanged(ActivityManager.RunningTaskInfo info)980     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
981         Objects.requireNonNull(mToken, "onTaskInfoChanged requires valid existing mToken");
982         if (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP
983                 && mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP) {
984             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
985                     "%s: Defer onTaskInfoChange in current state: %d", TAG,
986                     mPipTransitionState.getTransitionState());
987             // Defer applying PiP parameters if the task is entering PiP to avoid disturbing
988             // the animation.
989             mDeferredTaskInfo = info;
990             return;
991         }
992         mPipBoundsState.setLastPipComponentName(info.topActivity);
993         mPipBoundsState.setOverrideMinSize(
994                 mPipBoundsAlgorithm.getMinimalSize(info.topActivityInfo));
995         final PictureInPictureParams newParams = info.pictureInPictureParams;
996         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
997                 "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s",
998                 mTaskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, newParams);
999 
1000         // mPictureInPictureParams is only null if there is no PiP
1001         if (newParams == null || mPictureInPictureParams == null) {
1002             return;
1003         }
1004         applyNewPictureInPictureParams(newParams);
1005         mPictureInPictureParams = newParams;
1006     }
1007 
1008     @Override
onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo)1009     public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
1010         mPipMenuController.onFocusTaskChanged(taskInfo);
1011     }
1012 
1013     @Override
supportCompatUI()1014     public boolean supportCompatUI() {
1015         // PIP doesn't support compat.
1016         return false;
1017     }
1018 
1019     @Override
attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)1020     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
1021         b.setParent(findTaskSurface(taskId));
1022     }
1023 
1024     @Override
reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)1025     public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
1026             SurfaceControl.Transaction t) {
1027         t.reparent(sc, findTaskSurface(taskId));
1028     }
1029 
findTaskSurface(int taskId)1030     private SurfaceControl findTaskSurface(int taskId) {
1031         if (mTaskInfo == null || mLeash == null || mTaskInfo.taskId != taskId) {
1032             throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
1033         }
1034         return mLeash;
1035     }
1036 
1037     @Override
onFixedRotationStarted(int displayId, int newRotation)1038     public void onFixedRotationStarted(int displayId, int newRotation) {
1039         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1040                 "onFixedRotationStarted: %s, state=%s", mTaskInfo, mPipTransitionState);
1041         mNextRotation = newRotation;
1042         mWaitForFixedRotation = true;
1043 
1044         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
1045             // The fixed rotation will also be included in the transition info. However, if it is
1046             // not a PIP transition (such as open another app to different orientation),
1047             // PIP transition handler may not be aware of the fixed rotation start.
1048             // Notify the PIP transition handler so that it can fade out the PIP window early for
1049             // fixed transition of other windows.
1050             mPipTransitionController.onFixedRotationStarted();
1051             return;
1052         }
1053 
1054         if (mPipTransitionState.isInPip()) {
1055             // Fade out the existing PiP to avoid jump cut during seamless rotation.
1056             fadeExistingPip(false /* show */);
1057         }
1058     }
1059 
1060     @Override
onFixedRotationFinished(int displayId)1061     public void onFixedRotationFinished(int displayId) {
1062         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1063                 "onFixedRotationFinished: %s, state=%s", mTaskInfo, mPipTransitionState);
1064         if (!mWaitForFixedRotation) {
1065             return;
1066         }
1067         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
1068             mPipTransitionController.onFixedRotationFinished();
1069             clearWaitForFixedRotation();
1070             return;
1071         }
1072         if (mPipTransitionState.getTransitionState() == PipTransitionState.TASK_APPEARED) {
1073             if (mPipTransitionState.getInSwipePipToHomeTransition()) {
1074                 onEndOfSwipePipToHomeTransition();
1075             } else {
1076                 // Schedule a regular animation to ensure all the callbacks are still being sent.
1077                 enterPipWithAlphaAnimation(mPipBoundsAlgorithm.getEntryDestinationBounds(),
1078                         mEnterAnimationDuration);
1079             }
1080         } else if (mPipTransitionState.getTransitionState() == PipTransitionState.ENTERED_PIP
1081                 && mHasFadeOut) {
1082             fadeExistingPip(true /* show */);
1083         } else if (mPipTransitionState.getTransitionState() == PipTransitionState.ENTERING_PIP
1084                 && mDeferredAnimEndTransaction != null) {
1085             final PipAnimationController.PipTransitionAnimator<?> animator =
1086                     mPipAnimationController.getCurrentAnimator();
1087             final Rect destinationBounds = animator.getDestinationBounds();
1088             mPipBoundsState.setBounds(destinationBounds);
1089             applyEnterPipSyncTransaction(destinationBounds, () -> {
1090                 finishResizeForMenu(destinationBounds);
1091                 sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
1092             }, mDeferredAnimEndTransaction);
1093         }
1094         clearWaitForFixedRotation();
1095     }
1096 
1097     /** Called when exiting PIP transition is finished to do the state cleanup. */
onExitPipFinished(TaskInfo info)1098     void onExitPipFinished(TaskInfo info) {
1099         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1100                 "onExitPipFinished: %s, state=%s leash=%s",
1101                 info.topActivity, mPipTransitionState, mLeash);
1102         if (mLeash == null) {
1103             // TODO(239461594): Remove once the double call to onExitPipFinished() is fixed
1104             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1105                     "Warning, onExitPipFinished() called multiple times in the same session");
1106             return;
1107         }
1108 
1109         clearWaitForFixedRotation();
1110         if (mSwipePipToHomeOverlay != null) {
1111             removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */);
1112             mSwipePipToHomeOverlay = null;
1113         }
1114         resetShadowRadius();
1115         mPipTransitionState.setInSwipePipToHomeTransition(false);
1116         mPictureInPictureParams = null;
1117         mPipTransitionState.setTransitionState(PipTransitionState.UNDEFINED);
1118         // Re-set the PIP bounds to none.
1119         mPipBoundsState.setBounds(new Rect());
1120         mPipUiEventLoggerLogger.setTaskInfo(null);
1121         mPipMenuController.detach();
1122         mLeash = null;
1123 
1124         if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) {
1125             mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
1126         }
1127     }
1128 
fadeExistingPip(boolean show)1129     private void fadeExistingPip(boolean show) {
1130         if (mLeash == null || !mLeash.isValid()) {
1131             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1132                     "%s: Invalid leash on fadeExistingPip: %s", TAG, mLeash);
1133             return;
1134         }
1135         final float alphaStart = show ? 0 : 1;
1136         final float alphaEnd = show ? 1 : 0;
1137         mPipAnimationController
1138                 .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), alphaStart, alphaEnd)
1139                 .setTransitionDirection(TRANSITION_DIRECTION_SAME)
1140                 .setPipTransactionHandler(mPipTransactionHandler)
1141                 .setDuration(show ? mEnterAnimationDuration : mExitAnimationDuration)
1142                 .start();
1143         mHasFadeOut = !show;
1144     }
1145 
clearWaitForFixedRotation()1146     private void clearWaitForFixedRotation() {
1147         mWaitForFixedRotation = false;
1148         mDeferredAnimEndTransaction = null;
1149     }
1150 
1151     /** Explicitly set the visibility of PiP window. */
setPipVisibility(boolean visible)1152     public void setPipVisibility(boolean visible) {
1153         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1154                 "setPipVisibility: %s, state=%s visible=%s",
1155                 (mTaskInfo != null ? mTaskInfo.topActivity : null), mPipTransitionState, visible);
1156         if (!isInPip()) {
1157             return;
1158         }
1159         if (mLeash == null || !mLeash.isValid()) {
1160             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1161                     "%s: Invalid leash on setPipVisibility: %s", TAG, mLeash);
1162             return;
1163         }
1164         final SurfaceControl.Transaction tx =
1165                 mSurfaceControlTransactionFactory.getTransaction();
1166         mSurfaceTransactionHelper.alpha(tx, mLeash, visible ? 1f : 0f);
1167         tx.apply();
1168     }
1169 
1170     @Override
onDisplayConfigurationChanged(int displayId, Configuration newConfig)1171     public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
1172         mCurrentRotation = newConfig.windowConfiguration.getRotation();
1173     }
1174 
1175     /**
1176      * Called when display size or font size of settings changed
1177      */
onDensityOrFontScaleChanged(Context context)1178     public void onDensityOrFontScaleChanged(Context context) {
1179         mSurfaceTransactionHelper.onDensityOrFontScaleChanged(context);
1180     }
1181 
1182     /**
1183      * TODO(b/152809058): consolidate the display info handling logic in SysUI
1184      *
1185      * @param destinationBoundsOut the current destination bounds will be populated to this param
1186      */
1187     @SuppressWarnings("unchecked")
onMovementBoundsChanged(Rect destinationBoundsOut, boolean fromRotation, boolean fromImeAdjustment, boolean fromShelfAdjustment, WindowContainerTransaction wct)1188     public void onMovementBoundsChanged(Rect destinationBoundsOut, boolean fromRotation,
1189             boolean fromImeAdjustment, boolean fromShelfAdjustment,
1190             WindowContainerTransaction wct) {
1191         // note that this can be called when swipe-to-home or fixed-rotation is happening.
1192         // Skip this entirely if that's the case.
1193         final boolean waitForFixedRotationOnEnteringPip = mWaitForFixedRotation
1194                 && (mPipTransitionState.getTransitionState() != PipTransitionState.ENTERED_PIP);
1195         if ((mPipTransitionState.getInSwipePipToHomeTransition()
1196                 || waitForFixedRotationOnEnteringPip) && fromRotation) {
1197             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1198                     "%s: Skip onMovementBoundsChanged on rotation change"
1199                             + " InSwipePipToHomeTransition=%b"
1200                             + " mWaitForFixedRotation=%b"
1201                             + " getTransitionState=%d", TAG,
1202                     mPipTransitionState.getInSwipePipToHomeTransition(), mWaitForFixedRotation,
1203                     mPipTransitionState.getTransitionState());
1204             return;
1205         }
1206         final PipAnimationController.PipTransitionAnimator animator =
1207                 mPipAnimationController.getCurrentAnimator();
1208         if (animator == null || !animator.isRunning()
1209                 || animator.getTransitionDirection() != TRANSITION_DIRECTION_TO_PIP) {
1210             final boolean rotatingPip = mPipTransitionState.isInPip() && fromRotation;
1211             if (rotatingPip && Transitions.ENABLE_SHELL_TRANSITIONS) {
1212                 // The animation and surface update will be handled by the shell transition handler.
1213                 mPipBoundsState.setBounds(destinationBoundsOut);
1214             } else if (rotatingPip && mWaitForFixedRotation && mHasFadeOut) {
1215                 // The position will be used by fade-in animation when the fixed rotation is done.
1216                 mPipBoundsState.setBounds(destinationBoundsOut);
1217             } else if (rotatingPip) {
1218                 // Update bounds state to final destination first. It's important to do this
1219                 // before finishing & cancelling the transition animation so that the MotionHelper
1220                 // bounds are synchronized to the destination bounds when the animation ends.
1221                 mPipBoundsState.setBounds(destinationBoundsOut);
1222                 // If we are rotating while there is a current animation, immediately cancel the
1223                 // animation (remove the listeners so we don't trigger the normal finish resize
1224                 // call that should only happen on the update thread)
1225                 int direction = TRANSITION_DIRECTION_NONE;
1226                 if (animator != null) {
1227                     direction = animator.getTransitionDirection();
1228                     PipAnimationController.quietCancel(animator);
1229                     // Do notify the listeners that this was canceled
1230                     sendOnPipTransitionCancelled(direction);
1231                     sendOnPipTransitionFinished(direction);
1232                 }
1233 
1234                 // Create a reset surface transaction for the new bounds and update the window
1235                 // container transaction
1236                 final SurfaceControl.Transaction tx = createFinishResizeSurfaceTransaction(
1237                         destinationBoundsOut);
1238                 prepareFinishResizeTransaction(destinationBoundsOut, direction, tx, wct);
1239             } else  {
1240                 // There could be an animation on-going. If there is one on-going, last-reported
1241                 // bounds isn't yet updated. We'll use the animator's bounds instead.
1242                 if (animator != null && animator.isRunning()) {
1243                     if (!animator.getDestinationBounds().isEmpty()) {
1244                         destinationBoundsOut.set(animator.getDestinationBounds());
1245                     }
1246                 } else {
1247                     if (!mPipBoundsState.getBounds().isEmpty()) {
1248                         destinationBoundsOut.set(mPipBoundsState.getBounds());
1249                     }
1250                 }
1251             }
1252             return;
1253         }
1254 
1255         final Rect currentDestinationBounds = animator.getDestinationBounds();
1256         destinationBoundsOut.set(currentDestinationBounds);
1257         if (!fromImeAdjustment && !fromShelfAdjustment
1258                 && mPipBoundsState.getDisplayBounds().contains(currentDestinationBounds)) {
1259             // no need to update the destination bounds, bail early
1260             return;
1261         }
1262 
1263         final Rect newDestinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
1264         if (newDestinationBounds.equals(currentDestinationBounds)) return;
1265         updateAnimatorBounds(newDestinationBounds);
1266         destinationBoundsOut.set(newDestinationBounds);
1267     }
1268 
1269     /**
1270      * Directly update the animator bounds.
1271      */
updateAnimatorBounds(Rect bounds)1272     public void updateAnimatorBounds(Rect bounds) {
1273         final PipAnimationController.PipTransitionAnimator animator =
1274                 mPipAnimationController.getCurrentAnimator();
1275         if (animator != null && animator.isRunning()) {
1276             if (animator.getAnimationType() == ANIM_TYPE_BOUNDS) {
1277                 if (mWaitForFixedRotation) {
1278                     // The new destination bounds are in next rotation (DisplayLayout has been
1279                     // rotated in computeRotatedBounds). The animation runs in previous rotation so
1280                     // the end bounds need to be transformed.
1281                     final Rect displayBounds = mPipBoundsState.getDisplayBounds();
1282                     final Rect rotatedEndBounds = new Rect(bounds);
1283                     rotateBounds(rotatedEndBounds, displayBounds, mNextRotation, mCurrentRotation);
1284                     animator.updateEndValue(rotatedEndBounds);
1285                 } else {
1286                     animator.updateEndValue(bounds);
1287                 }
1288             }
1289             animator.setDestinationBounds(bounds);
1290         }
1291     }
1292 
1293     /**
1294      * Handles all changes to the PictureInPictureParams.
1295      */
applyNewPictureInPictureParams(@onNull PictureInPictureParams params)1296     protected void applyNewPictureInPictureParams(@NonNull PictureInPictureParams params) {
1297         if (mDeferredTaskInfo != null || PipUtils.aspectRatioChanged(params.getAspectRatioFloat(),
1298                 mPictureInPictureParams.getAspectRatioFloat())) {
1299             if (mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(
1300                     params.getAspectRatioFloat())) {
1301                 mPipParamsChangedForwarder.notifyAspectRatioChanged(params.getAspectRatioFloat());
1302             } else {
1303                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1304                         "%s: New aspect ratio is not valid."
1305                                 + " hasAspectRatio=%b"
1306                                 + " aspectRatio=%f",
1307                         TAG, params.hasSetAspectRatio(), params.getAspectRatioFloat());
1308             }
1309         }
1310         if (mDeferredTaskInfo != null
1311                 || PipUtils.remoteActionsChanged(params.getActions(),
1312                 mPictureInPictureParams.getActions())
1313                 || !PipUtils.remoteActionsMatch(params.getCloseAction(),
1314                 mPictureInPictureParams.getCloseAction())) {
1315             mPipParamsChangedForwarder.notifyActionsChanged(params.getActions(),
1316                     params.getCloseAction());
1317         }
1318     }
1319 
1320     /**
1321      * Animates resizing of the pinned stack given the duration.
1322      */
scheduleAnimateResizePip(Rect toBounds, int duration, Consumer<Rect> updateBoundsCallback)1323     public void scheduleAnimateResizePip(Rect toBounds, int duration,
1324             Consumer<Rect> updateBoundsCallback) {
1325         scheduleAnimateResizePip(toBounds, duration, TRANSITION_DIRECTION_NONE,
1326                 updateBoundsCallback);
1327     }
1328 
1329     /**
1330      * Animates resizing of the pinned stack given the duration.
1331      */
scheduleAnimateResizePip(Rect toBounds, int duration, @PipAnimationController.TransitionDirection int direction, Consumer<Rect> updateBoundsCallback)1332     public void scheduleAnimateResizePip(Rect toBounds, int duration,
1333             @PipAnimationController.TransitionDirection int direction,
1334             Consumer<Rect> updateBoundsCallback) {
1335         if (mWaitForFixedRotation) {
1336             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1337                     "%s: skip scheduleAnimateResizePip, entering pip deferred", TAG);
1338             return;
1339         }
1340         scheduleAnimateResizePip(mPipBoundsState.getBounds(), toBounds, 0 /* startingAngle */,
1341                 null /* sourceHintRect */, direction, duration, updateBoundsCallback);
1342     }
1343 
1344     /**
1345      * Animates resizing of the pinned stack given the duration and start bounds.
1346      * This is used when the starting bounds is not the current PiP bounds.
1347      *
1348      * @param pipFinishResizeWCTRunnable callback to run after window updates are complete
1349      */
scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration, float startingAngle, Consumer<Rect> updateBoundsCallback, Runnable pipFinishResizeWCTRunnable)1350     public void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
1351             float startingAngle, Consumer<Rect> updateBoundsCallback,
1352             Runnable pipFinishResizeWCTRunnable) {
1353         mPipFinishResizeWCTRunnable = pipFinishResizeWCTRunnable;
1354         if (mPipFinishResizeWCTRunnable != null) {
1355             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1356                     "mPipFinishResizeWCTRunnable is set to be called once window updates");
1357         }
1358 
1359         scheduleAnimateResizePip(fromBounds, toBounds, duration, startingAngle,
1360                 updateBoundsCallback);
1361     }
1362 
scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration, float startingAngle, Consumer<Rect> updateBoundsCallback)1363     private void scheduleAnimateResizePip(Rect fromBounds, Rect toBounds, int duration,
1364             float startingAngle, Consumer<Rect> updateBoundsCallback) {
1365         if (mWaitForFixedRotation) {
1366             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1367                     "%s: skip scheduleAnimateResizePip, entering pip deferred", TAG);
1368             return;
1369         }
1370         scheduleAnimateResizePip(fromBounds, toBounds, startingAngle, null /* sourceHintRect */,
1371                 TRANSITION_DIRECTION_SNAP_AFTER_RESIZE, duration, updateBoundsCallback);
1372     }
1373 
1374     /**
1375      * Animates resizing of the pinned stack given the duration and start bounds.
1376      * This always animates the angle to zero from the starting angle.
1377      */
scheduleAnimateResizePip( Rect currentBounds, Rect destinationBounds, float startingAngle, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, int durationMs, Consumer<Rect> updateBoundsCallback)1378     private @Nullable PipAnimationController.PipTransitionAnimator<?> scheduleAnimateResizePip(
1379             Rect currentBounds, Rect destinationBounds, float startingAngle, Rect sourceHintRect,
1380             @PipAnimationController.TransitionDirection int direction, int durationMs,
1381             Consumer<Rect> updateBoundsCallback) {
1382         if (!mPipTransitionState.isInPip()) {
1383             // TODO: tend to use shouldBlockResizeRequest here as well but need to consider
1384             // the fact that when in exitPip, scheduleAnimateResizePip is executed in the window
1385             // container transaction callback and we want to set the mState immediately.
1386             return null;
1387         }
1388 
1389         final PipAnimationController.PipTransitionAnimator<?> animator = animateResizePip(
1390                 currentBounds, destinationBounds, sourceHintRect, direction, durationMs,
1391                 startingAngle);
1392         if (updateBoundsCallback != null) {
1393             updateBoundsCallback.accept(destinationBounds);
1394         }
1395         return animator;
1396     }
1397 
1398     /**
1399      * Directly perform manipulation/resize on the leash. This will not perform any
1400      * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
1401      */
scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback)1402     public void scheduleResizePip(Rect toBounds, Consumer<Rect> updateBoundsCallback) {
1403         // Could happen when exitPip
1404         if (mToken == null || mLeash == null) {
1405             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1406                     "%s: Abort animation, invalid leash", TAG);
1407             return;
1408         }
1409         mPipBoundsState.setBounds(toBounds);
1410         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
1411         mSurfaceTransactionHelper
1412                 .crop(tx, mLeash, toBounds)
1413                 .round(tx, mLeash, mPipTransitionState.isInPip());
1414         if (shouldSyncPipTransactionWithMenu()) {
1415             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
1416         } else {
1417             tx.apply();
1418         }
1419         if (updateBoundsCallback != null) {
1420             updateBoundsCallback.accept(toBounds);
1421         }
1422     }
1423 
1424     /**
1425      * Directly perform manipulation/resize on the leash, along with rotation. This will not perform
1426      * any {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
1427      */
scheduleUserResizePip(Rect startBounds, Rect toBounds, Consumer<Rect> updateBoundsCallback)1428     public void scheduleUserResizePip(Rect startBounds, Rect toBounds,
1429             Consumer<Rect> updateBoundsCallback) {
1430         scheduleUserResizePip(startBounds, toBounds, 0 /* degrees */, updateBoundsCallback);
1431     }
1432 
1433     /**
1434      * Directly perform a scaled matrix transformation on the leash. This will not perform any
1435      * {@link WindowContainerTransaction} until {@link #scheduleFinishResizePip} is called.
1436      */
scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees, Consumer<Rect> updateBoundsCallback)1437     public void scheduleUserResizePip(Rect startBounds, Rect toBounds, float degrees,
1438             Consumer<Rect> updateBoundsCallback) {
1439         // Could happen when exitPip
1440         if (mToken == null || mLeash == null) {
1441             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1442                     "%s: Abort animation, invalid leash", TAG);
1443             return;
1444         }
1445 
1446         if (startBounds.isEmpty() || toBounds.isEmpty()) {
1447             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1448                     "%s: Attempted to user resize PIP to or from empty bounds, aborting.", TAG);
1449             return;
1450         }
1451 
1452         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
1453         mSurfaceTransactionHelper
1454                 .scale(tx, mLeash, startBounds, toBounds, degrees)
1455                 .round(tx, mLeash, startBounds, toBounds);
1456         if (shouldSyncPipTransactionWithMenu()) {
1457             mPipMenuController.movePipMenu(mLeash, tx, toBounds, PipMenuController.ALPHA_NO_CHANGE);
1458         } else {
1459             tx.apply();
1460         }
1461         if (updateBoundsCallback != null) {
1462             updateBoundsCallback.accept(toBounds);
1463         }
1464     }
1465 
1466     /**
1467      * Finish an intermediate resize operation. This is expected to be called after
1468      * {@link #scheduleResizePip}.
1469      */
scheduleFinishResizePip(Rect destinationBounds)1470     public void scheduleFinishResizePip(Rect destinationBounds) {
1471         scheduleFinishResizePip(destinationBounds, null /* updateBoundsCallback */);
1472     }
1473 
1474     /**
1475      * Same as {@link #scheduleFinishResizePip} but with a callback.
1476      */
scheduleFinishResizePip(Rect destinationBounds, Consumer<Rect> updateBoundsCallback)1477     public void scheduleFinishResizePip(Rect destinationBounds,
1478             Consumer<Rect> updateBoundsCallback) {
1479         scheduleFinishResizePip(destinationBounds, TRANSITION_DIRECTION_NONE, updateBoundsCallback);
1480     }
1481 
1482     /**
1483      * Finish an intermediate resize operation. This is expected to be called after
1484      * {@link #scheduleResizePip}.
1485      *
1486      * @param destinationBounds the final bounds of the PIP after resizing
1487      * @param direction the transition direction
1488      * @param updateBoundsCallback a callback to invoke after finishing the resize
1489      */
scheduleFinishResizePip(Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, Consumer<Rect> updateBoundsCallback)1490     public void scheduleFinishResizePip(Rect destinationBounds,
1491             @PipAnimationController.TransitionDirection int direction,
1492             Consumer<Rect> updateBoundsCallback) {
1493         if (mPipTransitionState.shouldBlockResizeRequest()) {
1494             return;
1495         }
1496 
1497         if (mLeash == null || !mLeash.isValid()) {
1498             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1499                     "%s: scheduleFinishResizePip with null leash! mState=%d",
1500                     TAG, mPipTransitionState.getTransitionState());
1501             return;
1502         }
1503 
1504         finishResize(createFinishResizeSurfaceTransaction(destinationBounds), destinationBounds,
1505                 direction, -1);
1506         if (updateBoundsCallback != null) {
1507             updateBoundsCallback.accept(destinationBounds);
1508         }
1509     }
1510 
createFinishResizeSurfaceTransaction( Rect destinationBounds)1511     private SurfaceControl.Transaction createFinishResizeSurfaceTransaction(
1512             Rect destinationBounds) {
1513         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
1514         mSurfaceTransactionHelper
1515                 .crop(tx, mLeash, destinationBounds)
1516                 .resetScale(tx, mLeash, destinationBounds)
1517                 .round(tx, mLeash, mPipTransitionState.isInPip());
1518         return tx;
1519     }
1520 
1521     /**
1522      * Offset the PiP window by a given offset on Y-axis, triggered also from screen rotation.
1523      */
scheduleOffsetPip(Rect originalBounds, int offset, int duration, Consumer<Rect> updateBoundsCallback)1524     public void scheduleOffsetPip(Rect originalBounds, int offset, int duration,
1525             Consumer<Rect> updateBoundsCallback) {
1526         if (mPipTransitionState.shouldBlockResizeRequest()
1527                 || mPipTransitionState.getInSwipePipToHomeTransition()) {
1528             return;
1529         }
1530         if (mWaitForFixedRotation) {
1531             ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1532                     "%s: skip scheduleOffsetPip, entering pip deferred", TAG);
1533             return;
1534         }
1535         offsetPip(originalBounds, 0 /* xOffset */, offset, duration);
1536         Rect toBounds = new Rect(originalBounds);
1537         toBounds.offset(0, offset);
1538         if (updateBoundsCallback != null) {
1539             updateBoundsCallback.accept(toBounds);
1540         }
1541     }
1542 
offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs)1543     private void offsetPip(Rect originalBounds, int xOffset, int yOffset, int durationMs) {
1544         if (mTaskInfo == null) {
1545             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: mTaskInfo is not set",
1546                     TAG);
1547             return;
1548         }
1549         final Rect destinationBounds = new Rect(originalBounds);
1550         destinationBounds.offset(xOffset, yOffset);
1551         animateResizePip(originalBounds, destinationBounds, null /* sourceHintRect */,
1552                 TRANSITION_DIRECTION_SAME, durationMs, 0);
1553     }
1554 
finishResize(SurfaceControl.Transaction tx, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, @PipAnimationController.AnimationType int type)1555     private void finishResize(SurfaceControl.Transaction tx, Rect destinationBounds,
1556             @PipAnimationController.TransitionDirection int direction,
1557             @PipAnimationController.AnimationType int type) {
1558         final Rect preResizeBounds = new Rect(mPipBoundsState.getBounds());
1559         mPipBoundsState.setBounds(destinationBounds);
1560         if (direction == TRANSITION_DIRECTION_REMOVE_STACK) {
1561             removePipImmediately();
1562             return;
1563         } else if (isInPipDirection(direction) && type == ANIM_TYPE_ALPHA) {
1564             // TODO: Synchronize this correctly in #applyEnterPipSyncTransaction
1565             finishResizeForMenu(destinationBounds);
1566             return;
1567         }
1568 
1569         WindowContainerTransaction wct = new WindowContainerTransaction();
1570         prepareFinishResizeTransaction(destinationBounds, direction, tx, wct);
1571 
1572         // Only corner drag, pinch or expand/un-expand resizing may lead to animating the finish
1573         // resize operation.
1574         final boolean mayAnimateFinishResize = direction == TRANSITION_DIRECTION_USER_RESIZE
1575                 || direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
1576                 || direction == TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
1577         // Animate with a cross-fade if enabled and seamless resize is disables by the app.
1578         final boolean animateCrossFadeResize = mayAnimateFinishResize
1579                 && mPictureInPictureParams != null
1580                 && !mPictureInPictureParams.isSeamlessResizeEnabled();
1581         if (animateCrossFadeResize) {
1582             // Take a snapshot of the PIP task and show it. We'll fade it out after the wct
1583             // transaction is applied and the activity is laid out again.
1584             preResizeBounds.offsetTo(0, 0);
1585             final Rect snapshotDest = new Rect(0, 0, destinationBounds.width(),
1586                     destinationBounds.height());
1587             // Note: Put this at layer=MAX_VALUE-2 since the input consumer for PIP is placed at
1588             //       MAX_VALUE-1
1589             final SurfaceControl snapshotSurface = ScreenshotUtils.takeScreenshot(
1590                     mSurfaceControlTransactionFactory.getTransaction(), mLeash, preResizeBounds,
1591                     Integer.MAX_VALUE - 2);
1592             if (snapshotSurface != null) {
1593                 mSyncTransactionQueue.queue(wct);
1594                 mSyncTransactionQueue.runInSync(t -> {
1595                     // reset the pinch gesture
1596                     maybePerformFinishResizeCallback();
1597 
1598                     // Scale the snapshot from its pre-resize bounds to the post-resize bounds.
1599                     mSurfaceTransactionHelper.scale(t, snapshotSurface, preResizeBounds,
1600                             snapshotDest);
1601 
1602                     // Start animation to fade out the snapshot.
1603                     fadeOutAndRemoveOverlay(snapshotSurface,
1604                             null /* callback */, false /* withStartDelay */);
1605                 });
1606             } else {
1607                 applyFinishBoundsResize(wct, direction, false);
1608             }
1609         } else {
1610             applyFinishBoundsResize(wct, direction, isPipToTopLeft());
1611             // Use sync transaction to apply finish transaction for enter split case.
1612             if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
1613                 mSyncTransactionQueue.runInSync(t -> {
1614                     t.merge(tx);
1615                 });
1616             }
1617         }
1618 
1619         finishResizeForMenu(destinationBounds);
1620     }
1621 
1622     /** Moves the PiP menu to the destination bounds. */
finishResizeForMenu(Rect destinationBounds)1623     public void finishResizeForMenu(Rect destinationBounds) {
1624         if (!isInPip()) {
1625             return;
1626         }
1627         mPipMenuController.movePipMenu(null, null, destinationBounds,
1628                 PipMenuController.ALPHA_NO_CHANGE);
1629         mPipMenuController.updateMenuBounds(destinationBounds);
1630     }
1631 
prepareFinishResizeTransaction(Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, SurfaceControl.Transaction tx, WindowContainerTransaction wct)1632     private void prepareFinishResizeTransaction(Rect destinationBounds,
1633             @PipAnimationController.TransitionDirection int direction,
1634             SurfaceControl.Transaction tx,
1635             WindowContainerTransaction wct) {
1636         if (mLeash == null || !mLeash.isValid()) {
1637             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1638                     "%s: Invalid leash on prepareFinishResizeTransaction: %s", TAG, mLeash);
1639             return;
1640         }
1641         final Rect taskBounds;
1642         if (isInPipDirection(direction)) {
1643             // If we are animating from fullscreen using a bounds animation, then reset the
1644             // activity windowing mode set by WM, and set the task bounds to the final bounds
1645             taskBounds = destinationBounds;
1646             wct.setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
1647         } else if (isOutPipDirection(direction)) {
1648             // If we are animating to fullscreen or split screen, then we need to reset the
1649             // override bounds on the task to ensure that the task "matches" the parent's bounds.
1650             taskBounds = null;
1651             applyWindowingModeChangeOnExit(wct, direction);
1652         } else {
1653             // Just a resize in PIP
1654             taskBounds = destinationBounds;
1655         }
1656         mSurfaceTransactionHelper.round(tx, mLeash, isInPip());
1657 
1658         wct.setBounds(mToken, taskBounds);
1659         // Pip to split should use sync transaction to sync split bounds change.
1660         if (direction != TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
1661             wct.setBoundsChangeTransaction(mToken, tx);
1662         }
1663     }
1664 
1665     /**
1666      * Applies the window container transaction to finish a bounds resize.
1667      *
1668      * Called by {@link #finishResize(SurfaceControl.Transaction, Rect, int, int)}} once it has
1669      * finished preparing the transaction. It allows subclasses to modify the transaction before
1670      * applying it.
1671      */
applyFinishBoundsResize(@onNull WindowContainerTransaction wct, @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft)1672     public void applyFinishBoundsResize(@NonNull WindowContainerTransaction wct,
1673             @PipAnimationController.TransitionDirection int direction, boolean wasPipTopLeft) {
1674         if (direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN) {
1675             mSplitScreenOptional.ifPresent(splitScreenController ->
1676                     splitScreenController.enterSplitScreen(mTaskInfo.taskId, wasPipTopLeft, wct));
1677         } else {
1678             mTaskOrganizer.applyTransaction(wct);
1679         }
1680     }
1681 
isPipToTopLeft()1682     private boolean isPipToTopLeft() {
1683         if (!mSplitScreenOptional.isPresent()) {
1684             return false;
1685         }
1686         return mSplitScreenOptional.get().getActivateSplitPosition(mTaskInfo)
1687                 == SPLIT_POSITION_TOP_OR_LEFT;
1688     }
1689 
1690     /**
1691      * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
1692      * and can be overridden to restore to an alternate windowing mode.
1693      */
getOutPipWindowingMode()1694     public int getOutPipWindowingMode() {
1695         // By default, simply reset the windowing mode to undefined.
1696         return WINDOWING_MODE_UNDEFINED;
1697     }
1698 
animateResizePip( Rect currentBounds, Rect destinationBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, int durationMs, float startingAngle)1699     private @Nullable PipAnimationController.PipTransitionAnimator<?> animateResizePip(
1700             Rect currentBounds, Rect destinationBounds, Rect sourceHintRect,
1701             @PipAnimationController.TransitionDirection int direction, int durationMs,
1702             float startingAngle) {
1703         // Could happen when exitPip
1704         if (mToken == null || mLeash == null) {
1705             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1706                     "%s: Abort animation, invalid leash", TAG);
1707             return null;
1708         }
1709         if (isInPipDirection(direction) && !PipBoundsAlgorithm
1710                 .isSourceRectHintValidForEnterPip(sourceHintRect, destinationBounds)) {
1711             // The given source rect hint is too small for enter PiP animation, reset it to null.
1712             sourceHintRect = null;
1713         }
1714         final int rotationDelta = mWaitForFixedRotation
1715                 ? deltaRotation(mCurrentRotation, mNextRotation)
1716                 : Surface.ROTATION_0;
1717         if (rotationDelta != Surface.ROTATION_0) {
1718             sourceHintRect = computeRotatedBounds(rotationDelta, direction, destinationBounds,
1719                     sourceHintRect);
1720         }
1721         Rect baseBounds = direction == TRANSITION_DIRECTION_SNAP_AFTER_RESIZE
1722                 ? mPipBoundsState.getBounds() : currentBounds;
1723         final boolean existingAnimatorRunning = mPipAnimationController.getCurrentAnimator() != null
1724                 && mPipAnimationController.getCurrentAnimator().isRunning();
1725         final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController
1726                 .getAnimator(mTaskInfo, mLeash, baseBounds, currentBounds, destinationBounds,
1727                         sourceHintRect, direction, startingAngle, rotationDelta);
1728         animator.setTransitionDirection(direction)
1729                 .setPipTransactionHandler(mPipTransactionHandler)
1730                 .setDuration(durationMs);
1731         if (!existingAnimatorRunning) {
1732             animator.setPipAnimationCallback(mPipAnimationCallback);
1733         }
1734         if (isInPipDirection(direction)) {
1735             // Similar to auto-enter-pip transition, we use content overlay when there is no
1736             // source rect hint to enter PiP use bounds animation.
1737             if (sourceHintRect == null) {
1738                 // We use content overlay when there is no source rect hint to enter PiP use bounds
1739                 // animation.
1740                 // TODO(b/272819817): cleanup the null-check and extra logging.
1741                 final boolean hasTopActivityInfo = mTaskInfo.topActivityInfo != null;
1742                 if (hasTopActivityInfo) {
1743                     animator.setAppIconContentOverlay(
1744                             mContext, currentBounds, mTaskInfo.topActivityInfo,
1745                             mPipBoundsState.getLauncherState().getAppIconSizePx());
1746                 } else {
1747                     ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
1748                             "%s: TaskInfo.topActivityInfo is null", TAG);
1749                     animator.setColorContentOverlay(mContext);
1750                 }
1751             } else {
1752                 final TaskSnapshot snapshot = PipUtils.getTaskSnapshot(
1753                         mTaskInfo.launchIntoPipHostTaskId, false /* isLowResolution */);
1754                 if (snapshot != null) {
1755                     // use the task snapshot during the animation, this is for
1756                     // launch-into-pip aka. content-pip use case.
1757                     animator.setSnapshotContentOverlay(snapshot, sourceHintRect);
1758                 }
1759             }
1760             // The destination bounds are used for the end rect of animation and the final bounds
1761             // after animation finishes. So after the animation is started, the destination bounds
1762             // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout
1763             // without affecting the animation.
1764             if (rotationDelta != Surface.ROTATION_0) {
1765                 animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
1766             }
1767         }
1768         animator.start();
1769         return animator;
1770     }
1771 
1772     /** Computes destination bounds in old rotation and returns source hint rect if available.
1773      *
1774      * Note: updates the internal state of {@link PipDisplayLayoutState} by applying a rotation
1775      * transformation onto the display layout.
1776      */
computeRotatedBounds(int rotationDelta, int direction, Rect outDestinationBounds, Rect sourceHintRect)1777     private @Nullable Rect computeRotatedBounds(int rotationDelta, int direction,
1778             Rect outDestinationBounds, Rect sourceHintRect) {
1779         if (direction == TRANSITION_DIRECTION_TO_PIP) {
1780             mPipDisplayLayoutState.rotateTo(mNextRotation);
1781 
1782             final Rect displayBounds = mPipBoundsState.getDisplayBounds();
1783             outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds());
1784             // Transform the destination bounds to current display coordinates.
1785             rotateBounds(outDestinationBounds, displayBounds, mNextRotation, mCurrentRotation);
1786             // When entering PiP (from button navigation mode), adjust the source rect hint by
1787             // display cutout if applicable.
1788             if (sourceHintRect != null && mTaskInfo.displayCutoutInsets != null) {
1789                 if (rotationDelta == Surface.ROTATION_270) {
1790                     sourceHintRect.offset(mTaskInfo.displayCutoutInsets.left,
1791                             mTaskInfo.displayCutoutInsets.top);
1792                 }
1793             }
1794         } else if (direction == TRANSITION_DIRECTION_LEAVE_PIP) {
1795             final Rect rotatedDestinationBounds = new Rect(outDestinationBounds);
1796             rotateBounds(rotatedDestinationBounds, mPipBoundsState.getDisplayBounds(),
1797                     rotationDelta);
1798             return PipBoundsAlgorithm.getValidSourceHintRect(mPictureInPictureParams,
1799                     rotatedDestinationBounds);
1800         }
1801         return sourceHintRect;
1802     }
1803 
1804     /**
1805      * Sync with {@link SplitScreenController} on destination bounds if PiP is going to
1806      * split screen.
1807      *
1808      * @param destinationBoundsOut contain the updated destination bounds if applicable
1809      * @return {@code true} if destinationBounds is altered for split screen
1810      */
syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit)1811     private boolean syncWithSplitScreenBounds(Rect destinationBoundsOut, boolean enterSplit) {
1812         if (mSplitScreenOptional.isEmpty()) {
1813             return false;
1814         }
1815         final SplitScreenController split = mSplitScreenOptional.get();
1816         final int position = mTaskInfo.lastParentTaskIdBeforePip > 0
1817                 ? split.getSplitPosition(mTaskInfo.lastParentTaskIdBeforePip)
1818                 : SPLIT_POSITION_UNDEFINED;
1819         if (position == SPLIT_POSITION_UNDEFINED && !enterSplit) {
1820             return false;
1821         }
1822         final Rect topLeft = new Rect();
1823         final Rect bottomRight = new Rect();
1824         split.getStageBounds(topLeft, bottomRight);
1825         if (enterSplit) {
1826             destinationBoundsOut.set(isPipToTopLeft() ? topLeft : bottomRight);
1827             return true;
1828         }
1829         // Moving to an existing split task.
1830         destinationBoundsOut.set(position == SPLIT_POSITION_TOP_OR_LEFT ? topLeft : bottomRight);
1831         return false;
1832     }
1833 
1834     /**
1835      * Fades out and removes an overlay surface.
1836      */
fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback, boolean withStartDelay)1837     void fadeOutAndRemoveOverlay(SurfaceControl surface, Runnable callback,
1838             boolean withStartDelay) {
1839         if (surface == null || !surface.isValid()) {
1840             return;
1841         }
1842 
1843         final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
1844         animator.setDuration(mCrossFadeAnimationDuration);
1845         animator.addUpdateListener(animation -> {
1846             if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
1847                 // Could happen if onTaskVanished happens during the animation since we may have
1848                 // set a start delay on this animation.
1849                 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1850                         "%s: Task vanished, skip fadeOutAndRemoveOverlay", TAG);
1851                 PipAnimationController.quietCancel(animation);
1852             } else if (surface.isValid()) {
1853                 final float alpha = (float) animation.getAnimatedValue();
1854                 final SurfaceControl.Transaction transaction =
1855                         mSurfaceControlTransactionFactory.getTransaction();
1856                 transaction.setAlpha(surface, alpha);
1857                 transaction.apply();
1858             }
1859         });
1860         animator.addListener(new AnimatorListenerAdapter() {
1861             @Override
1862             public void onAnimationEnd(Animator animation) {
1863                 removeContentOverlay(surface, callback);
1864             }
1865         });
1866         animator.setStartDelay(withStartDelay ? CONTENT_OVERLAY_FADE_OUT_DELAY_MS : 0);
1867         animator.start();
1868     }
1869 
removeContentOverlay(SurfaceControl surface, Runnable callback)1870     private void removeContentOverlay(SurfaceControl surface, Runnable callback) {
1871         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
1872             // Avoid double removal, which is fatal.
1873             return;
1874         }
1875         if (surface == null || !surface.isValid()) {
1876             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
1877                     "%s: trying to remove invalid content overlay (%s)", TAG, surface);
1878             return;
1879         }
1880         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
1881         tx.remove(surface);
1882         tx.apply();
1883         if (callback != null) callback.run();
1884     }
1885 
resetShadowRadius()1886     private void resetShadowRadius() {
1887         if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) {
1888             // mLeash is undefined when in PipTransitionState.UNDEFINED
1889             return;
1890         }
1891         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
1892         tx.setShadowRadius(mLeash, 0f);
1893         tx.apply();
1894     }
1895 
cancelCurrentAnimator()1896     private void cancelCurrentAnimator() {
1897         final PipAnimationController.PipTransitionAnimator<?> animator =
1898                 mPipAnimationController.getCurrentAnimator();
1899         if (animator != null) {
1900             if (animator.getContentOverlayLeash() != null) {
1901                 removeContentOverlay(animator.getContentOverlayLeash(),
1902                         animator::clearContentOverlay);
1903             }
1904             PipAnimationController.quietCancel(animator);
1905             mPipAnimationController.resetAnimatorState();
1906         }
1907     }
1908 
1909     @VisibleForTesting
setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory)1910     public void setSurfaceControlTransactionFactory(
1911             PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
1912         mSurfaceControlTransactionFactory = factory;
1913     }
1914 
isLaunchToSplit(TaskInfo taskInfo)1915     public boolean isLaunchToSplit(TaskInfo taskInfo) {
1916         return mSplitScreenOptional.isPresent()
1917                 && mSplitScreenOptional.get().isLaunchToSplit(taskInfo);
1918     }
1919 
1920     /**
1921      * Dumps internal states.
1922      */
1923     @Override
dump(PrintWriter pw, String prefix)1924     public void dump(PrintWriter pw, String prefix) {
1925         final String innerPrefix = prefix + "  ";
1926         pw.println(prefix + TAG);
1927         pw.println(innerPrefix + "mTaskInfo=" + mTaskInfo);
1928         pw.println(innerPrefix + "mToken=" + mToken
1929                 + " binder=" + (mToken != null ? mToken.asBinder() : null));
1930         pw.println(innerPrefix + "mLeash=" + mLeash);
1931         pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState());
1932         pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams);
1933         mPipTransitionController.dump(pw, innerPrefix);
1934     }
1935 
1936     @Override
toString()1937     public String toString() {
1938         return TAG + ":" + taskListenerTypeToString(TASK_LISTENER_TYPE_PIP);
1939     }
1940 }
1941