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