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