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.util.RotationUtils.rotateBounds; 20 import static android.view.Surface.ROTATION_270; 21 import static android.view.Surface.ROTATION_90; 22 23 import android.animation.AnimationHandler; 24 import android.animation.Animator; 25 import android.animation.RectEvaluator; 26 import android.animation.ValueAnimator; 27 import android.annotation.IntDef; 28 import android.annotation.NonNull; 29 import android.app.TaskInfo; 30 import android.content.Context; 31 import android.content.pm.ActivityInfo; 32 import android.graphics.Rect; 33 import android.view.Surface; 34 import android.view.SurfaceControl; 35 import android.window.TaskSnapshot; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.internal.graphics.SfVsyncFrameCallbackProvider; 39 import com.android.launcher3.icons.IconProvider; 40 import com.android.wm.shell.animation.Interpolators; 41 import com.android.wm.shell.transition.Transitions; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.Objects; 46 47 /** 48 * Controller class of PiP animations (both from and to PiP mode). 49 */ 50 public class PipAnimationController { 51 static final float FRACTION_START = 0f; 52 private static final float FRACTION_END = 1f; 53 54 public static final int ANIM_TYPE_BOUNDS = 0; 55 public static final int ANIM_TYPE_ALPHA = 1; 56 57 @IntDef(prefix = { "ANIM_TYPE_" }, value = { 58 ANIM_TYPE_BOUNDS, 59 ANIM_TYPE_ALPHA 60 }) 61 @Retention(RetentionPolicy.SOURCE) 62 public @interface AnimationType {} 63 64 public static final int TRANSITION_DIRECTION_NONE = 0; 65 public static final int TRANSITION_DIRECTION_SAME = 1; 66 public static final int TRANSITION_DIRECTION_TO_PIP = 2; 67 public static final int TRANSITION_DIRECTION_LEAVE_PIP = 3; 68 public static final int TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN = 4; 69 public static final int TRANSITION_DIRECTION_REMOVE_STACK = 5; 70 public static final int TRANSITION_DIRECTION_SNAP_AFTER_RESIZE = 6; 71 public static final int TRANSITION_DIRECTION_USER_RESIZE = 7; 72 public static final int TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND = 8; 73 74 @IntDef(prefix = { "TRANSITION_DIRECTION_" }, value = { 75 TRANSITION_DIRECTION_NONE, 76 TRANSITION_DIRECTION_SAME, 77 TRANSITION_DIRECTION_TO_PIP, 78 TRANSITION_DIRECTION_LEAVE_PIP, 79 TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN, 80 TRANSITION_DIRECTION_REMOVE_STACK, 81 TRANSITION_DIRECTION_SNAP_AFTER_RESIZE, 82 TRANSITION_DIRECTION_USER_RESIZE, 83 TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND 84 }) 85 @Retention(RetentionPolicy.SOURCE) 86 public @interface TransitionDirection {} 87 isInPipDirection(@ransitionDirection int direction)88 public static boolean isInPipDirection(@TransitionDirection int direction) { 89 return direction == TRANSITION_DIRECTION_TO_PIP; 90 } 91 isOutPipDirection(@ransitionDirection int direction)92 public static boolean isOutPipDirection(@TransitionDirection int direction) { 93 return direction == TRANSITION_DIRECTION_LEAVE_PIP 94 || direction == TRANSITION_DIRECTION_LEAVE_PIP_TO_SPLIT_SCREEN; 95 } 96 97 /** Whether the given direction represents removing PIP. */ isRemovePipDirection(@ransitionDirection int direction)98 public static boolean isRemovePipDirection(@TransitionDirection int direction) { 99 return direction == TRANSITION_DIRECTION_REMOVE_STACK; 100 } 101 102 private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; 103 104 private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = 105 ThreadLocal.withInitial(() -> { 106 AnimationHandler handler = new AnimationHandler(); 107 handler.setProvider(new SfVsyncFrameCallbackProvider()); 108 return handler; 109 }); 110 111 private PipTransitionAnimator mCurrentAnimator; 112 PipAnimationController(PipSurfaceTransactionHelper helper)113 public PipAnimationController(PipSurfaceTransactionHelper helper) { 114 mSurfaceTransactionHelper = helper; 115 } 116 117 @SuppressWarnings("unchecked") 118 @VisibleForTesting getAnimator(TaskInfo taskInfo, SurfaceControl leash, Rect destinationBounds, float alphaStart, float alphaEnd)119 public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash, 120 Rect destinationBounds, float alphaStart, float alphaEnd) { 121 if (mCurrentAnimator == null) { 122 mCurrentAnimator = setupPipTransitionAnimator( 123 PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart, 124 alphaEnd)); 125 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA 126 && Objects.equals(destinationBounds, mCurrentAnimator.getDestinationBounds()) 127 && mCurrentAnimator.isRunning()) { 128 mCurrentAnimator.updateEndValue(alphaEnd); 129 } else { 130 mCurrentAnimator.cancel(); 131 mCurrentAnimator = setupPipTransitionAnimator( 132 PipTransitionAnimator.ofAlpha(taskInfo, leash, destinationBounds, alphaStart, 133 alphaEnd)); 134 } 135 return mCurrentAnimator; 136 } 137 138 @SuppressWarnings("unchecked") 139 /** 140 * Construct and return an animator that animates from the {@param startBounds} to the 141 * {@param endBounds} with the given {@param direction}. If {@param direction} is type 142 * {@link ANIM_TYPE_BOUNDS}, then {@param sourceHintRect} will be used to animate 143 * in a better, more smooth manner. If the original bound was rotated and a reset needs to 144 * happen, pass in {@param startingAngle}. 145 * 146 * In the case where one wants to start animation during an intermediate animation (for example, 147 * if the user is currently doing a pinch-resize, and upon letting go now PiP needs to animate 148 * to the correct snap fraction region), then provide the base bounds, which is current PiP 149 * leash bounds before transformation/any animation. This is so when we try to construct 150 * the different transformation matrices for the animation, we are constructing this based off 151 * the PiP original bounds, rather than the {@param startBounds}, which is post-transformed. 152 * 153 * If non-zero {@param rotationDelta} is given, it means that the display will be rotated by 154 * leaving PiP to fullscreen, and the {@param endBounds} is the fullscreen bounds before the 155 * rotation change. 156 */ 157 @VisibleForTesting getAnimator(TaskInfo taskInfo, SurfaceControl leash, Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, float startingAngle, @Surface.Rotation int rotationDelta)158 public PipTransitionAnimator getAnimator(TaskInfo taskInfo, SurfaceControl leash, 159 Rect baseBounds, Rect startBounds, Rect endBounds, Rect sourceHintRect, 160 @PipAnimationController.TransitionDirection int direction, float startingAngle, 161 @Surface.Rotation int rotationDelta) { 162 if (mCurrentAnimator == null) { 163 mCurrentAnimator = setupPipTransitionAnimator( 164 PipTransitionAnimator.ofBounds(taskInfo, leash, startBounds, startBounds, 165 endBounds, sourceHintRect, direction, 0 /* startingAngle */, 166 rotationDelta)); 167 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_ALPHA 168 && mCurrentAnimator.isRunning()) { 169 // If we are still animating the fade into pip, then just move the surface and ensure 170 // we update with the new destination bounds, but don't interrupt the existing animation 171 // with a new bounds 172 mCurrentAnimator.setDestinationBounds(endBounds); 173 } else if (mCurrentAnimator.getAnimationType() == ANIM_TYPE_BOUNDS 174 && mCurrentAnimator.isRunning()) { 175 mCurrentAnimator.setDestinationBounds(endBounds); 176 // construct new Rect instances in case they are recycled 177 mCurrentAnimator.updateEndValue(new Rect(endBounds)); 178 } else { 179 mCurrentAnimator.cancel(); 180 mCurrentAnimator = setupPipTransitionAnimator( 181 PipTransitionAnimator.ofBounds(taskInfo, leash, baseBounds, startBounds, 182 endBounds, sourceHintRect, direction, startingAngle, rotationDelta)); 183 } 184 return mCurrentAnimator; 185 } 186 getCurrentAnimator()187 public PipTransitionAnimator getCurrentAnimator() { 188 return mCurrentAnimator; 189 } 190 setupPipTransitionAnimator(PipTransitionAnimator animator)191 private PipTransitionAnimator setupPipTransitionAnimator(PipTransitionAnimator animator) { 192 animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); 193 animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 194 animator.setFloatValues(FRACTION_START, FRACTION_END); 195 animator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); 196 return animator; 197 } 198 199 /** 200 * Returns true if the PiP window is currently being animated. 201 */ isAnimating()202 public boolean isAnimating() { 203 PipAnimationController.PipTransitionAnimator animator = getCurrentAnimator(); 204 if (animator != null && animator.isRunning()) { 205 return true; 206 } 207 return false; 208 } 209 210 /** 211 * Quietly cancel the animator by removing the listeners first. 212 */ quietCancel(@onNull ValueAnimator animator)213 static void quietCancel(@NonNull ValueAnimator animator) { 214 animator.removeAllUpdateListeners(); 215 animator.removeAllListeners(); 216 animator.cancel(); 217 } 218 219 /** 220 * Additional callback interface for PiP animation 221 */ 222 public static class PipAnimationCallback { 223 /** 224 * Called when PiP animation is started. 225 */ onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator)226 public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {} 227 228 /** 229 * Called when PiP animation is ended. 230 */ onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, PipTransitionAnimator animator)231 public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, 232 PipTransitionAnimator animator) {} 233 234 /** 235 * Called when PiP animation is cancelled. 236 */ onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator)237 public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {} 238 } 239 240 /** 241 * A handler class that could register itself to apply the transaction instead of the 242 * animation controller doing it. For example, the menu controller can be one such handler. 243 */ 244 public static class PipTransactionHandler { 245 246 /** 247 * Called when the animation controller is about to apply a transaction. Allow a registered 248 * handler to apply the transaction instead. 249 * 250 * @return true if handled by the handler, false otherwise. 251 */ handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds)252 public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, 253 Rect destinationBounds) { 254 return false; 255 } 256 } 257 258 /** 259 * Animator for PiP transition animation which supports both alpha and bounds animation. 260 * @param <T> Type of property to animate, either alpha (float) or bounds (Rect) 261 */ 262 public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements 263 ValueAnimator.AnimatorUpdateListener, 264 ValueAnimator.AnimatorListener { 265 private final TaskInfo mTaskInfo; 266 private final SurfaceControl mLeash; 267 private final @AnimationType int mAnimationType; 268 private final Rect mDestinationBounds = new Rect(); 269 270 private T mBaseValue; 271 protected T mCurrentValue; 272 protected T mStartValue; 273 private T mEndValue; 274 private PipAnimationCallback mPipAnimationCallback; 275 private PipTransactionHandler mPipTransactionHandler; 276 private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory 277 mSurfaceControlTransactionFactory; 278 private PipSurfaceTransactionHelper mSurfaceTransactionHelper; 279 private @TransitionDirection int mTransitionDirection; 280 protected PipContentOverlay mContentOverlay; 281 PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue, T endValue)282 private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, 283 @AnimationType int animationType, 284 Rect destinationBounds, T baseValue, T startValue, T endValue) { 285 mTaskInfo = taskInfo; 286 mLeash = leash; 287 mAnimationType = animationType; 288 mDestinationBounds.set(destinationBounds); 289 mBaseValue = baseValue; 290 mStartValue = startValue; 291 mEndValue = endValue; 292 addListener(this); 293 addUpdateListener(this); 294 mSurfaceControlTransactionFactory = 295 new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); 296 mTransitionDirection = TRANSITION_DIRECTION_NONE; 297 } 298 299 @Override onAnimationStart(Animator animation)300 public void onAnimationStart(Animator animation) { 301 mCurrentValue = mStartValue; 302 onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction()); 303 if (mPipAnimationCallback != null) { 304 mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this); 305 } 306 } 307 308 @Override onAnimationUpdate(ValueAnimator animation)309 public void onAnimationUpdate(ValueAnimator animation) { 310 applySurfaceControlTransaction(mLeash, 311 mSurfaceControlTransactionFactory.getTransaction(), 312 animation.getAnimatedFraction()); 313 } 314 315 @Override onAnimationEnd(Animator animation)316 public void onAnimationEnd(Animator animation) { 317 mCurrentValue = mEndValue; 318 final SurfaceControl.Transaction tx = 319 mSurfaceControlTransactionFactory.getTransaction(); 320 onEndTransaction(mLeash, tx, mTransitionDirection); 321 if (mPipAnimationCallback != null) { 322 mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this); 323 } 324 mTransitionDirection = TRANSITION_DIRECTION_NONE; 325 } 326 327 @Override onAnimationCancel(Animator animation)328 public void onAnimationCancel(Animator animation) { 329 if (mPipAnimationCallback != null) { 330 mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this); 331 } 332 mTransitionDirection = TRANSITION_DIRECTION_NONE; 333 } 334 onAnimationRepeat(Animator animation)335 @Override public void onAnimationRepeat(Animator animation) {} 336 337 @VisibleForTesting getAnimationType()338 @AnimationType public int getAnimationType() { 339 return mAnimationType; 340 } 341 342 @VisibleForTesting setPipAnimationCallback(PipAnimationCallback callback)343 public PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) { 344 mPipAnimationCallback = callback; 345 return this; 346 } 347 setPipTransactionHandler(PipTransactionHandler handler)348 PipTransitionAnimator<T> setPipTransactionHandler(PipTransactionHandler handler) { 349 mPipTransactionHandler = handler; 350 return this; 351 } 352 handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds)353 boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, 354 Rect destinationBounds) { 355 if (mPipTransactionHandler != null) { 356 return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds); 357 } 358 return false; 359 } 360 getContentOverlayLeash()361 SurfaceControl getContentOverlayLeash() { 362 return mContentOverlay == null ? null : mContentOverlay.mLeash; 363 } 364 setColorContentOverlay(Context context)365 void setColorContentOverlay(Context context) { 366 reattachContentOverlay(new PipContentOverlay.PipColorOverlay(context)); 367 } 368 setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint)369 void setSnapshotContentOverlay(TaskSnapshot snapshot, Rect sourceRectHint) { 370 reattachContentOverlay( 371 new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint)); 372 } 373 setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo, int appIconSizePx)374 void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo, 375 int appIconSizePx) { 376 reattachContentOverlay( 377 new PipContentOverlay.PipAppIconOverlay(context, bounds, 378 new IconProvider(context).getIcon(activityInfo), appIconSizePx)); 379 } 380 reattachContentOverlay(PipContentOverlay overlay)381 private void reattachContentOverlay(PipContentOverlay overlay) { 382 final SurfaceControl.Transaction tx = 383 mSurfaceControlTransactionFactory.getTransaction(); 384 if (mContentOverlay != null) { 385 mContentOverlay.detach(tx); 386 } 387 mContentOverlay = overlay; 388 mContentOverlay.attach(tx, mLeash); 389 } 390 391 /** 392 * Clears the {@link #mContentOverlay}, this should be done after the content overlay is 393 * faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay} 394 */ clearContentOverlay()395 void clearContentOverlay() { 396 mContentOverlay = null; 397 } 398 399 @VisibleForTesting getTransitionDirection()400 @TransitionDirection public int getTransitionDirection() { 401 return mTransitionDirection; 402 } 403 404 @VisibleForTesting setTransitionDirection(@ransitionDirection int direction)405 public PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) { 406 if (direction != TRANSITION_DIRECTION_SAME) { 407 mTransitionDirection = direction; 408 } 409 return this; 410 } 411 getStartValue()412 T getStartValue() { 413 return mStartValue; 414 } 415 getBaseValue()416 T getBaseValue() { 417 return mBaseValue; 418 } 419 420 @VisibleForTesting getEndValue()421 public T getEndValue() { 422 return mEndValue; 423 } 424 getDestinationBounds()425 Rect getDestinationBounds() { 426 return mDestinationBounds; 427 } 428 setDestinationBounds(Rect destinationBounds)429 void setDestinationBounds(Rect destinationBounds) { 430 mDestinationBounds.set(destinationBounds); 431 if (mAnimationType == ANIM_TYPE_ALPHA) { 432 onStartTransaction(mLeash, mSurfaceControlTransactionFactory.getTransaction()); 433 } 434 } 435 setCurrentValue(T value)436 void setCurrentValue(T value) { 437 mCurrentValue = value; 438 } 439 shouldApplyCornerRadius()440 boolean shouldApplyCornerRadius() { 441 return !isOutPipDirection(mTransitionDirection); 442 } 443 shouldApplyShadowRadius()444 boolean shouldApplyShadowRadius() { 445 return !isOutPipDirection(mTransitionDirection) 446 && !isRemovePipDirection(mTransitionDirection); 447 } 448 inScaleTransition()449 boolean inScaleTransition() { 450 if (mAnimationType != ANIM_TYPE_BOUNDS) return false; 451 final int direction = getTransitionDirection(); 452 return !isInPipDirection(direction) && !isOutPipDirection(direction); 453 } 454 455 /** 456 * Updates the {@link #mEndValue}. 457 * 458 * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation. 459 * This is typically used when we receive a shelf height adjustment during the bounds 460 * animation. In which case we can update the end bounds and keep the existing animation 461 * running instead of cancelling it. 462 */ updateEndValue(T endValue)463 public void updateEndValue(T endValue) { 464 mEndValue = endValue; 465 } 466 467 @VisibleForTesting setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory)468 public void setSurfaceControlTransactionFactory( 469 PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { 470 mSurfaceControlTransactionFactory = factory; 471 } 472 getSurfaceTransactionHelper()473 PipSurfaceTransactionHelper getSurfaceTransactionHelper() { 474 return mSurfaceTransactionHelper; 475 } 476 setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper)477 void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) { 478 mSurfaceTransactionHelper = helper; 479 } 480 onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)481 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {} 482 onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, @TransitionDirection int transitionDirection)483 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, 484 @TransitionDirection int transitionDirection) {} 485 applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)486 abstract void applySurfaceControlTransaction(SurfaceControl leash, 487 SurfaceControl.Transaction tx, float fraction); 488 ofAlpha(TaskInfo taskInfo, SurfaceControl leash, Rect destinationBounds, float startValue, float endValue)489 static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash, 490 Rect destinationBounds, float startValue, float endValue) { 491 return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA, 492 destinationBounds, startValue, startValue, endValue) { 493 @Override 494 void applySurfaceControlTransaction(SurfaceControl leash, 495 SurfaceControl.Transaction tx, float fraction) { 496 final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction; 497 setCurrentValue(alpha); 498 getSurfaceTransactionHelper().alpha(tx, leash, alpha) 499 .round(tx, leash, shouldApplyCornerRadius()) 500 .shadow(tx, leash, shouldApplyShadowRadius()); 501 tx.apply(); 502 } 503 504 @Override 505 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 506 if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) { 507 // while removing the pip stack, no extra work needs to be done here. 508 return; 509 } 510 getSurfaceTransactionHelper() 511 .resetScale(tx, leash, getDestinationBounds()) 512 .crop(tx, leash, getDestinationBounds()) 513 .round(tx, leash, shouldApplyCornerRadius()) 514 .shadow(tx, leash, shouldApplyShadowRadius()); 515 tx.show(leash); 516 tx.apply(); 517 } 518 519 @Override 520 public void updateEndValue(Float endValue) { 521 super.updateEndValue(endValue); 522 mStartValue = mCurrentValue; 523 } 524 }; 525 } 526 ofBounds(TaskInfo taskInfo, SurfaceControl leash, Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, float startingAngle, @Surface.Rotation int rotationDelta)527 static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash, 528 Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, 529 @PipAnimationController.TransitionDirection int direction, float startingAngle, 530 @Surface.Rotation int rotationDelta) { 531 final boolean isOutPipDirection = isOutPipDirection(direction); 532 final boolean isInPipDirection = isInPipDirection(direction); 533 // Just for simplicity we'll interpolate between the source rect hint insets and empty 534 // insets to calculate the window crop 535 final Rect initialSourceValue; 536 if (isOutPipDirection) { 537 initialSourceValue = new Rect(endValue); 538 } else { 539 initialSourceValue = new Rect(baseValue); 540 } 541 542 final Rect rotatedEndRect; 543 final Rect lastEndRect; 544 final Rect initialContainerRect; 545 if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) { 546 lastEndRect = new Rect(endValue); 547 rotatedEndRect = new Rect(endValue); 548 // Rotate the end bounds according to the rotation delta because the display will 549 // be rotated to the same orientation. 550 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta); 551 // Use the rect that has the same orientation as the hint rect. 552 initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue; 553 } else { 554 rotatedEndRect = lastEndRect = null; 555 initialContainerRect = initialSourceValue; 556 } 557 558 final Rect sourceHintRectInsets; 559 if (sourceHintRect == null) { 560 sourceHintRectInsets = null; 561 } else { 562 sourceHintRectInsets = new Rect(sourceHintRect.left - initialContainerRect.left, 563 sourceHintRect.top - initialContainerRect.top, 564 initialContainerRect.right - sourceHintRect.right, 565 initialContainerRect.bottom - sourceHintRect.bottom); 566 } 567 final Rect zeroInsets = new Rect(0, 0, 0, 0); 568 569 // construct new Rect instances in case they are recycled 570 return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS, 571 endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue)) { 572 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); 573 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); 574 575 @Override 576 void applySurfaceControlTransaction(SurfaceControl leash, 577 SurfaceControl.Transaction tx, float fraction) { 578 final Rect base = getBaseValue(); 579 final Rect start = getStartValue(); 580 final Rect end = getEndValue(); 581 Rect bounds = mRectEvaluator.evaluate(fraction, start, end); 582 if (mContentOverlay != null) { 583 mContentOverlay.onAnimationUpdate(tx, bounds, fraction); 584 } 585 if (rotatedEndRect != null) { 586 // Animate the bounds in a different orientation. It only happens when 587 // switching between PiP and fullscreen. 588 applyRotation(tx, leash, fraction, start, end); 589 return; 590 } 591 float angle = (1.0f - fraction) * startingAngle; 592 setCurrentValue(bounds); 593 if (inScaleTransition() || sourceHintRect == null) { 594 if (isOutPipDirection) { 595 getSurfaceTransactionHelper().crop(tx, leash, end) 596 .scale(tx, leash, end, bounds); 597 } else { 598 getSurfaceTransactionHelper().crop(tx, leash, base) 599 .scale(tx, leash, base, bounds, angle) 600 .round(tx, leash, base, bounds) 601 .shadow(tx, leash, shouldApplyShadowRadius()); 602 } 603 } else { 604 final Rect insets = computeInsets(fraction); 605 getSurfaceTransactionHelper().scaleAndCrop(tx, leash, 606 sourceHintRect, initialSourceValue, bounds, insets, 607 isInPipDirection, fraction); 608 if (shouldApplyCornerRadius()) { 609 final Rect sourceBounds = new Rect(initialContainerRect); 610 sourceBounds.inset(insets); 611 getSurfaceTransactionHelper() 612 .round(tx, leash, sourceBounds, bounds) 613 .shadow(tx, leash, shouldApplyShadowRadius()); 614 } 615 } 616 if (!handlePipTransaction(leash, tx, bounds)) { 617 tx.apply(); 618 } 619 } 620 621 private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash, 622 float fraction, Rect start, Rect end) { 623 if (!end.equals(lastEndRect)) { 624 // If the end bounds are changed during animating (e.g. shelf height), the 625 // rotated end bounds also need to be updated. 626 rotatedEndRect.set(endValue); 627 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta); 628 lastEndRect.set(end); 629 } 630 final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect); 631 setCurrentValue(bounds); 632 final Rect insets = computeInsets(fraction); 633 final float degree, x, y; 634 if (Transitions.SHELL_TRANSITIONS_ROTATION) { 635 if (rotationDelta == ROTATION_90) { 636 degree = 90 * (1 - fraction); 637 x = fraction * (end.left - start.left) 638 + start.left + start.width() * (1 - fraction); 639 y = fraction * (end.top - start.top) + start.top; 640 } else { 641 degree = -90 * (1 - fraction); 642 x = fraction * (end.left - start.left) + start.left; 643 y = fraction * (end.top - start.top) 644 + start.top + start.height() * (1 - fraction); 645 } 646 } else { 647 if (rotationDelta == ROTATION_90) { 648 degree = 90 * fraction; 649 x = fraction * (end.right - start.left) + start.left; 650 y = fraction * (end.top - start.top) + start.top; 651 } else { 652 degree = -90 * fraction; 653 x = fraction * (end.left - start.left) + start.left; 654 y = fraction * (end.bottom - start.top) + start.top; 655 } 656 } 657 final Rect sourceBounds = new Rect(initialContainerRect); 658 sourceBounds.inset(insets); 659 getSurfaceTransactionHelper() 660 .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds, 661 insets, degree, x, y, isOutPipDirection, 662 rotationDelta == ROTATION_270 /* clockwise */); 663 if (shouldApplyCornerRadius()) { 664 getSurfaceTransactionHelper() 665 .round(tx, leash, sourceBounds, bounds) 666 .shadow(tx, leash, shouldApplyShadowRadius()); 667 } 668 tx.apply(); 669 } 670 671 private Rect computeInsets(float fraction) { 672 if (sourceHintRectInsets == null) { 673 return zeroInsets; 674 } 675 final Rect startRect = isOutPipDirection ? sourceHintRectInsets : zeroInsets; 676 final Rect endRect = isOutPipDirection ? zeroInsets : sourceHintRectInsets; 677 return mInsetsEvaluator.evaluate(fraction, startRect, endRect); 678 } 679 680 @Override 681 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 682 getSurfaceTransactionHelper() 683 .alpha(tx, leash, 1f) 684 .round(tx, leash, shouldApplyCornerRadius()) 685 .shadow(tx, leash, shouldApplyShadowRadius()); 686 // TODO(b/178632364): this is a work around for the black background when 687 // entering PiP in button navigation mode. 688 if (isInPipDirection(direction)) { 689 tx.setWindowCrop(leash, getStartValue()); 690 } 691 tx.show(leash); 692 tx.apply(); 693 } 694 695 @Override 696 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, 697 int transitionDirection) { 698 // NOTE: intentionally does not apply the transaction here. 699 // this end transaction should get executed synchronously with the final 700 // WindowContainerTransaction in task organizer 701 final Rect destBounds = getDestinationBounds(); 702 getSurfaceTransactionHelper().resetScale(tx, leash, destBounds); 703 if (isOutPipDirection(transitionDirection)) { 704 // Exit pip, clear scale, position and crop. 705 tx.setMatrix(leash, 1, 0, 0, 1); 706 tx.setPosition(leash, 0, 0); 707 tx.setWindowCrop(leash, 0, 0); 708 } else { 709 getSurfaceTransactionHelper().crop(tx, leash, destBounds); 710 } 711 if (mContentOverlay != null) { 712 mContentOverlay.onAnimationEnd(tx, destBounds); 713 } 714 } 715 716 @Override 717 public void updateEndValue(Rect endValue) { 718 super.updateEndValue(endValue); 719 if (mStartValue != null && mCurrentValue != null) { 720 mStartValue.set(mCurrentValue); 721 } 722 } 723 }; 724 } 725 } 726 } 727