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