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