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