1 /* 2 * Copyright (C) 2016 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.systemui.pip.phone; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.graphics.PointF; 24 import android.graphics.Rect; 25 import android.os.Debug; 26 import android.util.Log; 27 import android.view.Choreographer; 28 29 import androidx.dynamicanimation.animation.AnimationHandler; 30 import androidx.dynamicanimation.animation.AnimationHandler.FrameCallbackScheduler; 31 import androidx.dynamicanimation.animation.SpringForce; 32 33 import com.android.systemui.pip.PipSnapAlgorithm; 34 import com.android.systemui.pip.PipTaskOrganizer; 35 import com.android.systemui.util.FloatingContentCoordinator; 36 import com.android.systemui.util.animation.FloatProperties; 37 import com.android.systemui.util.animation.PhysicsAnimator; 38 import com.android.systemui.util.magnetictarget.MagnetizedObject; 39 40 import java.io.PrintWriter; 41 import java.util.function.Consumer; 42 43 import kotlin.Unit; 44 import kotlin.jvm.functions.Function0; 45 46 /** 47 * A helper to animate and manipulate the PiP. 48 */ 49 public class PipMotionHelper implements PipAppOpsListener.Callback, 50 FloatingContentCoordinator.FloatingContent { 51 52 private static final String TAG = "PipMotionHelper"; 53 private static final boolean DEBUG = false; 54 55 private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; 56 private static final int EXPAND_STACK_TO_MENU_DURATION = 250; 57 private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300; 58 private static final int SHIFT_DURATION = 300; 59 60 /** Friction to use for PIP when it moves via physics fling animations. */ 61 private static final float DEFAULT_FRICTION = 2f; 62 63 private final Context mContext; 64 private final PipTaskOrganizer mPipTaskOrganizer; 65 66 private PipMenuActivityController mMenuController; 67 private PipSnapAlgorithm mSnapAlgorithm; 68 69 /** PIP's current bounds on the screen. */ 70 private final Rect mBounds = new Rect(); 71 72 /** The bounds within which PIP's top-left coordinate is allowed to move. */ 73 private final Rect mMovementBounds = new Rect(); 74 75 /** The region that all of PIP must stay within. */ 76 private final Rect mFloatingAllowedArea = new Rect(); 77 78 /** 79 * Temporary bounds used when PIP is being dragged or animated. These bounds are applied to PIP 80 * using {@link PipTaskOrganizer#scheduleUserResizePip}, so that we can animate shrinking into 81 * and expanding out of the magnetic dismiss target. 82 * 83 * Once PIP is done being dragged or animated, we set {@link #mBounds} equal to these temporary 84 * bounds, and call {@link PipTaskOrganizer#scheduleFinishResizePip} to 'officially' move PIP to 85 * its new bounds. 86 */ 87 private final Rect mTemporaryBounds = new Rect(); 88 89 /** The destination bounds to which PIP is animating. */ 90 private final Rect mAnimatingToBounds = new Rect(); 91 92 /** Coordinator instance for resolving conflicts with other floating content. */ 93 private FloatingContentCoordinator mFloatingContentCoordinator; 94 95 private ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = 96 ThreadLocal.withInitial(() -> { 97 FrameCallbackScheduler scheduler = runnable -> 98 Choreographer.getSfInstance().postFrameCallback(t -> runnable.run()); 99 AnimationHandler handler = new AnimationHandler(scheduler); 100 return handler; 101 }); 102 103 /** 104 * PhysicsAnimator instance for animating {@link #mTemporaryBounds} using physics animations. 105 */ 106 private PhysicsAnimator<Rect> mTemporaryBoundsPhysicsAnimator = PhysicsAnimator.getInstance( 107 mTemporaryBounds); 108 109 private MagnetizedObject<Rect> mMagnetizedPip; 110 111 /** 112 * Update listener that resizes the PIP to {@link #mTemporaryBounds}. 113 */ 114 private final PhysicsAnimator.UpdateListener<Rect> mResizePipUpdateListener; 115 116 /** FlingConfig instances provided to PhysicsAnimator for fling gestures. */ 117 private PhysicsAnimator.FlingConfig mFlingConfigX; 118 private PhysicsAnimator.FlingConfig mFlingConfigY; 119 120 /** SpringConfig to use for fling-then-spring animations. */ 121 private final PhysicsAnimator.SpringConfig mSpringConfig = 122 new PhysicsAnimator.SpringConfig( 123 SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY); 124 125 /** SpringConfig to use for springing PIP away from conflicting floating content. */ 126 private final PhysicsAnimator.SpringConfig mConflictResolutionSpringConfig = 127 new PhysicsAnimator.SpringConfig( 128 SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY); 129 130 private final Consumer<Rect> mUpdateBoundsCallback = mBounds::set; 131 132 /** 133 * Whether we're springing to the touch event location (vs. moving it to that position 134 * instantly). We spring-to-touch after PIP is dragged out of the magnetic target, since it was 135 * 'stuck' in the target and needs to catch up to the touch location. 136 */ 137 private boolean mSpringingToTouch = false; 138 139 /** 140 * Whether PIP was released in the dismiss target, and will be animated out and dismissed 141 * shortly. 142 */ 143 private boolean mDismissalPending = false; 144 145 /** 146 * Gets set in {@link #animateToExpandedState(Rect, Rect, Rect, Runnable)}, this callback is 147 * used to show menu activity when the expand animation is completed. 148 */ 149 private Runnable mPostPipTransitionCallback; 150 151 private final PipTaskOrganizer.PipTransitionCallback mPipTransitionCallback = 152 new PipTaskOrganizer.PipTransitionCallback() { 153 @Override 154 public void onPipTransitionStarted(ComponentName activity, int direction) {} 155 156 @Override 157 public void onPipTransitionFinished(ComponentName activity, int direction) { 158 if (mPostPipTransitionCallback != null) { 159 mPostPipTransitionCallback.run(); 160 mPostPipTransitionCallback = null; 161 } 162 } 163 164 @Override 165 public void onPipTransitionCanceled(ComponentName activity, int direction) {} 166 }; 167 PipMotionHelper(Context context, PipTaskOrganizer pipTaskOrganizer, PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm, FloatingContentCoordinator floatingContentCoordinator)168 public PipMotionHelper(Context context, PipTaskOrganizer pipTaskOrganizer, 169 PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm, 170 FloatingContentCoordinator floatingContentCoordinator) { 171 mContext = context; 172 mPipTaskOrganizer = pipTaskOrganizer; 173 mMenuController = menuController; 174 mSnapAlgorithm = snapAlgorithm; 175 mFloatingContentCoordinator = floatingContentCoordinator; 176 mPipTaskOrganizer.registerPipTransitionCallback(mPipTransitionCallback); 177 mTemporaryBoundsPhysicsAnimator.setCustomAnimationHandler( 178 mSfAnimationHandlerThreadLocal.get()); 179 180 mResizePipUpdateListener = (target, values) -> { 181 if (!mTemporaryBounds.isEmpty()) { 182 mPipTaskOrganizer.scheduleUserResizePip( 183 mBounds, mTemporaryBounds, null); 184 } 185 }; 186 } 187 188 @NonNull 189 @Override getFloatingBoundsOnScreen()190 public Rect getFloatingBoundsOnScreen() { 191 return !mAnimatingToBounds.isEmpty() ? mAnimatingToBounds : mBounds; 192 } 193 194 @NonNull 195 @Override getAllowedFloatingBoundsRegion()196 public Rect getAllowedFloatingBoundsRegion() { 197 return mFloatingAllowedArea; 198 } 199 200 @Override moveToBounds(@onNull Rect bounds)201 public void moveToBounds(@NonNull Rect bounds) { 202 animateToBounds(bounds, mConflictResolutionSpringConfig); 203 } 204 205 /** 206 * Synchronizes the current bounds with the pinned stack, cancelling any ongoing animations. 207 */ synchronizePinnedStackBounds()208 void synchronizePinnedStackBounds() { 209 cancelAnimations(); 210 mBounds.set(mPipTaskOrganizer.getLastReportedBounds()); 211 mTemporaryBounds.setEmpty(); 212 213 if (mPipTaskOrganizer.isInPip()) { 214 mFloatingContentCoordinator.onContentMoved(this); 215 } 216 } 217 isAnimating()218 boolean isAnimating() { 219 return mTemporaryBoundsPhysicsAnimator.isRunning(); 220 } 221 222 /** 223 * Tries to move the pinned stack to the given {@param bounds}. 224 */ movePip(Rect toBounds)225 void movePip(Rect toBounds) { 226 movePip(toBounds, false /* isDragging */); 227 } 228 229 /** 230 * Tries to move the pinned stack to the given {@param bounds}. 231 * 232 * @param isDragging Whether this movement is the result of a drag touch gesture. If so, we 233 * won't notify the floating content coordinator of this move, since that will 234 * happen when the gesture ends. 235 */ movePip(Rect toBounds, boolean isDragging)236 void movePip(Rect toBounds, boolean isDragging) { 237 if (!isDragging) { 238 mFloatingContentCoordinator.onContentMoved(this); 239 } 240 241 if (!mSpringingToTouch) { 242 // If we are moving PIP directly to the touch event locations, cancel any animations and 243 // move PIP to the given bounds. 244 cancelAnimations(); 245 246 if (!isDragging) { 247 resizePipUnchecked(toBounds); 248 mBounds.set(toBounds); 249 } else { 250 mTemporaryBounds.set(toBounds); 251 mPipTaskOrganizer.scheduleUserResizePip(mBounds, mTemporaryBounds, null); 252 } 253 } else { 254 // If PIP is 'catching up' after being stuck in the dismiss target, update the animation 255 // to spring towards the new touch location. 256 mTemporaryBoundsPhysicsAnimator 257 .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) 258 .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) 259 .spring(FloatProperties.RECT_X, toBounds.left, mSpringConfig) 260 .spring(FloatProperties.RECT_Y, toBounds.top, mSpringConfig); 261 262 startBoundsAnimator(toBounds.left /* toX */, toBounds.top /* toY */, 263 false /* dismiss */); 264 } 265 } 266 267 /** Animates the PIP into the dismiss target, scaling it down. */ animateIntoDismissTarget( MagnetizedObject.MagneticTarget target, float velX, float velY, boolean flung, Function0<Unit> after)268 void animateIntoDismissTarget( 269 MagnetizedObject.MagneticTarget target, 270 float velX, float velY, 271 boolean flung, Function0<Unit> after) { 272 final PointF targetCenter = target.getCenterOnScreen(); 273 274 final float desiredWidth = mBounds.width() / 2; 275 final float desiredHeight = mBounds.height() / 2; 276 277 final float destinationX = targetCenter.x - (desiredWidth / 2f); 278 final float destinationY = targetCenter.y - (desiredHeight / 2f); 279 280 // If we're already in the dismiss target area, then there won't be a move to set the 281 // temporary bounds, so just initialize it to the current bounds 282 if (mTemporaryBounds.isEmpty()) { 283 mTemporaryBounds.set(mBounds); 284 } 285 mTemporaryBoundsPhysicsAnimator 286 .spring(FloatProperties.RECT_X, destinationX, velX, mSpringConfig) 287 .spring(FloatProperties.RECT_Y, destinationY, velY, mSpringConfig) 288 .spring(FloatProperties.RECT_WIDTH, desiredWidth, mSpringConfig) 289 .spring(FloatProperties.RECT_HEIGHT, desiredHeight, mSpringConfig) 290 .withEndActions(after); 291 292 startBoundsAnimator(destinationX, destinationY, false); 293 } 294 295 /** Set whether we're springing-to-touch to catch up after being stuck in the dismiss target. */ setSpringingToTouch(boolean springingToTouch)296 void setSpringingToTouch(boolean springingToTouch) { 297 mSpringingToTouch = springingToTouch; 298 } 299 300 /** 301 * Resizes the pinned stack back to fullscreen. 302 */ expandPipToFullscreen()303 void expandPipToFullscreen() { 304 expandPipToFullscreen(false /* skipAnimation */); 305 } 306 307 /** 308 * Resizes the pinned stack back to fullscreen. 309 */ expandPipToFullscreen(boolean skipAnimation)310 void expandPipToFullscreen(boolean skipAnimation) { 311 if (DEBUG) { 312 Log.d(TAG, "exitPip: skipAnimation=" + skipAnimation 313 + " callers=\n" + Debug.getCallers(5, " ")); 314 } 315 cancelAnimations(); 316 mMenuController.hideMenuWithoutResize(); 317 mPipTaskOrganizer.getUpdateHandler().post(() -> { 318 mPipTaskOrganizer.exitPip(skipAnimation 319 ? 0 320 : EXPAND_STACK_TO_FULLSCREEN_DURATION); 321 }); 322 } 323 324 /** 325 * Dismisses the pinned stack. 326 */ 327 @Override dismissPip()328 public void dismissPip() { 329 if (DEBUG) { 330 Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " ")); 331 } 332 cancelAnimations(); 333 mMenuController.hideMenuWithoutResize(); 334 mPipTaskOrganizer.removePip(); 335 } 336 337 /** Sets the movement bounds to use to constrain PIP position animations. */ setCurrentMovementBounds(Rect movementBounds)338 void setCurrentMovementBounds(Rect movementBounds) { 339 mMovementBounds.set(movementBounds); 340 rebuildFlingConfigs(); 341 342 // The movement bounds represent the area within which we can move PIP's top-left position. 343 // The allowed area for all of PIP is those bounds plus PIP's width and height. 344 mFloatingAllowedArea.set(mMovementBounds); 345 mFloatingAllowedArea.right += mBounds.width(); 346 mFloatingAllowedArea.bottom += mBounds.height(); 347 } 348 349 /** 350 * @return the PiP bounds. 351 */ getBounds()352 Rect getBounds() { 353 return mBounds; 354 } 355 356 /** 357 * Returns the PIP bounds if we're not animating, or the current, temporary animating bounds 358 * otherwise. 359 */ getPossiblyAnimatingBounds()360 Rect getPossiblyAnimatingBounds() { 361 return mTemporaryBounds.isEmpty() ? mBounds : mTemporaryBounds; 362 } 363 364 /** 365 * Flings the PiP to the closest snap target. 366 */ flingToSnapTarget( float velocityX, float velocityY, @Nullable Runnable updateAction, @Nullable Runnable endAction)367 void flingToSnapTarget( 368 float velocityX, float velocityY, 369 @Nullable Runnable updateAction, @Nullable Runnable endAction) { 370 // If we're flinging to a snap target now, we're not springing to catch up to the touch 371 // location now. 372 mSpringingToTouch = false; 373 374 mTemporaryBoundsPhysicsAnimator 375 .spring(FloatProperties.RECT_WIDTH, mBounds.width(), mSpringConfig) 376 .spring(FloatProperties.RECT_HEIGHT, mBounds.height(), mSpringConfig) 377 .flingThenSpring( 378 FloatProperties.RECT_X, velocityX, mFlingConfigX, mSpringConfig, 379 true /* flingMustReachMinOrMax */) 380 .flingThenSpring( 381 FloatProperties.RECT_Y, velocityY, mFlingConfigY, mSpringConfig) 382 .withEndActions(endAction); 383 384 if (updateAction != null) { 385 mTemporaryBoundsPhysicsAnimator.addUpdateListener( 386 (target, values) -> updateAction.run()); 387 } 388 389 final float xEndValue = velocityX < 0 ? mMovementBounds.left : mMovementBounds.right; 390 final float estimatedFlingYEndValue = 391 PhysicsAnimator.estimateFlingEndValue( 392 mTemporaryBounds.top, velocityY, mFlingConfigY); 393 394 startBoundsAnimator(xEndValue /* toX */, estimatedFlingYEndValue /* toY */, 395 false /* dismiss */); 396 } 397 398 /** 399 * Animates PIP to the provided bounds, using physics animations and the given spring 400 * configuration 401 */ 402 void animateToBounds(Rect bounds, PhysicsAnimator.SpringConfig springConfig) { 403 if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { 404 // Animate from the current bounds if we're not already animating. 405 mTemporaryBounds.set(mBounds); 406 } 407 408 mTemporaryBoundsPhysicsAnimator 409 .spring(FloatProperties.RECT_X, bounds.left, springConfig) 410 .spring(FloatProperties.RECT_Y, bounds.top, springConfig); 411 startBoundsAnimator(bounds.left /* toX */, bounds.top /* toY */, 412 false /* dismiss */); 413 } 414 415 /** 416 * Animates the dismissal of the PiP off the edge of the screen. 417 */ 418 void animateDismiss() { 419 // Animate off the bottom of the screen, then dismiss PIP. 420 mTemporaryBoundsPhysicsAnimator 421 .spring(FloatProperties.RECT_Y, 422 mMovementBounds.bottom + mBounds.height() * 2, 423 0, 424 mSpringConfig) 425 .withEndActions(this::dismissPip); 426 427 startBoundsAnimator( 428 mBounds.left /* toX */, mBounds.bottom + mBounds.height() /* toY */, 429 true /* dismiss */); 430 431 mDismissalPending = false; 432 } 433 434 /** 435 * Animates the PiP to the expanded state to show the menu. 436 */ 437 float animateToExpandedState(Rect expandedBounds, Rect movementBounds, 438 Rect expandedMovementBounds, Runnable callback) { 439 float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), movementBounds); 440 mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction); 441 mPostPipTransitionCallback = callback; 442 resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION); 443 return savedSnapFraction; 444 } 445 446 /** 447 * Animates the PiP from the expanded state to the normal state after the menu is hidden. 448 */ 449 void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction, 450 Rect normalMovementBounds, Rect currentMovementBounds, boolean immediate) { 451 if (savedSnapFraction < 0f) { 452 // If there are no saved snap fractions, then just use the current bounds 453 savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), 454 currentMovementBounds); 455 } 456 mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction); 457 458 if (immediate) { 459 movePip(normalBounds); 460 } else { 461 resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION); 462 } 463 } 464 465 /** 466 * Animates the PiP to offset it from the IME or shelf. 467 */ 468 void animateToOffset(Rect originalBounds, int offset) { 469 if (DEBUG) { 470 Log.d(TAG, "animateToOffset: originalBounds=" + originalBounds + " offset=" + offset 471 + " callers=\n" + Debug.getCallers(5, " ")); 472 } 473 cancelAnimations(); 474 mPipTaskOrganizer.scheduleOffsetPip(originalBounds, offset, SHIFT_DURATION, 475 mUpdateBoundsCallback); 476 } 477 478 /** 479 * Cancels all existing animations. 480 */ 481 private void cancelAnimations() { 482 mTemporaryBoundsPhysicsAnimator.cancel(); 483 mAnimatingToBounds.setEmpty(); 484 mSpringingToTouch = false; 485 } 486 487 /** Set new fling configs whose min/max values respect the given movement bounds. */ 488 private void rebuildFlingConfigs() { 489 mFlingConfigX = new PhysicsAnimator.FlingConfig( 490 DEFAULT_FRICTION, mMovementBounds.left, mMovementBounds.right); 491 mFlingConfigY = new PhysicsAnimator.FlingConfig( 492 DEFAULT_FRICTION, mMovementBounds.top, mMovementBounds.bottom); 493 } 494 495 /** 496 * Starts the physics animator which will update the animated PIP bounds using physics 497 * animations, as well as the TimeAnimator which will apply those bounds to PIP. 498 * 499 * This will also add end actions to the bounds animator that cancel the TimeAnimator and update 500 * the 'real' bounds to equal the final animated bounds. 501 */ 502 private void startBoundsAnimator(float toX, float toY, boolean dismiss) { 503 if (!mSpringingToTouch) { 504 cancelAnimations(); 505 } 506 507 // Set animatingToBounds directly to avoid allocating a new Rect, but then call 508 // setAnimatingToBounds to run the normal logic for changing animatingToBounds. 509 mAnimatingToBounds.set( 510 (int) toX, 511 (int) toY, 512 (int) toX + mBounds.width(), 513 (int) toY + mBounds.height()); 514 setAnimatingToBounds(mAnimatingToBounds); 515 516 if (!mTemporaryBoundsPhysicsAnimator.isRunning()) { 517 mTemporaryBoundsPhysicsAnimator 518 .addUpdateListener(mResizePipUpdateListener) 519 .withEndActions(this::onBoundsAnimationEnd); 520 } 521 522 mTemporaryBoundsPhysicsAnimator.start(); 523 } 524 525 /** 526 * Notify that PIP was released in the dismiss target and will be animated out and dismissed 527 * shortly. 528 */ 529 void notifyDismissalPending() { 530 mDismissalPending = true; 531 } 532 533 private void onBoundsAnimationEnd() { 534 if (!mDismissalPending 535 && !mSpringingToTouch 536 && !mMagnetizedPip.getObjectStuckToTarget()) { 537 mBounds.set(mTemporaryBounds); 538 if (!mDismissalPending) { 539 // do not schedule resize if PiP is dismissing, which may cause app re-open to 540 // mBounds instead of it's normal bounds. 541 mPipTaskOrganizer.scheduleFinishResizePip(mBounds); 542 } 543 mTemporaryBounds.setEmpty(); 544 } 545 546 mAnimatingToBounds.setEmpty(); 547 mSpringingToTouch = false; 548 mDismissalPending = false; 549 } 550 551 /** 552 * Notifies the floating coordinator that we're moving, and sets {@link #mAnimatingToBounds} so 553 * we return these bounds from 554 * {@link FloatingContentCoordinator.FloatingContent#getFloatingBoundsOnScreen()}. 555 */ 556 private void setAnimatingToBounds(Rect bounds) { 557 mAnimatingToBounds.set(bounds); 558 mFloatingContentCoordinator.onContentMoved(this); 559 } 560 561 /** 562 * Directly resizes the PiP to the given {@param bounds}. 563 */ 564 private void resizePipUnchecked(Rect toBounds) { 565 if (DEBUG) { 566 Log.d(TAG, "resizePipUnchecked: toBounds=" + toBounds 567 + " callers=\n" + Debug.getCallers(5, " ")); 568 } 569 if (!toBounds.equals(mBounds)) { 570 mPipTaskOrganizer.scheduleResizePip(toBounds, mUpdateBoundsCallback); 571 } 572 } 573 574 /** 575 * Directly resizes the PiP to the given {@param bounds}. 576 */ 577 private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) { 578 if (DEBUG) { 579 Log.d(TAG, "resizeAndAnimatePipUnchecked: toBounds=" + toBounds 580 + " duration=" + duration + " callers=\n" + Debug.getCallers(5, " ")); 581 } 582 583 // Intentionally resize here even if the current bounds match the destination bounds. 584 // This is so all the proper callbacks are performed. 585 mPipTaskOrganizer.scheduleAnimateResizePip(toBounds, duration, mUpdateBoundsCallback); 586 setAnimatingToBounds(toBounds); 587 } 588 589 /** 590 * Returns a MagnetizedObject wrapper for PIP's animated bounds. This is provided to the 591 * magnetic dismiss target so it can calculate PIP's size and position. 592 */ 593 MagnetizedObject<Rect> getMagnetizedPip() { 594 if (mMagnetizedPip == null) { 595 mMagnetizedPip = new MagnetizedObject<Rect>( 596 mContext, mTemporaryBounds, FloatProperties.RECT_X, FloatProperties.RECT_Y) { 597 @Override 598 public float getWidth(@NonNull Rect animatedPipBounds) { 599 return animatedPipBounds.width(); 600 } 601 602 @Override 603 public float getHeight(@NonNull Rect animatedPipBounds) { 604 return animatedPipBounds.height(); 605 } 606 607 @Override 608 public void getLocationOnScreen( 609 @NonNull Rect animatedPipBounds, @NonNull int[] loc) { 610 loc[0] = animatedPipBounds.left; 611 loc[1] = animatedPipBounds.top; 612 } 613 }; 614 } 615 616 return mMagnetizedPip; 617 } 618 619 public void dump(PrintWriter pw, String prefix) { 620 final String innerPrefix = prefix + " "; 621 pw.println(prefix + TAG); 622 pw.println(innerPrefix + "mBounds=" + mBounds); 623 } 624 } 625