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 static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 21 22 import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN; 23 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN; 24 import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN; 25 26 import android.animation.AnimationHandler; 27 import android.animation.Animator; 28 import android.animation.Animator.AnimatorListener; 29 import android.animation.AnimatorListenerAdapter; 30 import android.animation.RectEvaluator; 31 import android.animation.ValueAnimator; 32 import android.animation.ValueAnimator.AnimatorUpdateListener; 33 import android.app.ActivityManager.StackInfo; 34 import android.app.IActivityManager; 35 import android.app.IActivityTaskManager; 36 import android.content.Context; 37 import android.graphics.Point; 38 import android.graphics.PointF; 39 import android.graphics.Rect; 40 import android.os.Debug; 41 import android.os.Handler; 42 import android.os.Message; 43 import android.os.RemoteException; 44 import android.util.Log; 45 import android.view.animation.Interpolator; 46 47 import com.android.internal.graphics.SfVsyncFrameCallbackProvider; 48 import com.android.internal.os.SomeArgs; 49 import com.android.internal.policy.PipSnapAlgorithm; 50 import com.android.systemui.shared.system.WindowManagerWrapper; 51 import com.android.systemui.statusbar.FlingAnimationUtils; 52 53 import java.io.PrintWriter; 54 55 /** 56 * A helper to animate and manipulate the PiP. 57 */ 58 public class PipMotionHelper implements Handler.Callback, PipAppOpsListener.Callback { 59 60 private static final String TAG = "PipMotionHelper"; 61 private static final boolean DEBUG = false; 62 63 private static final RectEvaluator RECT_EVALUATOR = new RectEvaluator(new Rect()); 64 65 private static final int DEFAULT_MOVE_STACK_DURATION = 225; 66 private static final int SNAP_STACK_DURATION = 225; 67 private static final int DRAG_TO_TARGET_DISMISS_STACK_DURATION = 375; 68 private static final int DRAG_TO_DISMISS_STACK_DURATION = 175; 69 private static final int SHRINK_STACK_FROM_MENU_DURATION = 250; 70 private static final int EXPAND_STACK_TO_MENU_DURATION = 250; 71 private static final int EXPAND_STACK_TO_FULLSCREEN_DURATION = 300; 72 private static final int MINIMIZE_STACK_MAX_DURATION = 200; 73 private static final int SHIFT_DURATION = 300; 74 75 // The fraction of the stack width that the user has to drag offscreen to minimize the PiP 76 private static final float MINIMIZE_OFFSCREEN_FRACTION = 0.3f; 77 // The fraction of the stack height that the user has to drag offscreen to dismiss the PiP 78 private static final float DISMISS_OFFSCREEN_FRACTION = 0.3f; 79 80 private static final int MSG_RESIZE_IMMEDIATE = 1; 81 private static final int MSG_RESIZE_ANIMATE = 2; 82 private static final int MSG_OFFSET_ANIMATE = 3; 83 84 private Context mContext; 85 private IActivityManager mActivityManager; 86 private IActivityTaskManager mActivityTaskManager; 87 private Handler mHandler; 88 89 private PipMenuActivityController mMenuController; 90 private PipSnapAlgorithm mSnapAlgorithm; 91 private FlingAnimationUtils mFlingAnimationUtils; 92 private AnimationHandler mAnimationHandler; 93 94 private final Rect mBounds = new Rect(); 95 private final Rect mStableInsets = new Rect(); 96 97 private ValueAnimator mBoundsAnimator = null; 98 PipMotionHelper(Context context, IActivityManager activityManager, IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils)99 public PipMotionHelper(Context context, IActivityManager activityManager, 100 IActivityTaskManager activityTaskManager, PipMenuActivityController menuController, 101 PipSnapAlgorithm snapAlgorithm, FlingAnimationUtils flingAnimationUtils) { 102 mContext = context; 103 mHandler = new Handler(ForegroundThread.get().getLooper(), this); 104 mActivityManager = activityManager; 105 mActivityTaskManager = activityTaskManager; 106 mMenuController = menuController; 107 mSnapAlgorithm = snapAlgorithm; 108 mFlingAnimationUtils = flingAnimationUtils; 109 mAnimationHandler = new AnimationHandler(); 110 mAnimationHandler.setProvider(new SfVsyncFrameCallbackProvider()); 111 onConfigurationChanged(); 112 } 113 114 /** 115 * Updates whenever the configuration changes. 116 */ onConfigurationChanged()117 void onConfigurationChanged() { 118 mSnapAlgorithm.onConfigurationChanged(); 119 WindowManagerWrapper.getInstance().getStableInsets(mStableInsets); 120 } 121 122 /** 123 * Synchronizes the current bounds with the pinned stack. 124 */ synchronizePinnedStackBounds()125 void synchronizePinnedStackBounds() { 126 cancelAnimations(); 127 try { 128 StackInfo stackInfo = mActivityTaskManager.getStackInfo( 129 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); 130 if (stackInfo != null) { 131 mBounds.set(stackInfo.bounds); 132 } 133 } catch (RemoteException e) { 134 Log.w(TAG, "Failed to get pinned stack bounds"); 135 } 136 } 137 138 /** 139 * Tries to the move the pinned stack to the given {@param bounds}. 140 */ movePip(Rect toBounds)141 void movePip(Rect toBounds) { 142 cancelAnimations(); 143 resizePipUnchecked(toBounds); 144 mBounds.set(toBounds); 145 } 146 147 /** 148 * Resizes the pinned stack back to fullscreen. 149 */ expandPip()150 void expandPip() { 151 expandPip(false /* skipAnimation */); 152 } 153 154 /** 155 * Resizes the pinned stack back to fullscreen. 156 */ expandPip(boolean skipAnimation)157 void expandPip(boolean skipAnimation) { 158 if (DEBUG) { 159 Log.d(TAG, "expandPip: skipAnimation=" + skipAnimation 160 + " callers=\n" + Debug.getCallers(5, " ")); 161 } 162 cancelAnimations(); 163 mMenuController.hideMenuWithoutResize(); 164 mHandler.post(() -> { 165 try { 166 mActivityTaskManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION); 167 } catch (RemoteException e) { 168 Log.e(TAG, "Error expanding PiP activity", e); 169 } 170 }); 171 } 172 173 /** 174 * Dismisses the pinned stack. 175 */ 176 @Override dismissPip()177 public void dismissPip() { 178 if (DEBUG) { 179 Log.d(TAG, "dismissPip: callers=\n" + Debug.getCallers(5, " ")); 180 } 181 cancelAnimations(); 182 mMenuController.hideMenuWithoutResize(); 183 mHandler.post(() -> { 184 try { 185 mActivityTaskManager.removeStacksInWindowingModes( 186 new int[]{ WINDOWING_MODE_PINNED }); 187 } catch (RemoteException e) { 188 Log.e(TAG, "Failed to remove PiP", e); 189 } 190 }); 191 } 192 193 /** 194 * @return the PiP bounds. 195 */ getBounds()196 Rect getBounds() { 197 return mBounds; 198 } 199 200 /** 201 * @return the closest minimized PiP bounds. 202 */ getClosestMinimizedBounds(Rect stackBounds, Rect movementBounds)203 Rect getClosestMinimizedBounds(Rect stackBounds, Rect movementBounds) { 204 Point displaySize = new Point(); 205 mContext.getDisplay().getRealSize(displaySize); 206 Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, stackBounds); 207 mSnapAlgorithm.applyMinimizedOffset(toBounds, movementBounds, displaySize, mStableInsets); 208 return toBounds; 209 } 210 211 /** 212 * @return whether the PiP at the current bounds should be minimized. 213 */ shouldMinimizePip()214 boolean shouldMinimizePip() { 215 Point displaySize = new Point(); 216 mContext.getDisplay().getRealSize(displaySize); 217 if (mBounds.left < 0) { 218 float offscreenFraction = (float) -mBounds.left / mBounds.width(); 219 return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION; 220 } else if (mBounds.right > displaySize.x) { 221 float offscreenFraction = (float) (mBounds.right - displaySize.x) / 222 mBounds.width(); 223 return offscreenFraction >= MINIMIZE_OFFSCREEN_FRACTION; 224 } else { 225 return false; 226 } 227 } 228 229 /** 230 * @return whether the PiP at the current bounds should be dismissed. 231 */ shouldDismissPip()232 boolean shouldDismissPip() { 233 Point displaySize = new Point(); 234 mContext.getDisplay().getRealSize(displaySize); 235 final int y = displaySize.y - mStableInsets.bottom; 236 if (mBounds.bottom > y) { 237 float offscreenFraction = (float) (mBounds.bottom - y) / mBounds.height(); 238 return offscreenFraction >= DISMISS_OFFSCREEN_FRACTION; 239 } 240 return false; 241 } 242 243 /** 244 * Flings the minimized PiP to the closest minimized snap target. 245 */ flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition)246 Rect flingToMinimizedState(float velocityY, Rect movementBounds, Point dragStartPosition) { 247 cancelAnimations(); 248 // We currently only allow flinging the minimized stack up and down, so just lock the 249 // movement bounds to the current stack bounds horizontally 250 movementBounds = new Rect(mBounds.left, movementBounds.top, mBounds.left, 251 movementBounds.bottom); 252 Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds, 253 0 /* velocityX */, velocityY, dragStartPosition); 254 if (!mBounds.equals(toBounds)) { 255 mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN); 256 mFlingAnimationUtils.apply(mBoundsAnimator, 0, 257 distanceBetweenRectOffsets(mBounds, toBounds), 258 velocityY); 259 mBoundsAnimator.start(); 260 } 261 return toBounds; 262 } 263 264 /** 265 * Animates the PiP to the minimized state, slightly offscreen. 266 */ animateToClosestMinimizedState(Rect movementBounds, AnimatorUpdateListener updateListener)267 Rect animateToClosestMinimizedState(Rect movementBounds, 268 AnimatorUpdateListener updateListener) { 269 cancelAnimations(); 270 Rect toBounds = getClosestMinimizedBounds(mBounds, movementBounds); 271 if (!mBounds.equals(toBounds)) { 272 mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 273 MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN); 274 if (updateListener != null) { 275 mBoundsAnimator.addUpdateListener(updateListener); 276 } 277 mBoundsAnimator.start(); 278 } 279 return toBounds; 280 } 281 282 /** 283 * Flings the PiP to the closest snap target. 284 */ flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds, AnimatorUpdateListener updateListener, AnimatorListener listener, Point startPosition)285 Rect flingToSnapTarget(float velocity, float velocityX, float velocityY, Rect movementBounds, 286 AnimatorUpdateListener updateListener, AnimatorListener listener, 287 Point startPosition) { 288 cancelAnimations(); 289 Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds, 290 velocityX, velocityY, startPosition); 291 if (!mBounds.equals(toBounds)) { 292 mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, 0, FAST_OUT_SLOW_IN); 293 mFlingAnimationUtils.apply(mBoundsAnimator, 0, 294 distanceBetweenRectOffsets(mBounds, toBounds), 295 velocity); 296 if (updateListener != null) { 297 mBoundsAnimator.addUpdateListener(updateListener); 298 } 299 if (listener != null){ 300 mBoundsAnimator.addListener(listener); 301 } 302 mBoundsAnimator.start(); 303 } 304 return toBounds; 305 } 306 307 /** 308 * Animates the PiP to the closest snap target. 309 */ animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener, AnimatorListener listener)310 Rect animateToClosestSnapTarget(Rect movementBounds, AnimatorUpdateListener updateListener, 311 AnimatorListener listener) { 312 cancelAnimations(); 313 Rect toBounds = mSnapAlgorithm.findClosestSnapBounds(movementBounds, mBounds); 314 if (!mBounds.equals(toBounds)) { 315 mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, SNAP_STACK_DURATION, 316 FAST_OUT_SLOW_IN); 317 if (updateListener != null) { 318 mBoundsAnimator.addUpdateListener(updateListener); 319 } 320 if (listener != null){ 321 mBoundsAnimator.addListener(listener); 322 } 323 mBoundsAnimator.start(); 324 } 325 return toBounds; 326 } 327 328 /** 329 * Animates the PiP to the expanded state to show the menu. 330 */ animateToExpandedState(Rect expandedBounds, Rect movementBounds, Rect expandedMovementBounds)331 float animateToExpandedState(Rect expandedBounds, Rect movementBounds, 332 Rect expandedMovementBounds) { 333 float savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), movementBounds); 334 mSnapAlgorithm.applySnapFraction(expandedBounds, expandedMovementBounds, savedSnapFraction); 335 resizeAndAnimatePipUnchecked(expandedBounds, EXPAND_STACK_TO_MENU_DURATION); 336 return savedSnapFraction; 337 } 338 339 /** 340 * Animates the PiP from the expanded state to the normal state after the menu is hidden. 341 */ animateToUnexpandedState(Rect normalBounds, float savedSnapFraction, Rect normalMovementBounds, Rect currentMovementBounds, boolean minimized, boolean immediate)342 void animateToUnexpandedState(Rect normalBounds, float savedSnapFraction, 343 Rect normalMovementBounds, Rect currentMovementBounds, boolean minimized, 344 boolean immediate) { 345 if (savedSnapFraction < 0f) { 346 // If there are no saved snap fractions, then just use the current bounds 347 savedSnapFraction = mSnapAlgorithm.getSnapFraction(new Rect(mBounds), 348 currentMovementBounds); 349 } 350 mSnapAlgorithm.applySnapFraction(normalBounds, normalMovementBounds, savedSnapFraction); 351 if (minimized) { 352 normalBounds = getClosestMinimizedBounds(normalBounds, normalMovementBounds); 353 } 354 if (immediate) { 355 movePip(normalBounds); 356 } else { 357 resizeAndAnimatePipUnchecked(normalBounds, SHRINK_STACK_FROM_MENU_DURATION); 358 } 359 } 360 361 /** 362 * Animates the PiP to offset it from the IME or shelf. 363 */ animateToOffset(Rect originalBounds, int offset)364 void animateToOffset(Rect originalBounds, int offset) { 365 cancelAnimations(); 366 adjustAndAnimatePipOffset(originalBounds, offset, SHIFT_DURATION); 367 } 368 adjustAndAnimatePipOffset(Rect originalBounds, int offset, int duration)369 private void adjustAndAnimatePipOffset(Rect originalBounds, int offset, int duration) { 370 if (offset == 0) { 371 return; 372 } 373 SomeArgs args = SomeArgs.obtain(); 374 args.arg1 = originalBounds; 375 args.argi1 = offset; 376 args.argi2 = duration; 377 mHandler.sendMessage(mHandler.obtainMessage(MSG_OFFSET_ANIMATE, args)); 378 } 379 380 /** 381 * Animates the dismissal of the PiP off the edge of the screen. 382 */ animateDismiss(Rect pipBounds, float velocityX, float velocityY, AnimatorUpdateListener listener)383 Rect animateDismiss(Rect pipBounds, float velocityX, float velocityY, 384 AnimatorUpdateListener listener) { 385 cancelAnimations(); 386 final float velocity = PointF.length(velocityX, velocityY); 387 final boolean isFling = velocity > mFlingAnimationUtils.getMinVelocityPxPerSecond(); 388 Point p = getDismissEndPoint(pipBounds, velocityX, velocityY, isFling); 389 Rect toBounds = new Rect(pipBounds); 390 toBounds.offsetTo(p.x, p.y); 391 mBoundsAnimator = createAnimationToBounds(mBounds, toBounds, DRAG_TO_DISMISS_STACK_DURATION, 392 FAST_OUT_LINEAR_IN); 393 mBoundsAnimator.addListener(new AnimatorListenerAdapter() { 394 @Override 395 public void onAnimationEnd(Animator animation) { 396 dismissPip(); 397 } 398 }); 399 if (isFling) { 400 mFlingAnimationUtils.apply(mBoundsAnimator, 0, 401 distanceBetweenRectOffsets(mBounds, toBounds), velocity); 402 } 403 if (listener != null) { 404 mBoundsAnimator.addUpdateListener(listener); 405 } 406 mBoundsAnimator.start(); 407 return toBounds; 408 } 409 410 /** 411 * Cancels all existing animations. 412 */ cancelAnimations()413 void cancelAnimations() { 414 if (mBoundsAnimator != null) { 415 mBoundsAnimator.cancel(); 416 mBoundsAnimator = null; 417 } 418 } 419 420 /** 421 * Creates an animation to move the PiP to give given {@param toBounds}. 422 */ createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration, Interpolator interpolator)423 private ValueAnimator createAnimationToBounds(Rect fromBounds, Rect toBounds, int duration, 424 Interpolator interpolator) { 425 ValueAnimator anim = new ValueAnimator() { 426 @Override 427 public AnimationHandler getAnimationHandler() { 428 return mAnimationHandler; 429 } 430 }; 431 anim.setObjectValues(fromBounds, toBounds); 432 anim.setEvaluator(RECT_EVALUATOR); 433 anim.setDuration(duration); 434 anim.setInterpolator(interpolator); 435 anim.addUpdateListener((ValueAnimator animation) -> { 436 resizePipUnchecked((Rect) animation.getAnimatedValue()); 437 }); 438 return anim; 439 } 440 441 /** 442 * Directly resizes the PiP to the given {@param bounds}. 443 */ resizePipUnchecked(Rect toBounds)444 private void resizePipUnchecked(Rect toBounds) { 445 if (DEBUG) { 446 Log.d(TAG, "resizePipUnchecked: toBounds=" + toBounds 447 + " callers=\n" + Debug.getCallers(5, " ")); 448 } 449 if (!toBounds.equals(mBounds)) { 450 SomeArgs args = SomeArgs.obtain(); 451 args.arg1 = toBounds; 452 mHandler.sendMessage(mHandler.obtainMessage(MSG_RESIZE_IMMEDIATE, args)); 453 } 454 } 455 456 /** 457 * Directly resizes the PiP to the given {@param bounds}. 458 */ resizeAndAnimatePipUnchecked(Rect toBounds, int duration)459 private void resizeAndAnimatePipUnchecked(Rect toBounds, int duration) { 460 if (DEBUG) { 461 Log.d(TAG, "resizeAndAnimatePipUnchecked: toBounds=" + toBounds 462 + " duration=" + duration + " callers=\n" + Debug.getCallers(5, " ")); 463 } 464 if (!toBounds.equals(mBounds)) { 465 SomeArgs args = SomeArgs.obtain(); 466 args.arg1 = toBounds; 467 args.argi1 = duration; 468 mHandler.sendMessage(mHandler.obtainMessage(MSG_RESIZE_ANIMATE, args)); 469 } 470 } 471 472 /** 473 * @return the coordinates the PIP should animate to based on the direction of velocity when 474 * dismissing. 475 */ getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling)476 private Point getDismissEndPoint(Rect pipBounds, float velX, float velY, boolean isFling) { 477 Point displaySize = new Point(); 478 mContext.getDisplay().getRealSize(displaySize); 479 final float bottomBound = displaySize.y + pipBounds.height() * .1f; 480 if (isFling && velX != 0 && velY != 0) { 481 // Line is defined by: y = mx + b, m = slope, b = y-intercept 482 // Find the slope 483 final float slope = velY / velX; 484 // Sub in slope and PiP position to solve for y-intercept: b = y - mx 485 final float yIntercept = pipBounds.top - slope * pipBounds.left; 486 // Now find the point on this line when y = bottom bound: x = (y - b) / m 487 final float x = (bottomBound - yIntercept) / slope; 488 return new Point((int) x, (int) bottomBound); 489 } else { 490 // If it wasn't a fling the velocity on 'up' is not reliable for direction of movement, 491 // just animate downwards. 492 return new Point(pipBounds.left, (int) bottomBound); 493 } 494 } 495 496 /** 497 * @return whether the gesture it towards the dismiss area based on the velocity when 498 * dismissing. 499 */ isGestureToDismissArea(Rect pipBounds, float velX, float velY, boolean isFling)500 public boolean isGestureToDismissArea(Rect pipBounds, float velX, float velY, 501 boolean isFling) { 502 Point endpoint = getDismissEndPoint(pipBounds, velX, velY, isFling); 503 // Center the point 504 endpoint.x += pipBounds.width() / 2; 505 endpoint.y += pipBounds.height() / 2; 506 507 // The dismiss area is the middle third of the screen, half the PIP's height from the bottom 508 Point size = new Point(); 509 mContext.getDisplay().getRealSize(size); 510 final int left = size.x / 3; 511 Rect dismissArea = new Rect(left, size.y - (pipBounds.height() / 2), left * 2, 512 size.y + pipBounds.height()); 513 return dismissArea.contains(endpoint.x, endpoint.y); 514 } 515 516 /** 517 * @return the distance between points {@param p1} and {@param p2}. 518 */ distanceBetweenRectOffsets(Rect r1, Rect r2)519 private float distanceBetweenRectOffsets(Rect r1, Rect r2) { 520 return PointF.length(r1.left - r2.left, r1.top - r2.top); 521 } 522 523 /** 524 * Handles messages to be processed on the background thread. 525 */ handleMessage(Message msg)526 public boolean handleMessage(Message msg) { 527 switch (msg.what) { 528 case MSG_RESIZE_IMMEDIATE: { 529 SomeArgs args = (SomeArgs) msg.obj; 530 Rect toBounds = (Rect) args.arg1; 531 try { 532 mActivityTaskManager.resizePinnedStack( 533 toBounds, null /* tempPinnedTaskBounds */); 534 mBounds.set(toBounds); 535 } catch (RemoteException e) { 536 Log.e(TAG, "Could not resize pinned stack to bounds: " + toBounds, e); 537 } 538 return true; 539 } 540 541 case MSG_RESIZE_ANIMATE: { 542 SomeArgs args = (SomeArgs) msg.obj; 543 Rect toBounds = (Rect) args.arg1; 544 int duration = args.argi1; 545 try { 546 StackInfo stackInfo = mActivityTaskManager.getStackInfo( 547 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); 548 if (stackInfo == null) { 549 // In the case where we've already re-expanded or dismissed the PiP, then 550 // just skip the resize 551 return true; 552 } 553 554 mActivityTaskManager.resizeStack(stackInfo.stackId, toBounds, 555 false /* allowResizeInDockedMode */, true /* preserveWindows */, 556 true /* animate */, duration); 557 mBounds.set(toBounds); 558 } catch (RemoteException e) { 559 Log.e(TAG, "Could not animate resize pinned stack to bounds: " + toBounds, e); 560 } 561 return true; 562 } 563 564 case MSG_OFFSET_ANIMATE: { 565 SomeArgs args = (SomeArgs) msg.obj; 566 Rect originalBounds = (Rect) args.arg1; 567 final int offset = args.argi1; 568 final int duration = args.argi2; 569 try { 570 StackInfo stackInfo = mActivityTaskManager.getStackInfo( 571 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED); 572 if (stackInfo == null) { 573 // In the case where we've already re-expanded or dismissed the PiP, then 574 // just skip the resize 575 return true; 576 } 577 578 mActivityTaskManager.offsetPinnedStackBounds(stackInfo.stackId, originalBounds, 579 0/* xOffset */, offset, duration); 580 Rect toBounds = new Rect(originalBounds); 581 toBounds.offset(0, offset); 582 mBounds.set(toBounds); 583 } catch (RemoteException e) { 584 Log.e(TAG, "Could not animate offset pinned stack with offset: " + offset, e); 585 } 586 return true; 587 } 588 589 default: 590 return false; 591 } 592 } 593 dump(PrintWriter pw, String prefix)594 public void dump(PrintWriter pw, String prefix) { 595 final String innerPrefix = prefix + " "; 596 pw.println(prefix + TAG); 597 pw.println(innerPrefix + "mBounds=" + mBounds); 598 pw.println(innerPrefix + "mStableInsets=" + mStableInsets); 599 } 600 } 601