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