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