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 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 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 * Additional callback interface for PiP animation 201 */ 202 public static class PipAnimationCallback { 203 /** 204 * Called when PiP animation is started. 205 */ onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator)206 public void onPipAnimationStart(TaskInfo taskInfo, PipTransitionAnimator animator) {} 207 208 /** 209 * Called when PiP animation is ended. 210 */ onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, PipTransitionAnimator animator)211 public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, 212 PipTransitionAnimator animator) {} 213 214 /** 215 * Called when PiP animation is cancelled. 216 */ onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator)217 public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {} 218 } 219 220 /** 221 * A handler class that could register itself to apply the transaction instead of the 222 * animation controller doing it. For example, the menu controller can be one such handler. 223 */ 224 public static class PipTransactionHandler { 225 226 /** 227 * Called when the animation controller is about to apply a transaction. Allow a registered 228 * handler to apply the transaction instead. 229 * 230 * @return true if handled by the handler, false otherwise. 231 */ handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds)232 public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, 233 Rect destinationBounds) { 234 return false; 235 } 236 } 237 238 /** 239 * Animator for PiP transition animation which supports both alpha and bounds animation. 240 * @param <T> Type of property to animate, either alpha (float) or bounds (Rect) 241 */ 242 public abstract static class PipTransitionAnimator<T> extends ValueAnimator implements 243 ValueAnimator.AnimatorUpdateListener, 244 ValueAnimator.AnimatorListener { 245 private final TaskInfo mTaskInfo; 246 private final SurfaceControl mLeash; 247 private final @AnimationType int mAnimationType; 248 private final Rect mDestinationBounds = new Rect(); 249 250 private T mBaseValue; 251 protected T mCurrentValue; 252 protected T mStartValue; 253 private T mEndValue; 254 private float mStartingAngle; 255 private PipAnimationCallback mPipAnimationCallback; 256 private PipTransactionHandler mPipTransactionHandler; 257 private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory 258 mSurfaceControlTransactionFactory; 259 private PipSurfaceTransactionHelper mSurfaceTransactionHelper; 260 private @TransitionDirection int mTransitionDirection; 261 protected SurfaceControl mContentOverlay; 262 PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue, T endValue, float startingAngle)263 private PipTransitionAnimator(TaskInfo taskInfo, SurfaceControl leash, 264 @AnimationType int animationType, Rect destinationBounds, T baseValue, T startValue, 265 T endValue, float startingAngle) { 266 mTaskInfo = taskInfo; 267 mLeash = leash; 268 mAnimationType = animationType; 269 mDestinationBounds.set(destinationBounds); 270 mBaseValue = baseValue; 271 mStartValue = startValue; 272 mEndValue = endValue; 273 mStartingAngle = startingAngle; 274 addListener(this); 275 addUpdateListener(this); 276 mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; 277 mTransitionDirection = TRANSITION_DIRECTION_NONE; 278 } 279 280 @Override onAnimationStart(Animator animation)281 public void onAnimationStart(Animator animation) { 282 mCurrentValue = mStartValue; 283 onStartTransaction(mLeash, newSurfaceControlTransaction()); 284 if (mPipAnimationCallback != null) { 285 mPipAnimationCallback.onPipAnimationStart(mTaskInfo, this); 286 } 287 } 288 289 @Override onAnimationUpdate(ValueAnimator animation)290 public void onAnimationUpdate(ValueAnimator animation) { 291 applySurfaceControlTransaction(mLeash, newSurfaceControlTransaction(), 292 animation.getAnimatedFraction()); 293 } 294 295 @Override onAnimationEnd(Animator animation)296 public void onAnimationEnd(Animator animation) { 297 mCurrentValue = mEndValue; 298 final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); 299 onEndTransaction(mLeash, tx, mTransitionDirection); 300 if (mPipAnimationCallback != null) { 301 mPipAnimationCallback.onPipAnimationEnd(mTaskInfo, tx, this); 302 } 303 mTransitionDirection = TRANSITION_DIRECTION_NONE; 304 } 305 306 @Override onAnimationCancel(Animator animation)307 public void onAnimationCancel(Animator animation) { 308 if (mPipAnimationCallback != null) { 309 mPipAnimationCallback.onPipAnimationCancel(mTaskInfo, this); 310 } 311 mTransitionDirection = TRANSITION_DIRECTION_NONE; 312 } 313 onAnimationRepeat(Animator animation)314 @Override public void onAnimationRepeat(Animator animation) {} 315 316 @VisibleForTesting getAnimationType()317 @AnimationType public int getAnimationType() { 318 return mAnimationType; 319 } 320 321 @VisibleForTesting setPipAnimationCallback(PipAnimationCallback callback)322 public PipTransitionAnimator<T> setPipAnimationCallback(PipAnimationCallback callback) { 323 mPipAnimationCallback = callback; 324 return this; 325 } 326 setPipTransactionHandler(PipTransactionHandler handler)327 PipTransitionAnimator<T> setPipTransactionHandler(PipTransactionHandler handler) { 328 mPipTransactionHandler = handler; 329 return this; 330 } 331 handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, Rect destinationBounds)332 boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, 333 Rect destinationBounds) { 334 if (mPipTransactionHandler != null) { 335 return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds); 336 } 337 return false; 338 } 339 getContentOverlay()340 SurfaceControl getContentOverlay() { 341 return mContentOverlay; 342 } 343 setUseContentOverlay(Context context)344 PipTransitionAnimator<T> setUseContentOverlay(Context context) { 345 final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); 346 if (mContentOverlay != null) { 347 // remove existing content overlay if there is any. 348 tx.remove(mContentOverlay); 349 tx.apply(); 350 } 351 mContentOverlay = new SurfaceControl.Builder(new SurfaceSession()) 352 .setCallsite("PipAnimation") 353 .setName("PipContentOverlay") 354 .setColorLayer() 355 .build(); 356 tx.show(mContentOverlay); 357 tx.setLayer(mContentOverlay, Integer.MAX_VALUE); 358 tx.setColor(mContentOverlay, getContentOverlayColor(context)); 359 tx.setAlpha(mContentOverlay, 0f); 360 tx.reparent(mContentOverlay, mLeash); 361 tx.apply(); 362 return this; 363 } 364 getContentOverlayColor(Context context)365 private float[] getContentOverlayColor(Context context) { 366 final TypedArray ta = context.obtainStyledAttributes(new int[] { 367 android.R.attr.colorBackground }); 368 try { 369 int colorAccent = ta.getColor(0, 0); 370 return new float[] { 371 Color.red(colorAccent) / 255f, 372 Color.green(colorAccent) / 255f, 373 Color.blue(colorAccent) / 255f }; 374 } finally { 375 ta.recycle(); 376 } 377 } 378 379 /** 380 * Clears the {@link #mContentOverlay}, this should be done after the content overlay is 381 * faded out, such as in {@link PipTaskOrganizer#fadeOutAndRemoveOverlay} 382 */ clearContentOverlay()383 void clearContentOverlay() { 384 mContentOverlay = null; 385 } 386 387 @VisibleForTesting getTransitionDirection()388 @TransitionDirection public int getTransitionDirection() { 389 return mTransitionDirection; 390 } 391 392 @VisibleForTesting setTransitionDirection(@ransitionDirection int direction)393 public PipTransitionAnimator<T> setTransitionDirection(@TransitionDirection int direction) { 394 if (direction != TRANSITION_DIRECTION_SAME) { 395 mTransitionDirection = direction; 396 } 397 return this; 398 } 399 getStartValue()400 T getStartValue() { 401 return mStartValue; 402 } 403 getBaseValue()404 T getBaseValue() { 405 return mBaseValue; 406 } 407 408 @VisibleForTesting getEndValue()409 public T getEndValue() { 410 return mEndValue; 411 } 412 getDestinationBounds()413 Rect getDestinationBounds() { 414 return mDestinationBounds; 415 } 416 setDestinationBounds(Rect destinationBounds)417 void setDestinationBounds(Rect destinationBounds) { 418 mDestinationBounds.set(destinationBounds); 419 if (mAnimationType == ANIM_TYPE_ALPHA) { 420 onStartTransaction(mLeash, newSurfaceControlTransaction()); 421 } 422 } 423 setCurrentValue(T value)424 void setCurrentValue(T value) { 425 mCurrentValue = value; 426 } 427 shouldApplyCornerRadius()428 boolean shouldApplyCornerRadius() { 429 return !isOutPipDirection(mTransitionDirection); 430 } 431 inScaleTransition()432 boolean inScaleTransition() { 433 if (mAnimationType != ANIM_TYPE_BOUNDS) return false; 434 final int direction = getTransitionDirection(); 435 return !isInPipDirection(direction) && !isOutPipDirection(direction); 436 } 437 438 /** 439 * Updates the {@link #mEndValue}. 440 * 441 * NOTE: Do not forget to call {@link #setDestinationBounds(Rect)} for bounds animation. 442 * This is typically used when we receive a shelf height adjustment during the bounds 443 * animation. In which case we can update the end bounds and keep the existing animation 444 * running instead of cancelling it. 445 */ updateEndValue(T endValue)446 public void updateEndValue(T endValue) { 447 mEndValue = endValue; 448 } 449 450 /** 451 * @return {@link SurfaceControl.Transaction} instance with vsync-id. 452 */ newSurfaceControlTransaction()453 protected SurfaceControl.Transaction newSurfaceControlTransaction() { 454 final SurfaceControl.Transaction tx = 455 mSurfaceControlTransactionFactory.getTransaction(); 456 tx.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId()); 457 return tx; 458 } 459 460 @VisibleForTesting setSurfaceControlTransactionFactory( PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory)461 public void setSurfaceControlTransactionFactory( 462 PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) { 463 mSurfaceControlTransactionFactory = factory; 464 } 465 getSurfaceTransactionHelper()466 PipSurfaceTransactionHelper getSurfaceTransactionHelper() { 467 return mSurfaceTransactionHelper; 468 } 469 setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper)470 void setSurfaceTransactionHelper(PipSurfaceTransactionHelper helper) { 471 mSurfaceTransactionHelper = helper; 472 } 473 onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)474 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) {} 475 onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, @TransitionDirection int transitionDirection)476 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, 477 @TransitionDirection int transitionDirection) {} 478 applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)479 abstract void applySurfaceControlTransaction(SurfaceControl leash, 480 SurfaceControl.Transaction tx, float fraction); 481 ofAlpha(TaskInfo taskInfo, SurfaceControl leash, Rect destinationBounds, float startValue, float endValue)482 static PipTransitionAnimator<Float> ofAlpha(TaskInfo taskInfo, SurfaceControl leash, 483 Rect destinationBounds, float startValue, float endValue) { 484 return new PipTransitionAnimator<Float>(taskInfo, leash, ANIM_TYPE_ALPHA, 485 destinationBounds, startValue, startValue, endValue, 0) { 486 @Override 487 void applySurfaceControlTransaction(SurfaceControl leash, 488 SurfaceControl.Transaction tx, float fraction) { 489 final float alpha = getStartValue() * (1 - fraction) + getEndValue() * fraction; 490 setCurrentValue(alpha); 491 getSurfaceTransactionHelper().alpha(tx, leash, alpha) 492 .round(tx, leash, shouldApplyCornerRadius()); 493 tx.apply(); 494 } 495 496 @Override 497 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 498 if (getTransitionDirection() == TRANSITION_DIRECTION_REMOVE_STACK) { 499 // while removing the pip stack, no extra work needs to be done here. 500 return; 501 } 502 getSurfaceTransactionHelper() 503 .resetScale(tx, leash, getDestinationBounds()) 504 .crop(tx, leash, getDestinationBounds()) 505 .round(tx, leash, shouldApplyCornerRadius()); 506 tx.show(leash); 507 tx.apply(); 508 } 509 510 @Override 511 public void updateEndValue(Float endValue) { 512 super.updateEndValue(endValue); 513 mStartValue = mCurrentValue; 514 } 515 }; 516 } 517 ofBounds(TaskInfo taskInfo, SurfaceControl leash, Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, @PipAnimationController.TransitionDirection int direction, float startingAngle, @Surface.Rotation int rotationDelta)518 static PipTransitionAnimator<Rect> ofBounds(TaskInfo taskInfo, SurfaceControl leash, 519 Rect baseValue, Rect startValue, Rect endValue, Rect sourceHintRect, 520 @PipAnimationController.TransitionDirection int direction, float startingAngle, 521 @Surface.Rotation int rotationDelta) { 522 final boolean isOutPipDirection = isOutPipDirection(direction); 523 524 // Just for simplicity we'll interpolate between the source rect hint insets and empty 525 // insets to calculate the window crop 526 final Rect initialSourceValue; 527 if (isOutPipDirection) { 528 initialSourceValue = new Rect(endValue); 529 } else { 530 initialSourceValue = new Rect(baseValue); 531 } 532 533 final Rect rotatedEndRect; 534 final Rect lastEndRect; 535 final Rect initialContainerRect; 536 if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) { 537 lastEndRect = new Rect(endValue); 538 rotatedEndRect = new Rect(endValue); 539 // Rotate the end bounds according to the rotation delta because the display will 540 // be rotated to the same orientation. 541 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta); 542 // Use the rect that has the same orientation as the hint rect. 543 initialContainerRect = isOutPipDirection ? rotatedEndRect : initialSourceValue; 544 } else { 545 rotatedEndRect = lastEndRect = null; 546 initialContainerRect = initialSourceValue; 547 } 548 549 final Rect sourceHintRectInsets; 550 if (sourceHintRect == null) { 551 sourceHintRectInsets = null; 552 } else { 553 sourceHintRectInsets = new Rect(sourceHintRect.left - initialContainerRect.left, 554 sourceHintRect.top - initialContainerRect.top, 555 initialContainerRect.right - sourceHintRect.right, 556 initialContainerRect.bottom - sourceHintRect.bottom); 557 } 558 final Rect zeroInsets = new Rect(0, 0, 0, 0); 559 560 // construct new Rect instances in case they are recycled 561 return new PipTransitionAnimator<Rect>(taskInfo, leash, ANIM_TYPE_BOUNDS, 562 endValue, new Rect(baseValue), new Rect(startValue), new Rect(endValue), 563 startingAngle) { 564 private final RectEvaluator mRectEvaluator = new RectEvaluator(new Rect()); 565 private final RectEvaluator mInsetsEvaluator = new RectEvaluator(new Rect()); 566 567 @Override 568 void applySurfaceControlTransaction(SurfaceControl leash, 569 SurfaceControl.Transaction tx, float fraction) { 570 final Rect base = getBaseValue(); 571 final Rect start = getStartValue(); 572 final Rect end = getEndValue(); 573 if (mContentOverlay != null) { 574 tx.setAlpha(mContentOverlay, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2); 575 } 576 if (rotatedEndRect != null) { 577 // Animate the bounds in a different orientation. It only happens when 578 // switching between PiP and fullscreen. 579 applyRotation(tx, leash, fraction, start, end); 580 return; 581 } 582 Rect bounds = mRectEvaluator.evaluate(fraction, start, end); 583 float angle = (1.0f - fraction) * startingAngle; 584 setCurrentValue(bounds); 585 if (inScaleTransition() || sourceHintRect == null) { 586 if (isOutPipDirection) { 587 getSurfaceTransactionHelper().scale(tx, leash, end, bounds); 588 } else { 589 getSurfaceTransactionHelper().scale(tx, leash, base, bounds, angle) 590 .round(tx, leash, base, bounds); 591 } 592 } else { 593 final Rect insets = computeInsets(fraction); 594 getSurfaceTransactionHelper().scaleAndCrop(tx, leash, 595 initialSourceValue, bounds, insets); 596 if (shouldApplyCornerRadius()) { 597 final Rect sourceBounds = new Rect(initialContainerRect); 598 sourceBounds.inset(insets); 599 getSurfaceTransactionHelper().round(tx, leash, 600 sourceBounds, bounds); 601 } 602 } 603 if (!handlePipTransaction(leash, tx, bounds)) { 604 tx.apply(); 605 } 606 } 607 608 private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash, 609 float fraction, Rect start, Rect end) { 610 if (!end.equals(lastEndRect)) { 611 // If the end bounds are changed during animating (e.g. shelf height), the 612 // rotated end bounds also need to be updated. 613 rotatedEndRect.set(endValue); 614 rotateBounds(rotatedEndRect, initialSourceValue, rotationDelta); 615 lastEndRect.set(end); 616 } 617 final Rect bounds = mRectEvaluator.evaluate(fraction, start, rotatedEndRect); 618 setCurrentValue(bounds); 619 final Rect insets = computeInsets(fraction); 620 final float degree, x, y; 621 if (Transitions.ENABLE_SHELL_TRANSITIONS) { 622 if (rotationDelta == ROTATION_90) { 623 degree = 90 * (1 - fraction); 624 x = fraction * (end.left - start.left) 625 + start.left + start.right * (1 - fraction); 626 y = fraction * (end.top - start.top) + start.top; 627 } else { 628 degree = -90 * (1 - fraction); 629 x = fraction * (end.left - start.left) + start.left; 630 y = fraction * (end.top - start.top) 631 + start.top + start.bottom * (1 - fraction); 632 } 633 } else { 634 if (rotationDelta == ROTATION_90) { 635 degree = 90 * fraction; 636 x = fraction * (end.right - start.left) + start.left; 637 y = fraction * (end.top - start.top) + start.top; 638 } else { 639 degree = -90 * fraction; 640 x = fraction * (end.left - start.left) + start.left; 641 y = fraction * (end.bottom - start.top) + start.top; 642 } 643 } 644 final Rect sourceBounds = new Rect(initialContainerRect); 645 sourceBounds.inset(insets); 646 getSurfaceTransactionHelper() 647 .rotateAndScaleWithCrop(tx, leash, initialContainerRect, bounds, 648 insets, degree, x, y, isOutPipDirection, 649 rotationDelta == ROTATION_270 /* clockwise */) 650 .round(tx, leash, sourceBounds, bounds); 651 tx.apply(); 652 } 653 654 private Rect computeInsets(float fraction) { 655 if (sourceHintRectInsets == null) { 656 return zeroInsets; 657 } 658 final Rect startRect = isOutPipDirection ? sourceHintRectInsets : zeroInsets; 659 final Rect endRect = isOutPipDirection ? zeroInsets : sourceHintRectInsets; 660 return mInsetsEvaluator.evaluate(fraction, startRect, endRect); 661 } 662 663 @Override 664 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 665 getSurfaceTransactionHelper() 666 .alpha(tx, leash, 1f) 667 .round(tx, leash, shouldApplyCornerRadius()); 668 // TODO(b/178632364): this is a work around for the black background when 669 // entering PiP in buttion navigation mode. 670 if (isInPipDirection(direction)) { 671 tx.setWindowCrop(leash, getStartValue()); 672 } 673 tx.show(leash); 674 tx.apply(); 675 } 676 677 @Override 678 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, 679 int transitionDirection) { 680 // NOTE: intentionally does not apply the transaction here. 681 // this end transaction should get executed synchronously with the final 682 // WindowContainerTransaction in task organizer 683 final Rect destBounds = getDestinationBounds(); 684 getSurfaceTransactionHelper().resetScale(tx, leash, destBounds); 685 if (isOutPipDirection(transitionDirection)) { 686 // Exit pip, clear scale, position and crop. 687 tx.setMatrix(leash, 1, 0, 0, 1); 688 tx.setPosition(leash, 0, 0); 689 tx.setWindowCrop(leash, 0, 0); 690 } else { 691 getSurfaceTransactionHelper().crop(tx, leash, destBounds); 692 } 693 } 694 695 @Override 696 public void updateEndValue(Rect endValue) { 697 super.updateEndValue(endValue); 698 if (mStartValue != null && mCurrentValue != null) { 699 mStartValue.set(mCurrentValue); 700 } 701 } 702 }; 703 } 704 } 705 } 706