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