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