1 /* 2 * Copyright (C) 2021 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.ROTATION_UNDEFINED; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 22 import static android.util.RotationUtils.deltaRotation; 23 import static android.util.RotationUtils.rotateBounds; 24 import static android.view.Surface.ROTATION_270; 25 import static android.view.Surface.ROTATION_90; 26 import static android.view.WindowManager.TRANSIT_CHANGE; 27 import static android.view.WindowManager.TRANSIT_OPEN; 28 import static android.view.WindowManager.TRANSIT_PIP; 29 import static android.view.WindowManager.TRANSIT_TO_BACK; 30 import static android.view.WindowManager.transitTypeToString; 31 import static android.window.TransitionInfo.FLAG_IS_DISPLAY; 32 33 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA; 34 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS; 35 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP; 36 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_SAME; 37 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP; 38 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection; 39 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection; 40 import static com.android.wm.shell.pip.PipTransitionState.ENTERED_PIP; 41 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP; 42 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT; 43 import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; 44 45 import android.animation.Animator; 46 import android.app.ActivityManager; 47 import android.app.TaskInfo; 48 import android.content.Context; 49 import android.graphics.Point; 50 import android.graphics.Rect; 51 import android.os.IBinder; 52 import android.view.Surface; 53 import android.view.SurfaceControl; 54 import android.view.WindowManager; 55 import android.window.TaskSnapshot; 56 import android.window.TransitionInfo; 57 import android.window.TransitionRequestInfo; 58 import android.window.WindowContainerToken; 59 import android.window.WindowContainerTransaction; 60 61 import androidx.annotation.NonNull; 62 import androidx.annotation.Nullable; 63 64 import com.android.internal.protolog.common.ProtoLog; 65 import com.android.wm.shell.R; 66 import com.android.wm.shell.ShellTaskOrganizer; 67 import com.android.wm.shell.common.pip.PipBoundsAlgorithm; 68 import com.android.wm.shell.common.pip.PipBoundsState; 69 import com.android.wm.shell.common.pip.PipDisplayLayoutState; 70 import com.android.wm.shell.common.pip.PipUtils; 71 import com.android.wm.shell.protolog.ShellProtoLogGroup; 72 import com.android.wm.shell.splitscreen.SplitScreenController; 73 import com.android.wm.shell.sysui.ShellInit; 74 import com.android.wm.shell.transition.CounterRotatorHelper; 75 import com.android.wm.shell.transition.Transitions; 76 import com.android.wm.shell.util.TransitionUtil; 77 78 import java.io.PrintWriter; 79 import java.util.Optional; 80 81 /** 82 * Implementation of transitions for PiP on phone. Responsible for enter (alpha, bounds) and 83 * exit animation. 84 */ 85 public class PipTransition extends PipTransitionController { 86 87 private static final String TAG = PipTransition.class.getSimpleName(); 88 89 private final Context mContext; 90 private final PipTransitionState mPipTransitionState; 91 private final PipDisplayLayoutState mPipDisplayLayoutState; 92 private final int mEnterExitAnimationDuration; 93 private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; 94 private final Optional<SplitScreenController> mSplitScreenOptional; 95 private final PipAnimationController mPipAnimationController; 96 private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; 97 private Transitions.TransitionFinishCallback mFinishCallback; 98 private SurfaceControl.Transaction mFinishTransaction; 99 private final Rect mExitDestinationBounds = new Rect(); 100 @Nullable 101 private IBinder mExitTransition; 102 @Nullable 103 private IBinder mMoveToBackTransition; 104 private IBinder mRequestedEnterTransition; 105 private WindowContainerToken mRequestedEnterTask; 106 /** The Task window that is currently in PIP windowing mode. */ 107 @Nullable 108 private WindowContainerToken mCurrentPipTaskToken; 109 /** Whether display is in fixed rotation. */ 110 private boolean mInFixedRotation; 111 /** 112 * The rotation that the display will apply after expanding PiP to fullscreen. This is only 113 * meaningful if {@link #mInFixedRotation} is true. 114 */ 115 @Surface.Rotation 116 private int mEndFixedRotation; 117 /** Whether the PIP window has fade out for fixed rotation. */ 118 private boolean mHasFadeOut; 119 120 /** Used for setting transform to a transaction from animator. */ 121 private final PipAnimationController.PipTransactionHandler mTransactionConsumer = 122 new PipAnimationController.PipTransactionHandler() { 123 @Override 124 public boolean handlePipTransaction(SurfaceControl leash, 125 SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) { 126 // Only set the operation to transaction but do not apply. 127 return true; 128 } 129 }; 130 PipTransition(Context context, @NonNull ShellInit shellInit, @NonNull ShellTaskOrganizer shellTaskOrganizer, @NonNull Transitions transitions, PipBoundsState pipBoundsState, PipDisplayLayoutState pipDisplayLayoutState, PipTransitionState pipTransitionState, PipMenuController pipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm, PipAnimationController pipAnimationController, PipSurfaceTransactionHelper pipSurfaceTransactionHelper, Optional<SplitScreenController> splitScreenOptional)131 public PipTransition(Context context, 132 @NonNull ShellInit shellInit, 133 @NonNull ShellTaskOrganizer shellTaskOrganizer, 134 @NonNull Transitions transitions, 135 PipBoundsState pipBoundsState, 136 PipDisplayLayoutState pipDisplayLayoutState, 137 PipTransitionState pipTransitionState, 138 PipMenuController pipMenuController, 139 PipBoundsAlgorithm pipBoundsAlgorithm, 140 PipAnimationController pipAnimationController, 141 PipSurfaceTransactionHelper pipSurfaceTransactionHelper, 142 Optional<SplitScreenController> splitScreenOptional) { 143 super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController, 144 pipBoundsAlgorithm); 145 mContext = context; 146 mPipTransitionState = pipTransitionState; 147 mPipDisplayLayoutState = pipDisplayLayoutState; 148 mPipAnimationController = pipAnimationController; 149 mEnterExitAnimationDuration = context.getResources() 150 .getInteger(R.integer.config_pipResizeAnimationDuration); 151 mSurfaceTransactionHelper = pipSurfaceTransactionHelper; 152 mSplitScreenOptional = splitScreenOptional; 153 } 154 155 @Override onInit()156 protected void onInit() { 157 if (!PipUtils.isPip2ExperimentEnabled()) { 158 mTransitions.addHandler(this); 159 } 160 } 161 162 @Override startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds)163 public void startExitTransition(int type, WindowContainerTransaction out, 164 @Nullable Rect destinationBounds) { 165 if (destinationBounds != null) { 166 mExitDestinationBounds.set(destinationBounds); 167 } 168 final PipAnimationController.PipTransitionAnimator animator = 169 mPipAnimationController.getCurrentAnimator(); 170 if (animator != null && animator.isRunning()) { 171 animator.cancel(); 172 } 173 mExitTransition = mTransitions.startTransition(type, out, this); 174 } 175 176 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)177 public boolean startAnimation(@NonNull IBinder transition, 178 @NonNull TransitionInfo info, 179 @NonNull SurfaceControl.Transaction startTransaction, 180 @NonNull SurfaceControl.Transaction finishTransaction, 181 @NonNull Transitions.TransitionFinishCallback finishCallback) { 182 final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); 183 final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); 184 mInFixedRotation = fixedRotationChange != null; 185 mEndFixedRotation = mInFixedRotation 186 ? fixedRotationChange.getEndFixedRotation() 187 : ROTATION_UNDEFINED; 188 189 // Exiting PIP. 190 final int type = info.getType(); 191 if (transition.equals(mExitTransition) || transition.equals(mMoveToBackTransition)) { 192 mExitDestinationBounds.setEmpty(); 193 mExitTransition = null; 194 mMoveToBackTransition = null; 195 mHasFadeOut = false; 196 if (mFinishCallback != null) { 197 callFinishCallback(null /* wct */); 198 mFinishTransaction = null; 199 throw new RuntimeException("Previous callback not called, aborting exit PIP."); 200 } 201 202 // PipTaskChange can be null if the PIP task has been detached, for example, when the 203 // task contains multiple activities, the PIP will be moved to a new PIP task when 204 // entering, and be moved back when exiting. In that case, the PIP task will be removed 205 // immediately. 206 final TaskInfo pipTaskInfo = currentPipTaskChange != null 207 ? currentPipTaskChange.getTaskInfo() 208 : mPipOrganizer.getTaskInfo(); 209 if (pipTaskInfo == null) { 210 throw new RuntimeException("Cannot find the pip task for exit-pip transition."); 211 } 212 213 switch (type) { 214 case TRANSIT_EXIT_PIP: 215 startExitAnimation(info, startTransaction, finishTransaction, finishCallback, 216 pipTaskInfo, currentPipTaskChange); 217 break; 218 case TRANSIT_EXIT_PIP_TO_SPLIT: 219 startExitToSplitAnimation(info, startTransaction, finishTransaction, 220 finishCallback, pipTaskInfo); 221 break; 222 case TRANSIT_TO_BACK: 223 // pass through here is intended 224 case TRANSIT_REMOVE_PIP: 225 removePipImmediately(info, startTransaction, finishTransaction, finishCallback, 226 pipTaskInfo); 227 break; 228 default: 229 throw new IllegalStateException("mExitTransition with unexpected transit type=" 230 + transitTypeToString(type)); 231 } 232 mCurrentPipTaskToken = null; 233 return true; 234 } else if (transition == mRequestedEnterTransition) { 235 mRequestedEnterTransition = null; 236 mRequestedEnterTask = null; 237 } 238 239 // The previous PIP Task is no longer in PIP, but this is not an exit transition (This can 240 // happen when a new activity requests enter PIP). In this case, we just show this Task in 241 // its end state, and play other animation as normal. 242 if (currentPipTaskChange != null 243 && currentPipTaskChange.getTaskInfo().getWindowingMode() != WINDOWING_MODE_PINNED) { 244 resetPrevPip(currentPipTaskChange, startTransaction); 245 } 246 247 // Entering PIP. 248 if (isEnteringPip(info)) { 249 startEnterAnimation(info, startTransaction, finishTransaction, finishCallback); 250 return true; 251 } 252 253 // For transition that we don't animate, but contains the PIP leash, we need to update the 254 // PIP surface, otherwise it will be reset after the transition. 255 if (currentPipTaskChange != null) { 256 // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is 257 // changing the *finish*Transaction, we need to use the end bounds. This will also 258 // make sure that the fade-in animation (below) uses the end bounds as well. 259 if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) { 260 mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds()); 261 } 262 updatePipForUnhandledTransition(currentPipTaskChange, startTransaction, 263 finishTransaction); 264 } 265 266 return false; 267 } 268 269 @Override mergeAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback)270 public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, 271 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, 272 @NonNull Transitions.TransitionFinishCallback finishCallback) { 273 end(); 274 } 275 276 /** Helper to identify whether this handler is currently the one playing an animation */ isAnimatingLocally()277 private boolean isAnimatingLocally() { 278 return mFinishTransaction != null; 279 } 280 281 @Nullable 282 @Override handleRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request)283 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 284 @NonNull TransitionRequestInfo request) { 285 if (requestHasPipEnter(request)) { 286 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 287 "%s: handle PiP enter request", TAG); 288 WindowContainerTransaction wct = new WindowContainerTransaction(); 289 augmentRequest(transition, request, wct); 290 return wct; 291 } else if (request.getType() == TRANSIT_TO_BACK && request.getTriggerTask() != null 292 && request.getTriggerTask().getWindowingMode() == WINDOWING_MODE_PINNED) { 293 // if we receive a TRANSIT_TO_BACK type of request while in PiP 294 mMoveToBackTransition = transition; 295 // update the transition state to avoid {@link PipTaskOrganizer#onTaskVanished()} calls 296 mPipTransitionState.setTransitionState(PipTransitionState.EXITING_PIP); 297 298 // return an empty WindowContainerTransaction so that we don't check other handlers 299 return new WindowContainerTransaction(); 300 } else { 301 return null; 302 } 303 } 304 305 @Override augmentRequest(@onNull IBinder transition, @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT)306 public void augmentRequest(@NonNull IBinder transition, 307 @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) { 308 if (!requestHasPipEnter(request)) { 309 throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); 310 } 311 if (mEnterAnimationType == ANIM_TYPE_ALPHA) { 312 mRequestedEnterTransition = transition; 313 mRequestedEnterTask = request.getTriggerTask().token; 314 outWCT.setActivityWindowingMode(request.getTriggerTask().token, 315 WINDOWING_MODE_UNDEFINED); 316 final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 317 outWCT.setBounds(request.getTriggerTask().token, destinationBounds); 318 } 319 } 320 321 @Override end()322 public void end() { 323 Animator animator = mPipAnimationController.getCurrentAnimator(); 324 if (animator != null && animator.isRunning()) { 325 animator.end(); 326 } 327 } 328 329 @Override handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct)330 public boolean handleRotateDisplay(int startRotation, int endRotation, 331 WindowContainerTransaction wct) { 332 if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) { 333 // A fade-in was requested but not-yet started. In this case, just recalculate the 334 // initial state under the new rotation. 335 int rotationDelta = deltaRotation(startRotation, endRotation); 336 if (rotationDelta != Surface.ROTATION_0) { 337 mPipDisplayLayoutState.rotateTo(endRotation); 338 339 final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 340 wct.setBounds(mRequestedEnterTask, destinationBounds); 341 return true; 342 } 343 } 344 return false; 345 } 346 347 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT)348 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 349 @Nullable SurfaceControl.Transaction finishT) { 350 if (transition != mExitTransition) { 351 return; 352 } 353 // This means an expand happened before enter-pip finished and we are now "merging" a 354 // no-op transition that happens to match our exit-pip. 355 // Or that the keyguard is up and preventing the transition from applying, in which case we 356 // want to manually reset pip. (b/283783868) 357 boolean cancelled = false; 358 if (mPipAnimationController.getCurrentAnimator() != null) { 359 mPipAnimationController.getCurrentAnimator().cancel(); 360 mPipAnimationController.resetAnimatorState(); 361 cancelled = true; 362 } 363 364 // Unset exitTransition AFTER cancel so that finishResize knows we are merging. 365 mExitTransition = null; 366 if (!cancelled) return; 367 final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); 368 if (taskInfo != null) { 369 if (aborted) { 370 // keyguard case - the transition got aborted, so we want to reset state and 371 // windowing mode before reapplying the resize transaction 372 sendOnPipTransitionFinished(TRANSITION_DIRECTION_LEAVE_PIP); 373 mPipOrganizer.onExitPipFinished(taskInfo); 374 375 WindowContainerTransaction wct = new WindowContainerTransaction(); 376 mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); 377 wct.setBounds(taskInfo.token, null); 378 mPipOrganizer.applyFinishBoundsResize(wct, TRANSITION_DIRECTION_LEAVE_PIP, false); 379 } else { 380 // merge case 381 startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(), 382 mPipBoundsState.getBounds(), mPipBoundsState.getBounds(), 383 new Rect(mExitDestinationBounds), Surface.ROTATION_0, null /* startT */); 384 } 385 } 386 mExitDestinationBounds.setEmpty(); 387 mCurrentPipTaskToken = null; 388 } 389 390 @Override onFinishResize(TaskInfo taskInfo, Rect destinationBounds, @PipAnimationController.TransitionDirection int direction, @NonNull SurfaceControl.Transaction tx)391 public void onFinishResize(TaskInfo taskInfo, Rect destinationBounds, 392 @PipAnimationController.TransitionDirection int direction, 393 @NonNull SurfaceControl.Transaction tx) { 394 final boolean enteringPip = isInPipDirection(direction); 395 if (enteringPip) { 396 mPipTransitionState.setTransitionState(ENTERED_PIP); 397 } 398 // If we have an exit transition, but aren't playing a transition locally, it 399 // means we're expecting the exit transition will be "merged" into another transition 400 // (likely a remote like launcher), so don't fire the finish-callback here -- wait until 401 // the exit transition is merged. 402 if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) { 403 final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); 404 final boolean hasValidLeash = leash != null && leash.isValid(); 405 WindowContainerTransaction wct = null; 406 if (isOutPipDirection(direction)) { 407 // Only need to reset surface properties. The server-side operations were already 408 // done at the start. But if it is running fixed rotation, there will be a seamless 409 // display transition later. So the last rotation transform needs to be kept to 410 // avoid flickering, and then the display transition will reset the transform. 411 if (!mInFixedRotation && mFinishTransaction != null) { 412 mFinishTransaction.merge(tx); 413 } 414 } else { 415 wct = new WindowContainerTransaction(); 416 if (isInPipDirection(direction)) { 417 // If we are animating from fullscreen using a bounds animation, then reset the 418 // activity windowing mode, and set the task bounds to the final bounds 419 wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); 420 wct.setBounds(taskInfo.token, destinationBounds); 421 } else { 422 wct.setBounds(taskInfo.token, null /* bounds */); 423 } 424 // Reset the scale with bounds change synchronously. 425 if (hasValidLeash) { 426 mSurfaceTransactionHelper.crop(tx, leash, destinationBounds) 427 .resetScale(tx, leash, destinationBounds) 428 .round(tx, leash, true /* applyCornerRadius */); 429 } 430 wct.setBoundsChangeTransaction(taskInfo.token, tx); 431 } 432 final int displayRotation = taskInfo.getConfiguration().windowConfiguration 433 .getDisplayRotation(); 434 if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation 435 && hasValidLeash) { 436 // Launcher may update the Shelf height during the animation, which will update the 437 // destination bounds. Because this is in fixed rotation, We need to make sure the 438 // finishTransaction is using the updated bounds in the display rotation. 439 final PipAnimationController.PipTransitionAnimator<?> animator = 440 mPipAnimationController.getCurrentAnimator(); 441 final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); 442 final Rect finishBounds = new Rect(destinationBounds); 443 rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation); 444 if (!finishBounds.equals(animator.getEndValue())) { 445 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 446 "%s: Destination bounds were changed during animation", TAG); 447 rotateBounds(finishBounds, displayBounds, mEndFixedRotation, displayRotation); 448 mSurfaceTransactionHelper.crop(mFinishTransaction, leash, finishBounds); 449 } 450 } 451 mFinishTransaction = null; 452 callFinishCallback(wct); 453 } 454 finishResizeForMenu(destinationBounds); 455 } 456 callFinishCallback(WindowContainerTransaction wct)457 private void callFinishCallback(WindowContainerTransaction wct) { 458 // Need to unset mFinishCallback first because onTransitionFinished can re-enter this 459 // handler if there is a pending PiP animation. 460 final Transitions.TransitionFinishCallback finishCallback = mFinishCallback; 461 mFinishCallback = null; 462 finishCallback.onTransitionFinished(wct); 463 } 464 465 @Override forceFinishTransition()466 public void forceFinishTransition() { 467 // mFinishCallback might be null with an outdated mCurrentPipTaskToken 468 // for example, when app crashes while in PiP and exit transition has not started 469 mCurrentPipTaskToken = null; 470 if (mFinishCallback == null) return; 471 mFinishCallback.onTransitionFinished(null /* wct */); 472 mFinishCallback = null; 473 mFinishTransaction = null; 474 } 475 476 @Override onFixedRotationStarted()477 public void onFixedRotationStarted() { 478 fadeEnteredPipIfNeed(false /* show */); 479 } 480 481 @Override onFixedRotationFinished()482 public void onFixedRotationFinished() { 483 fadeEnteredPipIfNeed(true /* show */); 484 } 485 fadeEnteredPipIfNeed(boolean show)486 private void fadeEnteredPipIfNeed(boolean show) { 487 // The transition with this fixed rotation may be handled by other handler before reaching 488 // PipTransition, so we cannot do this in #startAnimation. 489 if (!mPipTransitionState.hasEnteredPip()) { 490 return; 491 } 492 if (show && mHasFadeOut) { 493 // If there is a pending transition, then let startAnimation handle it. And if it is 494 // handled, mHasFadeOut will be set to false and this runnable will be no-op. Otherwise 495 // make sure the PiP will reshow, e.g. swipe-up with fixed rotation (fade-out) but 496 // return to the current app (only finish the recent transition). 497 mTransitions.runOnIdle(() -> { 498 if (mHasFadeOut && mPipTransitionState.hasEnteredPip()) { 499 fadeExistingPip(true /* show */); 500 } 501 }); 502 } else if (!show && !mHasFadeOut) { 503 // Fade out the existing PiP to avoid jump cut during seamless rotation. 504 fadeExistingPip(false /* show */); 505 } 506 } 507 508 @Nullable findCurrentPipTaskChange(@onNull TransitionInfo info)509 private TransitionInfo.Change findCurrentPipTaskChange(@NonNull TransitionInfo info) { 510 if (mCurrentPipTaskToken == null) { 511 return null; 512 } 513 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 514 final TransitionInfo.Change change = info.getChanges().get(i); 515 if (mCurrentPipTaskToken.equals(change.getContainer())) { 516 return change; 517 } 518 } 519 return null; 520 } 521 522 @Nullable findFixedRotationChange(@onNull TransitionInfo info)523 private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) { 524 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 525 final TransitionInfo.Change change = info.getChanges().get(i); 526 if (change.getEndFixedRotation() != ROTATION_UNDEFINED) { 527 return change; 528 } 529 } 530 return null; 531 } 532 startExitAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange)533 private void startExitAnimation(@NonNull TransitionInfo info, 534 @NonNull SurfaceControl.Transaction startTransaction, 535 @NonNull SurfaceControl.Transaction finishTransaction, 536 @NonNull Transitions.TransitionFinishCallback finishCallback, 537 @NonNull TaskInfo taskInfo, @Nullable TransitionInfo.Change pipTaskChange) { 538 TransitionInfo.Change pipChange = pipTaskChange; 539 SurfaceControl activitySc = null; 540 if (mCurrentPipTaskToken == null) { 541 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 542 "%s: There is no existing PiP Task for TRANSIT_EXIT_PIP", TAG); 543 } else if (pipChange == null) { 544 // The pipTaskChange is null, this can happen if we are reparenting the PIP activity 545 // back to its original Task. In that case, we should animate the activity leash 546 // instead, which should be the change whose last parent is the recorded PiP Task. 547 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 548 final TransitionInfo.Change change = info.getChanges().get(i); 549 if (mCurrentPipTaskToken.equals(change.getLastParent())) { 550 // Find the activity that is exiting PiP. 551 pipChange = change; 552 activitySc = change.getLeash(); 553 break; 554 } 555 } 556 } 557 if (pipChange == null) { 558 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 559 "%s: No window of exiting PIP is found. Can't play expand animation", TAG); 560 removePipImmediately(info, startTransaction, finishTransaction, finishCallback, 561 taskInfo); 562 return; 563 } 564 565 // When exiting PiP, the PiP leash may be an Activity of a multi-windowing Task, for which 566 // case it may not be in the screen coordinate. 567 // Reparent the pip leash to the root with max layer so that we can animate it outside of 568 // parent crop, and make sure it is not covered by other windows. 569 final TransitionInfo.Root root = TransitionUtil.getRootFor(pipChange, info); 570 final SurfaceControl pipLeash; 571 if (activitySc != null) { 572 // Use a local leash to animate activity in case the activity has letterbox which may 573 // be broken by PiP animation, e.g. always end at 0,0 in parent and unable to include 574 // letterbox area in crop bounds. 575 final SurfaceControl activitySurface = pipChange.getLeash(); 576 pipLeash = new SurfaceControl.Builder() 577 .setName(activitySc + "_pip-leash") 578 .setContainerLayer() 579 .setHidden(false) 580 .setParent(root.getLeash()) 581 .build(); 582 startTransaction.reparent(activitySurface, pipLeash); 583 // Put the activity at local position with offset in case it is letterboxed. 584 final Point activityOffset = pipChange.getEndRelOffset(); 585 startTransaction.setPosition(activitySc, activityOffset.x, activityOffset.y); 586 } else { 587 pipLeash = pipChange.getLeash(); 588 startTransaction.reparent(pipLeash, root.getLeash()); 589 } 590 startTransaction.setLayer(pipLeash, Integer.MAX_VALUE); 591 // Note: because of this, the bounds to animate should be translated to the root coordinate. 592 final Point offset = root.getOffset(); 593 final Rect currentBounds = mPipBoundsState.getBounds(); 594 currentBounds.offset(-offset.x, -offset.y); 595 startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top); 596 597 final WindowContainerToken pipTaskToken = pipChange.getContainer(); 598 final boolean useLocalLeash = activitySc != null; 599 final boolean toFullscreen = pipChange.getEndAbsBounds().equals( 600 mPipBoundsState.getDisplayBounds()); 601 mFinishCallback = (wct) -> { 602 mPipOrganizer.onExitPipFinished(taskInfo); 603 604 // TODO(b/286346098): remove the OPEN app flicker completely 605 // not checking if we go to fullscreen helps avoid getting pip into an inconsistent 606 // state after the flicker occurs. This is a temp solution until flicker is removed. 607 if (!Transitions.SHELL_TRANSITIONS_ROTATION) { 608 // will help to debug the case when we are not exiting to fullscreen 609 if (!toFullscreen) { 610 ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 611 "%s: startExitAnimation() not exiting to fullscreen", TAG); 612 } 613 wct = wct != null ? wct : new WindowContainerTransaction(); 614 wct.setBounds(pipTaskToken, null); 615 mPipOrganizer.applyWindowingModeChangeOnExit(wct, TRANSITION_DIRECTION_LEAVE_PIP); 616 } 617 if (useLocalLeash) { 618 if (mPipAnimationController.isAnimating()) { 619 mPipAnimationController.getCurrentAnimator().end(); 620 } 621 // Make sure the animator don't use the released leash, e.g. mergeAnimation. 622 mPipAnimationController.resetAnimatorState(); 623 finishTransaction.remove(pipLeash); 624 } 625 finishCallback.onTransitionFinished(wct); 626 }; 627 mFinishTransaction = finishTransaction; 628 629 // Check if it is Shell rotation. 630 if (Transitions.SHELL_TRANSITIONS_ROTATION) { 631 TransitionInfo.Change displayRotationChange = null; 632 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 633 final TransitionInfo.Change change = info.getChanges().get(i); 634 if (change.getMode() == TRANSIT_CHANGE 635 && (change.getFlags() & FLAG_IS_DISPLAY) != 0 636 && change.getStartRotation() != change.getEndRotation()) { 637 displayRotationChange = change; 638 break; 639 } 640 } 641 if (displayRotationChange != null) { 642 // Exiting PIP to fullscreen with orientation change. 643 startExpandAndRotationAnimation(info, startTransaction, finishTransaction, 644 displayRotationChange, taskInfo, pipChange, offset); 645 return; 646 } 647 } 648 649 final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds()); 650 destinationBounds.offset(-offset.x, -offset.y); 651 652 // Check if it is fixed rotation. 653 final int rotationDelta; 654 if (mInFixedRotation) { 655 final int startRotation = pipChange.getStartRotation(); 656 final int endRotation = mEndFixedRotation; 657 rotationDelta = deltaRotation(startRotation, endRotation); 658 final Rect endBounds = new Rect(destinationBounds); 659 660 // Set the end frame since the display won't rotate until fixed rotation is finished 661 // in the next display change transition. 662 rotateBounds(endBounds, destinationBounds, rotationDelta); 663 final int degree, x, y; 664 if (rotationDelta == ROTATION_90) { 665 degree = 90; 666 x = destinationBounds.right; 667 y = destinationBounds.top; 668 } else { 669 degree = -90; 670 x = destinationBounds.left; 671 y = destinationBounds.bottom; 672 } 673 mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction, 674 pipLeash, endBounds, endBounds, new Rect(), degree, x, y, 675 true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */); 676 } else { 677 rotationDelta = Surface.ROTATION_0; 678 } 679 startExpandAnimation(taskInfo, pipLeash, currentBounds, currentBounds, destinationBounds, 680 rotationDelta, startTransaction); 681 } 682 startExpandAndRotationAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionInfo.Change displayRotationChange, @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange, @NonNull Point offset)683 private void startExpandAndRotationAnimation(@NonNull TransitionInfo info, 684 @NonNull SurfaceControl.Transaction startTransaction, 685 @NonNull SurfaceControl.Transaction finishTransaction, 686 @NonNull TransitionInfo.Change displayRotationChange, 687 @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange, 688 @NonNull Point offset) { 689 final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(), 690 displayRotationChange.getEndRotation()); 691 692 // Counter-rotate all "going-away" things since they are still in the old orientation. 693 final CounterRotatorHelper rotator = new CounterRotatorHelper(); 694 rotator.handleClosingChanges(info, startTransaction, displayRotationChange); 695 696 // Get the start bounds in new orientation. 697 final Rect startBounds = new Rect(pipChange.getStartAbsBounds()); 698 rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta); 699 final Rect endBounds = new Rect(pipChange.getEndAbsBounds()); 700 startBounds.offset(-offset.x, -offset.y); 701 endBounds.offset(-offset.x, -offset.y); 702 703 // Reverse the rotation direction for expansion. 704 final int pipRotateDelta = deltaRotation(rotateDelta, 0); 705 706 // Set the start frame. 707 final int degree, x, y; 708 if (pipRotateDelta == ROTATION_90) { 709 degree = 90; 710 x = startBounds.right; 711 y = startBounds.top; 712 } else { 713 degree = -90; 714 x = startBounds.left; 715 y = startBounds.bottom; 716 } 717 mSurfaceTransactionHelper.rotateAndScaleWithCrop(startTransaction, pipChange.getLeash(), 718 endBounds, startBounds, new Rect(), degree, x, y, true /* isExpanding */, 719 pipRotateDelta == ROTATION_270 /* clockwise */); 720 startTransaction.apply(); 721 rotator.cleanUp(finishTransaction); 722 723 // Expand and rotate the pip window to fullscreen. 724 final PipAnimationController.PipTransitionAnimator animator = 725 mPipAnimationController.getAnimator(taskInfo, pipChange.getLeash(), 726 startBounds, startBounds, endBounds, null, TRANSITION_DIRECTION_LEAVE_PIP, 727 0 /* startingAngle */, pipRotateDelta); 728 animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) 729 .setPipAnimationCallback(mPipAnimationCallback) 730 .setDuration(mEnterExitAnimationDuration) 731 .start(); 732 } 733 startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, final Rect baseBounds, final Rect startBounds, final Rect endBounds, final int rotationDelta, @Nullable SurfaceControl.Transaction startTransaction)734 private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash, 735 final Rect baseBounds, final Rect startBounds, final Rect endBounds, 736 final int rotationDelta, @Nullable SurfaceControl.Transaction startTransaction) { 737 final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( 738 taskInfo.pictureInPictureParams, endBounds); 739 final PipAnimationController.PipTransitionAnimator animator = 740 mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds, 741 endBounds, sourceHintRect, TRANSITION_DIRECTION_LEAVE_PIP, 742 0 /* startingAngle */, rotationDelta); 743 animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) 744 .setDuration(mEnterExitAnimationDuration); 745 if (startTransaction != null) { 746 animator.setPipTransactionHandler(mTransactionConsumer).applySurfaceControlTransaction( 747 leash, startTransaction, PipAnimationController.FRACTION_START); 748 startTransaction.apply(); 749 } 750 animator.setPipAnimationCallback(mPipAnimationCallback) 751 .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()) 752 .start(); 753 } 754 755 /** For {@link Transitions#TRANSIT_REMOVE_PIP}, we just immediately remove the PIP Task. */ removePipImmediately(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo)756 private void removePipImmediately(@NonNull TransitionInfo info, 757 @NonNull SurfaceControl.Transaction startTransaction, 758 @NonNull SurfaceControl.Transaction finishTransaction, 759 @NonNull Transitions.TransitionFinishCallback finishCallback, 760 @NonNull TaskInfo taskInfo) { 761 startTransaction.apply(); 762 finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(), 763 mPipDisplayLayoutState.getDisplayBounds()); 764 mPipOrganizer.onExitPipFinished(taskInfo); 765 finishCallback.onTransitionFinished(null); 766 } 767 768 /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */ isEnteringPip(@onNull TransitionInfo info)769 private boolean isEnteringPip(@NonNull TransitionInfo info) { 770 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 771 final TransitionInfo.Change change = info.getChanges().get(i); 772 if (isEnteringPip(change, info.getType())) return true; 773 } 774 return false; 775 } 776 777 /** Whether a particular change is a window that is entering pip. */ 778 @Override isEnteringPip(@onNull TransitionInfo.Change change, @WindowManager.TransitionType int transitType)779 public boolean isEnteringPip(@NonNull TransitionInfo.Change change, 780 @WindowManager.TransitionType int transitType) { 781 if (change.getTaskInfo() != null 782 && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED 783 && !change.getContainer().equals(mCurrentPipTaskToken)) { 784 // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps 785 // that enter PiP instantly on opening, mostly from CTS/Flicker tests) 786 if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN) { 787 return true; 788 } 789 // This can happen if the request to enter PIP happens when we are collecting for 790 // another transition, such as TRANSIT_CHANGE (display rotation). 791 if (transitType == TRANSIT_CHANGE) { 792 return true; 793 } 794 795 // Please file a bug to handle the unexpected transition type. 796 android.util.Slog.e(TAG, "Found new PIP in transition with mis-matched type=" 797 + transitTypeToString(transitType), new Throwable()); 798 } 799 return false; 800 } 801 802 @Override setEnterAnimationType(@ipAnimationController.AnimationType int type)803 public void setEnterAnimationType(@PipAnimationController.AnimationType int type) { 804 mEnterAnimationType = type; 805 } 806 startEnterAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)807 private void startEnterAnimation(@NonNull TransitionInfo info, 808 @NonNull SurfaceControl.Transaction startTransaction, 809 @NonNull SurfaceControl.Transaction finishTransaction, 810 @NonNull Transitions.TransitionFinishCallback finishCallback) { 811 // Search for an Enter PiP transition 812 TransitionInfo.Change enterPip = null; 813 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 814 final TransitionInfo.Change change = info.getChanges().get(i); 815 if (change.getTaskInfo() != null 816 && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) { 817 enterPip = change; 818 } 819 } 820 if (enterPip == null) { 821 throw new IllegalStateException("Trying to start PiP animation without a pip" 822 + "participant"); 823 } 824 825 // Make sure other open changes are visible as entering PIP. Some may be hidden in 826 // Transitions#setupStartState because the transition type is OPEN (such as auto-enter). 827 for (int i = info.getChanges().size() - 1; i >= 0; --i) { 828 final TransitionInfo.Change change = info.getChanges().get(i); 829 if (change == enterPip) continue; 830 if (TransitionUtil.isOpeningType(change.getMode())) { 831 final SurfaceControl leash = change.getLeash(); 832 startTransaction.show(leash).setAlpha(leash, 1.f); 833 } 834 } 835 836 startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback); 837 } 838 839 @Override startEnterAnimation(@onNull final TransitionInfo.Change pipChange, @NonNull final SurfaceControl.Transaction startTransaction, @NonNull final SurfaceControl.Transaction finishTransaction, @NonNull final Transitions.TransitionFinishCallback finishCallback)840 public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange, 841 @NonNull final SurfaceControl.Transaction startTransaction, 842 @NonNull final SurfaceControl.Transaction finishTransaction, 843 @NonNull final Transitions.TransitionFinishCallback finishCallback) { 844 if (mFinishCallback != null) { 845 callFinishCallback(null /* wct */); 846 mFinishTransaction = null; 847 throw new RuntimeException("Previous callback not called, aborting entering PIP."); 848 } 849 850 // Keep track of the PIP task and animation. 851 mCurrentPipTaskToken = pipChange.getContainer(); 852 mHasFadeOut = false; 853 mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); 854 mFinishCallback = finishCallback; 855 mFinishTransaction = finishTransaction; 856 857 final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo(); 858 final SurfaceControl leash = pipChange.getLeash(); 859 final int startRotation = pipChange.getStartRotation(); 860 // Check again in case some callers use startEnterAnimation directly so the flag was not 861 // set in startAnimation, e.g. from DefaultMixedHandler. 862 if (!mInFixedRotation) { 863 mEndFixedRotation = pipChange.getEndFixedRotation(); 864 mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED; 865 } 866 final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation(); 867 868 setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, 869 taskInfo.topActivityInfo); 870 871 if (mPipOrganizer.shouldAttachMenuEarly()) { 872 mPipMenuController.attach(leash); 873 } 874 875 final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); 876 final Rect currentBounds = pipChange.getStartAbsBounds(); 877 int rotationDelta = deltaRotation(startRotation, endRotation); 878 Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( 879 taskInfo.pictureInPictureParams, currentBounds, destinationBounds); 880 if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { 881 // Need to get the bounds of new rotation in old rotation for fixed rotation, 882 computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, 883 destinationBounds, sourceHintRect); 884 } 885 if (!mPipOrganizer.shouldAttachMenuEarly()) { 886 mTransitions.getMainExecutor().executeDelayed( 887 () -> mPipMenuController.attach(leash), 0); 888 } 889 890 if (taskInfo.pictureInPictureParams != null 891 && taskInfo.pictureInPictureParams.isAutoEnterEnabled() 892 && mPipTransitionState.getInSwipePipToHomeTransition()) { 893 handleSwipePipToHomeTransition(startTransaction, finishTransaction, leash, 894 sourceHintRect, destinationBounds, taskInfo); 895 return; 896 } 897 898 final int enterAnimationType = mEnterAnimationType; 899 if (enterAnimationType == ANIM_TYPE_ALPHA) { 900 startTransaction.setAlpha(leash, 0f); 901 } else { 902 // set alpha to 1, because for multi-activity PiP it will create a new task with alpha 0 903 startTransaction.setAlpha(leash, 1f); 904 } 905 startTransaction.apply(); 906 907 PipAnimationController.PipTransitionAnimator animator; 908 if (enterAnimationType == ANIM_TYPE_BOUNDS) { 909 animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, 910 currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 911 0 /* startingAngle */, rotationDelta); 912 if (sourceHintRect == null) { 913 // We use content overlay when there is no source rect hint to enter PiP use bounds 914 // animation. 915 // TODO(b/272819817): cleanup the null-check and extra logging. 916 final boolean hasTopActivityInfo = taskInfo.topActivityInfo != null; 917 if (hasTopActivityInfo) { 918 animator.setAppIconContentOverlay( 919 mContext, currentBounds, taskInfo.topActivityInfo, 920 mPipBoundsState.getLauncherState().getAppIconSizePx()); 921 } else { 922 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 923 "%s: TaskInfo.topActivityInfo is null", TAG); 924 animator.setColorContentOverlay(mContext); 925 } 926 } else { 927 final TaskSnapshot snapshot = PipUtils.getTaskSnapshot( 928 taskInfo.launchIntoPipHostTaskId, false /* isLowResolution */); 929 if (snapshot != null) { 930 // use the task snapshot during the animation, this is for 931 // launch-into-pip aka. content-pip use case. 932 animator.setSnapshotContentOverlay(snapshot, sourceHintRect); 933 } 934 } 935 } else if (enterAnimationType == ANIM_TYPE_ALPHA) { 936 animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 937 0f, 1f); 938 mSurfaceTransactionHelper 939 .crop(finishTransaction, leash, destinationBounds) 940 .round(finishTransaction, leash, true /* applyCornerRadius */); 941 } else { 942 throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); 943 } 944 animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) 945 .setPipAnimationCallback(mPipAnimationCallback) 946 .setDuration(mEnterExitAnimationDuration); 947 if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { 948 // For fixed rotation, the animation destination bounds is in old rotation coordinates. 949 // Set the destination bounds to new coordinates after the animation is finished. 950 // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation. 951 animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds()); 952 } 953 // Keep the last appearance when finishing the transition. The transform will be reset when 954 // setting bounds. 955 animator.setPipTransactionHandler(mTransactionConsumer).applySurfaceControlTransaction( 956 leash, finishTransaction, PipAnimationController.FRACTION_END); 957 // Start to animate enter PiP. 958 animator.setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()).start(); 959 } 960 961 /** Computes destination bounds in old rotation and updates source hint rect if available. */ computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation, TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect)962 private void computeEnterPipRotatedBounds(int rotationDelta, int startRotation, int endRotation, 963 TaskInfo taskInfo, Rect outDestinationBounds, @Nullable Rect outSourceHintRect) { 964 mPipDisplayLayoutState.rotateTo(endRotation); 965 966 final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); 967 outDestinationBounds.set(mPipBoundsAlgorithm.getEntryDestinationBounds()); 968 // Transform the destination bounds to current display coordinates. 969 rotateBounds(outDestinationBounds, displayBounds, endRotation, startRotation); 970 // When entering PiP (from button navigation mode), adjust the source rect hint by 971 // display cutout if applicable. 972 if (outSourceHintRect != null && taskInfo.displayCutoutInsets != null) { 973 if (rotationDelta == Surface.ROTATION_270) { 974 outSourceHintRect.offset(taskInfo.displayCutoutInsets.left, 975 taskInfo.displayCutoutInsets.top); 976 } 977 } 978 } 979 handleSwipePipToHomeTransition( @onNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect, @NonNull Rect destinationBounds, @NonNull ActivityManager.RunningTaskInfo pipTaskInfo)980 private void handleSwipePipToHomeTransition( 981 @NonNull SurfaceControl.Transaction startTransaction, 982 @NonNull SurfaceControl.Transaction finishTransaction, 983 @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect, 984 @NonNull Rect destinationBounds, 985 @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) { 986 if (mInFixedRotation) { 987 // If rotation changes when returning to home, the transition should contain both the 988 // entering PiP and the display change (PipController#startSwipePipToHome has updated 989 // the display layout to new rotation). So it is not expected to see fixed rotation. 990 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, 991 "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation); 992 } 993 final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; 994 if (swipePipToHomeOverlay != null) { 995 // Launcher fade in the overlay on top of the fullscreen Task. It is possible we 996 // reparent the PIP activity to a new PIP task (in case there are other activities 997 // in the original Task, in other words multi-activity apps), so we should also reparent 998 // the overlay to the final PIP task. 999 startTransaction.reparent(swipePipToHomeOverlay, leash) 1000 .setLayer(swipePipToHomeOverlay, Integer.MAX_VALUE); 1001 mPipOrganizer.mSwipePipToHomeOverlay = null; 1002 } 1003 1004 final Rect sourceBounds = pipTaskInfo.configuration.windowConfiguration.getBounds(); 1005 final PipAnimationController.PipTransitionAnimator animator = 1006 mPipAnimationController.getAnimator(pipTaskInfo, leash, sourceBounds, sourceBounds, 1007 destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 1008 0 /* startingAngle */, 0 /* rotationDelta */) 1009 .setPipTransactionHandler(mTransactionConsumer) 1010 .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP); 1011 // The start state is the end state for swipe-auto-pip. 1012 startTransaction.merge(finishTransaction); 1013 animator.applySurfaceControlTransaction(leash, startTransaction, 1014 PipAnimationController.FRACTION_END); 1015 startTransaction.apply(); 1016 1017 mPipBoundsState.setBounds(destinationBounds); 1018 final SurfaceControl.Transaction tx = new SurfaceControl.Transaction(); 1019 onFinishResize(pipTaskInfo, destinationBounds, TRANSITION_DIRECTION_TO_PIP, tx); 1020 sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP); 1021 if (swipePipToHomeOverlay != null) { 1022 mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay, 1023 null /* callback */, false /* withStartDelay */); 1024 } 1025 mPipTransitionState.setInSwipePipToHomeTransition(false); 1026 } 1027 startExitToSplitAnimation(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback, @NonNull TaskInfo taskInfo)1028 private void startExitToSplitAnimation(@NonNull TransitionInfo info, 1029 @NonNull SurfaceControl.Transaction startTransaction, 1030 @NonNull SurfaceControl.Transaction finishTransaction, 1031 @NonNull Transitions.TransitionFinishCallback finishCallback, 1032 @NonNull TaskInfo taskInfo) { 1033 for (int i = info.getChanges().size() - 1; i >= 0; i--) { 1034 final TransitionInfo.Change change = info.getChanges().get(i); 1035 final int mode = change.getMode(); 1036 1037 if (mode == TRANSIT_CHANGE && change.getParent() != null) { 1038 // TODO: perform resize/expand animation for reparented child task. 1039 continue; 1040 } 1041 1042 if (TransitionUtil.isOpeningType(mode) && change.getParent() == null) { 1043 final SurfaceControl leash = change.getLeash(); 1044 final Rect endBounds = change.getEndAbsBounds(); 1045 startTransaction 1046 .show(leash) 1047 .setAlpha(leash, 1f) 1048 .setPosition(leash, endBounds.left, endBounds.top) 1049 .setWindowCrop(leash, endBounds.width(), endBounds.height()); 1050 } 1051 } 1052 mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction); 1053 startTransaction.apply(); 1054 1055 mPipOrganizer.onExitPipFinished(taskInfo); 1056 finishCallback.onTransitionFinished(null); 1057 } 1058 resetPrevPip(@onNull TransitionInfo.Change prevPipTaskChange, @NonNull SurfaceControl.Transaction startTransaction)1059 private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange, 1060 @NonNull SurfaceControl.Transaction startTransaction) { 1061 final SurfaceControl leash = prevPipTaskChange.getLeash(); 1062 final Rect bounds = prevPipTaskChange.getEndAbsBounds(); 1063 final Point offset = prevPipTaskChange.getEndRelOffset(); 1064 bounds.offset(-offset.x, -offset.y); 1065 1066 startTransaction.setWindowCrop(leash, null); 1067 startTransaction.setMatrix(leash, 1, 0, 0, 1); 1068 startTransaction.setCornerRadius(leash, 0); 1069 startTransaction.setPosition(leash, bounds.left, bounds.top); 1070 1071 if (mHasFadeOut && prevPipTaskChange.getTaskInfo().isVisible()) { 1072 if (mPipAnimationController.getCurrentAnimator() != null) { 1073 mPipAnimationController.getCurrentAnimator().cancel(); 1074 } 1075 startTransaction.setAlpha(leash, 1); 1076 } 1077 1078 mHasFadeOut = false; 1079 mCurrentPipTaskToken = null; 1080 1081 // clean-up the state in PipTaskOrganizer if the PipTaskOrganizer#onTaskAppeared() hasn't 1082 // been called yet with its leash reference now pointing to a new SurfaceControl not 1083 // matching the leash of the pip we are removing. 1084 if (mPipOrganizer.getSurfaceControl() == leash) { 1085 mPipOrganizer.onExitPipFinished(prevPipTaskChange.getTaskInfo()); 1086 } 1087 } 1088 1089 @Override syncPipSurfaceState(@onNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)1090 public boolean syncPipSurfaceState(@NonNull TransitionInfo info, 1091 @NonNull SurfaceControl.Transaction startTransaction, 1092 @NonNull SurfaceControl.Transaction finishTransaction) { 1093 final TransitionInfo.Change pipChange = findCurrentPipTaskChange(info); 1094 if (pipChange == null) return false; 1095 updatePipForUnhandledTransition(pipChange, startTransaction, finishTransaction); 1096 return true; 1097 } 1098 updatePipForUnhandledTransition(@onNull TransitionInfo.Change pipChange, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction)1099 private void updatePipForUnhandledTransition(@NonNull TransitionInfo.Change pipChange, 1100 @NonNull SurfaceControl.Transaction startTransaction, 1101 @NonNull SurfaceControl.Transaction finishTransaction) { 1102 // When the PIP window is visible and being a part of the transition, such as display 1103 // rotation, we need to update its bounds and rounded corner. 1104 final SurfaceControl leash = pipChange.getLeash(); 1105 final Rect destBounds = mPipOrganizer.getCurrentOrAnimatingBounds(); 1106 final boolean isInPip = mPipTransitionState.isInPip(); 1107 mSurfaceTransactionHelper 1108 .crop(startTransaction, leash, destBounds) 1109 .round(startTransaction, leash, isInPip) 1110 .shadow(startTransaction, leash, isInPip); 1111 mSurfaceTransactionHelper 1112 .crop(finishTransaction, leash, destBounds) 1113 .round(finishTransaction, leash, isInPip) 1114 .shadow(finishTransaction, leash, isInPip); 1115 // Make sure the PiP keeps invisible if it was faded out. If it needs to fade in, that will 1116 // be handled by onFixedRotationFinished(). 1117 if (isInPip && mHasFadeOut) { 1118 startTransaction.setAlpha(leash, 0f); 1119 finishTransaction.setAlpha(leash, 0f); 1120 } 1121 } 1122 1123 /** Hides and shows the existing PIP during fixed rotation transition of other activities. */ fadeExistingPip(boolean show)1124 private void fadeExistingPip(boolean show) { 1125 final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); 1126 final TaskInfo taskInfo = mPipOrganizer.getTaskInfo(); 1127 if (leash == null || !leash.isValid() || taskInfo == null) { 1128 ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, 1129 "%s: Invalid leash on fadeExistingPip: %s", TAG, leash); 1130 return; 1131 } 1132 final float alphaStart = show ? 0 : 1; 1133 final float alphaEnd = show ? 1 : 0; 1134 final PipAnimationController.PipTransactionHandler transactionHandler = 1135 new PipAnimationController.PipTransactionHandler() { 1136 @Override 1137 public boolean handlePipTransaction(SurfaceControl leash, 1138 SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) { 1139 if (alpha == 0) { 1140 if (show) { 1141 tx.setPosition(leash, destinationBounds.left, destinationBounds.top); 1142 } else { 1143 // Put PiP out of the display so it won't block touch when it is hidden. 1144 final Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); 1145 final int max = Math.max(displayBounds.width(), displayBounds.height()); 1146 tx.setPosition(leash, max, max); 1147 } 1148 } 1149 return false; 1150 } 1151 }; 1152 mPipAnimationController 1153 .getAnimator(taskInfo, leash, mPipBoundsState.getBounds(), alphaStart, alphaEnd) 1154 .setTransitionDirection(TRANSITION_DIRECTION_SAME) 1155 .setPipTransactionHandler(transactionHandler) 1156 .setDuration(mEnterExitAnimationDuration) 1157 .start(); 1158 mHasFadeOut = !show; 1159 } 1160 finishResizeForMenu(Rect destinationBounds)1161 private void finishResizeForMenu(Rect destinationBounds) { 1162 mPipMenuController.movePipMenu(null, null, destinationBounds, 1163 PipMenuController.ALPHA_NO_CHANGE); 1164 mPipMenuController.updateMenuBounds(destinationBounds); 1165 } 1166 1167 @Override dump(PrintWriter pw, String prefix)1168 public void dump(PrintWriter pw, String prefix) { 1169 final String innerPrefix = prefix + " "; 1170 pw.println(prefix + TAG); 1171 pw.println(innerPrefix + "mCurrentPipTaskToken=" + mCurrentPipTaskToken); 1172 pw.println(innerPrefix + "mFinishCallback=" + mFinishCallback); 1173 } 1174 } 1175