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