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