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