1 /* 2 * Copyright (C) 2023 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 package com.android.wm.shell.bubbles.bar; 17 18 import static android.view.View.ALPHA; 19 import static android.view.View.INVISIBLE; 20 import static android.view.View.SCALE_X; 21 import static android.view.View.SCALE_Y; 22 import static android.view.View.TRANSLATION_X; 23 import static android.view.View.TRANSLATION_Y; 24 import static android.view.View.VISIBLE; 25 import static android.view.View.X; 26 import static android.view.View.Y; 27 28 import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS; 29 import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.TASK_VIEW_ALPHA; 30 import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED; 31 import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE; 32 33 import static java.lang.Math.max; 34 35 import android.animation.Animator; 36 import android.animation.AnimatorListenerAdapter; 37 import android.animation.AnimatorSet; 38 import android.animation.ObjectAnimator; 39 import android.annotation.NonNull; 40 import android.content.Context; 41 import android.graphics.Point; 42 import android.graphics.Rect; 43 import android.util.Log; 44 import android.util.Size; 45 import android.view.SurfaceControl; 46 import android.widget.FrameLayout; 47 48 import androidx.annotation.Nullable; 49 50 import com.android.app.animation.Interpolators; 51 import com.android.wm.shell.R; 52 import com.android.wm.shell.animation.SizeChangeAnimation; 53 import com.android.wm.shell.bubbles.Bubble; 54 import com.android.wm.shell.bubbles.BubbleOverflow; 55 import com.android.wm.shell.bubbles.BubblePositioner; 56 import com.android.wm.shell.bubbles.BubbleViewProvider; 57 import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; 58 import com.android.wm.shell.shared.animation.PhysicsAnimator; 59 import com.android.wm.shell.shared.magnetictarget.MagnetizedObject.MagneticTarget; 60 61 /** 62 * Helper class to animate a {@link BubbleBarExpandedView} on a bubble. 63 */ 64 public class BubbleBarAnimationHelper { 65 66 private static final String TAG = BubbleBarAnimationHelper.class.getSimpleName(); 67 68 private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f; 69 private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f; 70 private static final int EXPANDED_VIEW_EXPAND_ALPHA_DURATION = 150; 71 private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 400; 72 private static final int EXPANDED_VIEW_ANIMATE_TO_REST_DURATION = 400; 73 private static final int EXPANDED_VIEW_DISMISS_DURATION = 250; 74 private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 400; 75 /** 76 * Additional scale applied to expanded view when it is positioned inside a magnetic target. 77 */ 78 private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.2f; 79 private static final float EXPANDED_VIEW_DRAG_SCALE = 0.4f; 80 private static final float DISMISS_VIEW_SCALE = 1.25f; 81 private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100; 82 83 private static final float SWITCH_OUT_SCALE = 0.97f; 84 private static final long SWITCH_OUT_SCALE_DURATION = 200L; 85 private static final long SWITCH_OUT_ALPHA_DURATION = 100L; 86 private static final long SWITCH_OUT_HANDLE_ALPHA_DURATION = 50L; 87 private static final long SWITCH_IN_ANIM_DELAY = 50L; 88 private static final long SWITCH_IN_TX_DURATION = 350L; 89 private static final long SWITCH_IN_ALPHA_DURATION = 50L; 90 // Keep this handle alpha delay at least as long as alpha animation for both expanded views. 91 private static final long SWITCH_IN_HANDLE_ALPHA_DELAY = 150L; 92 private static final long SWITCH_IN_HANDLE_ALPHA_DURATION = 100L; 93 94 /** Spring config for the expanded view scale-in animation. */ 95 private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = 96 new PhysicsAnimator.SpringConfig(300f, 0.9f); 97 98 /** Spring config for the expanded view scale-out animation. */ 99 private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig = 100 new PhysicsAnimator.SpringConfig(900f, 1f); 101 102 private final int mSwitchAnimPositionOffset; 103 104 /** Matrix used to scale the expanded view container with a given pivot point. */ 105 private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix(); 106 107 @Nullable 108 private Animator mRunningAnimator; 109 110 private final BubblePositioner mPositioner; 111 private final int[] mTmpLocation = new int[2]; 112 113 // TODO(b/381936992): remove expanded bubble state from this helper class 114 private BubbleViewProvider mExpandedBubble; 115 BubbleBarAnimationHelper(Context context, BubblePositioner positioner)116 public BubbleBarAnimationHelper(Context context, BubblePositioner positioner) { 117 mPositioner = positioner; 118 mSwitchAnimPositionOffset = context.getResources().getDimensionPixelSize( 119 R.dimen.bubble_bar_expanded_view_switch_offset); 120 } 121 122 /** 123 * Animates the provided bubble's expanded view to the expanded state. 124 */ animateExpansion(BubbleViewProvider expandedBubble, @Nullable Runnable afterAnimation)125 public void animateExpansion(BubbleViewProvider expandedBubble, 126 @Nullable Runnable afterAnimation) { 127 mExpandedBubble = expandedBubble; 128 final BubbleBarExpandedView bbev = getExpandedView(); 129 if (bbev == null) { 130 return; 131 } 132 133 mExpandedViewContainerMatrix.setScaleX(0f); 134 mExpandedViewContainerMatrix.setScaleY(0f); 135 136 prepareForAnimateIn(bbev); 137 138 setScaleFromBubbleBar(mExpandedViewContainerMatrix, 139 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT); 140 141 bbev.setAnimationMatrix(mExpandedViewContainerMatrix); 142 143 bbev.animateExpansionWhenTaskViewVisible(() -> { 144 ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ true); 145 alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION); 146 alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); 147 alphaAnim.addListener(new AnimatorListenerAdapter() { 148 @Override 149 public void onAnimationEnd(Animator animation) { 150 bbev.setAnimating(false); 151 } 152 }); 153 startNewAnimator(alphaAnim); 154 155 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); 156 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) 157 .spring(AnimatableScaleMatrix.SCALE_X, 158 AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), 159 mScaleInSpringConfig) 160 .spring(AnimatableScaleMatrix.SCALE_Y, 161 AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), 162 mScaleInSpringConfig) 163 .addUpdateListener((target, values) -> { 164 bbev.setAnimationMatrix(mExpandedViewContainerMatrix); 165 }) 166 .withEndActions(() -> { 167 bbev.setAnimationMatrix(null); 168 updateExpandedView(bbev); 169 if (afterAnimation != null) { 170 afterAnimation.run(); 171 } 172 }) 173 .start(); 174 }); 175 } 176 prepareForAnimateIn(BubbleBarExpandedView bbev)177 private void prepareForAnimateIn(BubbleBarExpandedView bbev) { 178 bbev.setAnimating(true); 179 updateExpandedView(bbev); 180 // We need to be Z ordered on top in order for taskView alpha to work. 181 // It is also set when the alpha animation starts, but needs to be set here to too avoid 182 // flickers. 183 bbev.setSurfaceZOrderedOnTop(true); 184 bbev.setTaskViewAlpha(0f); 185 bbev.setContentVisibility(false); 186 bbev.setVisibility(VISIBLE); 187 } 188 189 /** 190 * Collapses the currently expanded bubble. 191 * 192 * @param endRunnable a runnable to run at the end of the animation. 193 */ animateCollapse(Runnable endRunnable)194 public void animateCollapse(Runnable endRunnable) { 195 final BubbleBarExpandedView bbev = getExpandedView(); 196 if (bbev == null) { 197 Log.w(TAG, "Trying to animate collapse without a bubble"); 198 return; 199 } 200 bbev.setScaleX(1f); 201 bbev.setScaleY(1f); 202 203 setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f); 204 205 bbev.setAnimating(true); 206 207 ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false); 208 alphaAnim.setDuration(EXPANDED_VIEW_EXPAND_ALPHA_DURATION); 209 alphaAnim.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); 210 alphaAnim.addListener(new AnimatorListenerAdapter() { 211 @Override 212 public void onAnimationEnd(Animator animation) { 213 bbev.setAnimating(false); 214 } 215 }); 216 startNewAnimator(alphaAnim); 217 218 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); 219 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) 220 .spring(AnimatableScaleMatrix.SCALE_X, 221 AnimatableScaleMatrix.getAnimatableValueForScaleFactor( 222 EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), 223 mScaleOutSpringConfig) 224 .spring(AnimatableScaleMatrix.SCALE_Y, 225 AnimatableScaleMatrix.getAnimatableValueForScaleFactor( 226 EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), 227 mScaleOutSpringConfig) 228 .addUpdateListener((target, values) -> { 229 bbev.setAnimationMatrix(mExpandedViewContainerMatrix); 230 }) 231 .withEndActions(() -> { 232 bbev.setAnimationMatrix(null); 233 if (endRunnable != null) { 234 endRunnable.run(); 235 } 236 }) 237 .start(); 238 } 239 setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale)240 private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) { 241 // Set the pivot point for the scale, so the view animates out from the bubble bar. 242 Rect availableRect = mPositioner.getAvailableRect(); 243 float pivotX = mPositioner.isBubbleBarOnLeft() ? availableRect.left : availableRect.right; 244 float pivotY = mPositioner.getBubbleBarTopOnScreen(); 245 matrix.setScale(scale, scale, pivotX, pivotY); 246 } 247 248 /** 249 * Animate between two bubble views using a switch animation 250 * 251 * @param fromBubble bubble to hide 252 * @param toBubble bubble to show 253 * @param afterAnimation optional runnable after animation finishes 254 */ animateSwitch(BubbleViewProvider fromBubble, BubbleViewProvider toBubble, @Nullable Runnable afterAnimation)255 public void animateSwitch(BubbleViewProvider fromBubble, BubbleViewProvider toBubble, 256 @Nullable Runnable afterAnimation) { 257 /* 258 * Switch animation 259 * 260 * |.....................fromBubble scale to 0.97.....................| 261 * |fromBubble handle alpha 0|....fromBubble alpha to 0.....| | 262 * 0-------------------------50-----------------------100---150--------200----------250--400 263 * |..toBubble alpha to 1...| |toBubble handle alpha 1| | 264 * |................toBubble position +/-48 to 0...............| 265 */ 266 267 mExpandedBubble = toBubble; 268 final BubbleBarExpandedView toBbev = toBubble.getBubbleBarExpandedView(); 269 final BubbleBarExpandedView fromBbev = fromBubble.getBubbleBarExpandedView(); 270 if (toBbev == null || fromBbev == null) { 271 return; 272 } 273 274 fromBbev.setAnimating(true); 275 276 prepareForAnimateIn(toBbev); 277 final float endTx = toBbev.getTranslationX(); 278 final float startTx = getSwitchAnimationInitialTx(endTx); 279 toBbev.setTranslationX(startTx); 280 toBbev.getHandleView().setAlpha(0f); 281 toBbev.getHandleView().setHandleInitialColor(fromBbev.getHandleView().getHandleColor()); 282 283 toBbev.animateExpansionWhenTaskViewVisible(() -> { 284 AnimatorSet switchAnim = new AnimatorSet(); 285 switchAnim.playTogether(switchOutAnimator(fromBbev), switchInAnimator(toBbev, endTx)); 286 287 switchAnim.addListener(new AnimatorListenerAdapter() { 288 @Override 289 public void onAnimationEnd(Animator animation) { 290 if (afterAnimation != null) { 291 afterAnimation.run(); 292 } 293 } 294 }); 295 startNewAnimator(switchAnim); 296 }); 297 } 298 getSwitchAnimationInitialTx(float endTx)299 private float getSwitchAnimationInitialTx(float endTx) { 300 if (mPositioner.isBubbleBarOnLeft()) { 301 return endTx - mSwitchAnimPositionOffset; 302 } else { 303 return endTx + mSwitchAnimPositionOffset; 304 } 305 } 306 switchOutAnimator(BubbleBarExpandedView bbev)307 private Animator switchOutAnimator(BubbleBarExpandedView bbev) { 308 setPivotToCenter(bbev); 309 AnimatorSet scaleAnim = new AnimatorSet(); 310 scaleAnim.playTogether( 311 ObjectAnimator.ofFloat(bbev, SCALE_X, SWITCH_OUT_SCALE), 312 ObjectAnimator.ofFloat(bbev, SCALE_Y, SWITCH_OUT_SCALE) 313 ); 314 scaleAnim.setInterpolator(Interpolators.ACCELERATE); 315 scaleAnim.setDuration(SWITCH_OUT_SCALE_DURATION); 316 317 ObjectAnimator alphaAnim = createAlphaAnimator(bbev, /* visible= */ false); 318 alphaAnim.setStartDelay(SWITCH_OUT_HANDLE_ALPHA_DURATION); 319 alphaAnim.setDuration(SWITCH_OUT_ALPHA_DURATION); 320 321 ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f) 322 .setDuration(SWITCH_OUT_HANDLE_ALPHA_DURATION); 323 324 AnimatorSet animator = new AnimatorSet(); 325 animator.playTogether(scaleAnim, alphaAnim, handleAlphaAnim); 326 327 animator.addListener(new AnimatorListenerAdapter() { 328 @Override 329 public void onAnimationEnd(Animator animation) { 330 bbev.setAnimating(false); 331 } 332 }); 333 return animator; 334 } 335 switchInAnimator(BubbleBarExpandedView bbev, float restingTx)336 private Animator switchInAnimator(BubbleBarExpandedView bbev, float restingTx) { 337 ObjectAnimator positionAnim = ObjectAnimator.ofFloat(bbev, TRANSLATION_X, restingTx); 338 positionAnim.setInterpolator(Interpolators.EMPHASIZED_DECELERATE); 339 positionAnim.setStartDelay(SWITCH_IN_ANIM_DELAY); 340 positionAnim.setDuration(SWITCH_IN_TX_DURATION); 341 342 // Animate alpha directly to have finer control over surface z-ordering 343 ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(bbev, TASK_VIEW_ALPHA, 1f); 344 alphaAnim.setStartDelay(SWITCH_IN_ANIM_DELAY); 345 alphaAnim.setDuration(SWITCH_IN_ALPHA_DURATION); 346 alphaAnim.addListener(new AnimatorListenerAdapter() { 347 @Override 348 public void onAnimationStart(Animator animation) { 349 bbev.setSurfaceZOrderedOnTop(true); 350 } 351 352 @Override 353 public void onAnimationEnd(Animator animation) { 354 bbev.setContentVisibility(true); 355 // The outgoing expanded view alpha animation is still in progress. 356 // Do not reset the surface z-order as otherwise the outgoing expanded view is 357 // placed on top. 358 } 359 }); 360 361 ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f); 362 handleAlphaAnim.setStartDelay(SWITCH_IN_HANDLE_ALPHA_DELAY); 363 handleAlphaAnim.setDuration(SWITCH_IN_HANDLE_ALPHA_DURATION); 364 handleAlphaAnim.addListener(new AnimatorListenerAdapter() { 365 @Override 366 public void onAnimationStart(Animator animation) { 367 bbev.setSurfaceZOrderedOnTop(false); 368 bbev.setAnimating(false); 369 } 370 }); 371 372 AnimatorSet animator = new AnimatorSet(); 373 animator.playTogether(positionAnim, alphaAnim, handleAlphaAnim); 374 375 animator.addListener(new AnimatorListenerAdapter() { 376 @Override 377 public void onAnimationEnd(Animator animation) { 378 updateExpandedView(bbev); 379 } 380 }); 381 return animator; 382 } 383 384 /** 385 * Animate the expanded bubble when it is being dragged 386 */ animateStartDrag()387 public void animateStartDrag() { 388 final BubbleBarExpandedView bbev = getExpandedView(); 389 if (bbev == null) { 390 Log.w(TAG, "Trying to animate start drag without a bubble"); 391 return; 392 } 393 setDragPivot(bbev); 394 bbev.setDragging(true); 395 // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius 396 final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE; 397 398 AnimatorSet contentAnim = new AnimatorSet(); 399 contentAnim.playTogether( 400 ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE), 401 ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE), 402 ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius) 403 ); 404 contentAnim.setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION).setInterpolator(EMPHASIZED); 405 406 ObjectAnimator handleAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f) 407 .setDuration(HANDLE_ALPHA_ANIMATION_DURATION); 408 409 AnimatorSet animatorSet = new AnimatorSet(); 410 animatorSet.playTogether(contentAnim, handleAnim); 411 animatorSet.addListener(new DragAnimatorListenerAdapter(bbev)); 412 startNewAnimator(animatorSet); 413 } 414 415 /** 416 * Animates dismissal of currently expanded bubble 417 * 418 * @param endRunnable a runnable to run at the end of the animation 419 */ animateDismiss(Runnable endRunnable)420 public void animateDismiss(Runnable endRunnable) { 421 final BubbleBarExpandedView bbev = getExpandedView(); 422 if (bbev == null) { 423 Log.w(TAG, "Trying to animate dismiss without a bubble"); 424 return; 425 } 426 427 int[] location = bbev.getLocationOnScreen(); 428 int diffFromBottom = mPositioner.getScreenRect().bottom - location[1]; 429 430 cancelAnimations(); 431 bbev.animate() 432 // 2x distance from bottom so the view flies out 433 .translationYBy(diffFromBottom * 2) 434 .setDuration(EXPANDED_VIEW_DISMISS_DURATION) 435 .withEndAction(endRunnable) 436 .start(); 437 } 438 439 /** 440 * Animate current expanded bubble back to its rest position 441 */ animateToRestPosition()442 public void animateToRestPosition() { 443 BubbleBarExpandedView bbev = getExpandedView(); 444 if (bbev == null) { 445 Log.w(TAG, "Trying to animate expanded view to rest position without a bubble"); 446 return; 447 } 448 Point restPoint = getExpandedViewRestPosition(getExpandedViewSize()); 449 450 AnimatorSet contentAnim = new AnimatorSet(); 451 contentAnim.playTogether( 452 ObjectAnimator.ofFloat(bbev, X, restPoint.x), 453 ObjectAnimator.ofFloat(bbev, Y, restPoint.y), 454 ObjectAnimator.ofFloat(bbev, SCALE_X, 1f), 455 ObjectAnimator.ofFloat(bbev, SCALE_Y, 1f), 456 ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, bbev.getRestingCornerRadius()) 457 ); 458 contentAnim.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION).setInterpolator(EMPHASIZED); 459 460 ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f) 461 .setDuration(HANDLE_ALPHA_ANIMATION_DURATION); 462 463 AnimatorSet animatorSet = new AnimatorSet(); 464 animatorSet.playTogether(contentAnim, handleAlphaAnim); 465 animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) { 466 @Override 467 public void onAnimationEnd(Animator animation) { 468 super.onAnimationEnd(animation); 469 bbev.resetPivot(); 470 bbev.setDragging(false); 471 updateExpandedView(bbev); 472 } 473 }); 474 startNewAnimator(animatorSet); 475 } 476 477 /** 478 * Animates currently expanded bubble into the given {@link MagneticTarget}. 479 * 480 * @param target magnetic target to snap to 481 * @param endRunnable a runnable to run at the end of the animation 482 */ animateIntoTarget(MagneticTarget target, @Nullable Runnable endRunnable)483 public void animateIntoTarget(MagneticTarget target, @Nullable Runnable endRunnable) { 484 BubbleBarExpandedView bbev = getExpandedView(); 485 if (bbev == null) { 486 Log.w(TAG, "Trying to snap the expanded view to target without a bubble"); 487 return; 488 } 489 490 setDragPivot(bbev); 491 492 // When the view animates into the target, it is scaled down with the pivot at center top. 493 // Find the point on the view that would be the center of the view at its final scale. 494 // Once we know that, we can calculate x and y distance from the center of the target view 495 // and use that for the translation animation to ensure that the view at final scale is 496 // placed at the center of the target. 497 498 // Set mTmpLocation to the current location of the view on the screen, taking into account 499 // any scale applied. 500 bbev.getLocationOnScreen(mTmpLocation); 501 // Since pivotX is at the center of the x-axis, even at final scale, center of the view on 502 // x-axis will be the same as the center of the view at current size. 503 // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the 504 // center of the view at its current size. 505 float currentWidth = bbev.getWidth() * bbev.getScaleX(); 506 mTmpLocation[0] += (int) (currentWidth / 2f); 507 // Since pivotY is at the top of the view, at final scale, top coordinate of the view 508 // remains the same. 509 // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is 510 // moved down by half of the height at final scale. 511 float targetHeight = bbev.getHeight() * EXPANDED_VIEW_IN_TARGET_SCALE; 512 mTmpLocation[1] += (int) (targetHeight / 2f); 513 // mTmpLocation is now set to the point on the view that will be the center of the view once 514 // scale is applied. 515 516 // Calculate the difference between the target's center coordinates and mTmpLocation 517 float xDiff = target.getCenterOnScreen().x - mTmpLocation[0]; 518 float yDiff = target.getCenterOnScreen().y - mTmpLocation[1]; 519 520 // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius 521 final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_IN_TARGET_SCALE; 522 523 AnimatorSet animatorSet = new AnimatorSet(); 524 animatorSet.playTogether( 525 // Move expanded view to the center of dismiss view 526 ObjectAnimator.ofFloat(bbev, TRANSLATION_X, bbev.getTranslationX() + xDiff), 527 ObjectAnimator.ofFloat(bbev, TRANSLATION_Y, bbev.getTranslationY() + yDiff), 528 // Scale expanded view down 529 ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_IN_TARGET_SCALE), 530 ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_IN_TARGET_SCALE), 531 // Update corner radius for expanded view 532 ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius), 533 // Scale dismiss view up 534 ObjectAnimator.ofFloat(target.getTargetView(), SCALE_X, DISMISS_VIEW_SCALE), 535 ObjectAnimator.ofFloat(target.getTargetView(), SCALE_Y, DISMISS_VIEW_SCALE) 536 ); 537 animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator( 538 EMPHASIZED_DECELERATE); 539 animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) { 540 @Override 541 public void onAnimationEnd(Animator animation) { 542 super.onAnimationEnd(animation); 543 if (endRunnable != null) { 544 endRunnable.run(); 545 } 546 } 547 }); 548 startNewAnimator(animatorSet); 549 } 550 551 /** 552 * Animate currently expanded view when it is released from dismiss view 553 */ animateUnstuckFromDismissView(MagneticTarget target)554 public void animateUnstuckFromDismissView(MagneticTarget target) { 555 BubbleBarExpandedView bbev = getExpandedView(); 556 if (bbev == null) { 557 Log.w(TAG, "Trying to unsnap the expanded view from dismiss without a bubble"); 558 return; 559 } 560 setDragPivot(bbev); 561 // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius 562 final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE; 563 AnimatorSet animatorSet = new AnimatorSet(); 564 animatorSet.playTogether( 565 ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE), 566 ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE), 567 ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius), 568 ObjectAnimator.ofFloat(target.getTargetView(), SCALE_X, 1f), 569 ObjectAnimator.ofFloat(target.getTargetView(), SCALE_Y, 1f) 570 ); 571 animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator( 572 EMPHASIZED_DECELERATE); 573 animatorSet.addListener(new DragAnimatorListenerAdapter(bbev)); 574 startNewAnimator(animatorSet); 575 } 576 577 /** 578 * Animates converting of a non-bubble task into an expanded bubble view. 579 */ animateConvert(BubbleViewProvider expandedBubble, @NonNull SurfaceControl.Transaction startT, @NonNull Rect origBounds, float origScale, @NonNull SurfaceControl snapshot, @NonNull SurfaceControl taskLeash, @Nullable Runnable afterAnimation)580 public void animateConvert(BubbleViewProvider expandedBubble, 581 @NonNull SurfaceControl.Transaction startT, 582 @NonNull Rect origBounds, 583 float origScale, 584 @NonNull SurfaceControl snapshot, 585 @NonNull SurfaceControl taskLeash, 586 @Nullable Runnable afterAnimation) { 587 mExpandedBubble = expandedBubble; 588 final BubbleBarExpandedView bbev = getExpandedView(); 589 if (bbev == null) { 590 return; 591 } 592 593 bbev.setTaskViewAlpha(1f); 594 SurfaceControl tvSf = ((Bubble) mExpandedBubble).getTaskView().getSurfaceControl(); 595 596 final Size size = getExpandedViewSize(); 597 Point position = getExpandedViewRestPosition(size); 598 599 Rect startBounds = new Rect(origBounds.left - position.x, origBounds.top - position.y, 600 origBounds.right - position.x, origBounds.bottom - position.y); 601 Rect endBounds = new Rect(0, 0, size.getWidth(), size.getHeight()); 602 final SizeChangeAnimation sca = new SizeChangeAnimation(startBounds, endBounds, 603 origScale, /* scaleFactor= */ 1f); 604 sca.initialize(bbev, taskLeash, snapshot, startT); 605 606 Animator a = sca.buildViewAnimator(bbev, tvSf, snapshot, /* onFinish */ (va) -> { 607 updateExpandedView(bbev); 608 snapshot.release(); 609 bbev.setSurfaceZOrderedOnTop(false); 610 bbev.setAnimating(false); 611 if (afterAnimation != null) { 612 afterAnimation.run(); 613 } 614 }); 615 616 bbev.setSurfaceZOrderedOnTop(true); 617 a.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION); 618 a.setInterpolator(EMPHASIZED); 619 a.start(); 620 } 621 622 /** 623 * Cancel current animations 624 */ cancelAnimations()625 public void cancelAnimations() { 626 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); 627 BubbleBarExpandedView bbev = getExpandedView(); 628 if (bbev != null) { 629 bbev.animate().cancel(); 630 } 631 if (mRunningAnimator != null) { 632 if (mRunningAnimator.isRunning()) { 633 mRunningAnimator.cancel(); 634 } 635 mRunningAnimator = null; 636 } 637 } 638 639 /** Handles IME position changes. */ onImeTopChanged(int imeTop)640 public void onImeTopChanged(int imeTop) { 641 BubbleBarExpandedView bbev = getExpandedView(); 642 if (bbev == null) { 643 Log.w(TAG, "Bubble bar expanded view was null when IME top changed"); 644 return; 645 } 646 int bbevBottom = bbev.getContentBottomOnScreen(); 647 int clip = max(bbevBottom - imeTop, 0); 648 bbev.updateBottomClip(clip); 649 } 650 getExpandedView()651 private @Nullable BubbleBarExpandedView getExpandedView() { 652 BubbleViewProvider bubble = mExpandedBubble; 653 if (bubble != null) { 654 return bubble.getBubbleBarExpandedView(); 655 } 656 return null; 657 } 658 updateExpandedView(BubbleBarExpandedView bbev)659 private void updateExpandedView(BubbleBarExpandedView bbev) { 660 if (bbev == null) { 661 Log.w(TAG, "Trying to update the expanded view without a bubble"); 662 return; 663 } 664 665 final Size size = getExpandedViewSize(); 666 Point position = getExpandedViewRestPosition(size); 667 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams(); 668 lp.width = size.getWidth(); 669 lp.height = size.getHeight(); 670 bbev.setLayoutParams(lp); 671 bbev.setX(position.x); 672 bbev.setY(position.y); 673 bbev.setScaleX(1f); 674 bbev.setScaleY(1f); 675 bbev.updateLocation(); 676 bbev.maybeShowOverflow(); 677 } 678 getExpandedViewRestBounds(Rect out)679 void getExpandedViewRestBounds(Rect out) { 680 final int width = mPositioner.getExpandedViewWidthForBubbleBar(false /* overflow */); 681 final int height = mPositioner.getExpandedViewHeightForBubbleBar(false /* overflow */); 682 Point position = getExpandedViewRestPosition(new Size(width, height)); 683 out.set(position.x, position.y, position.x + width, position.y + height); 684 } 685 getExpandedViewRestPosition(Size size)686 private Point getExpandedViewRestPosition(Size size) { 687 final int padding = mPositioner.getBubbleBarExpandedViewPadding(); 688 Point point = new Point(); 689 if (mPositioner.isBubbleBarOnLeft()) { 690 point.x = mPositioner.getInsets().left + padding; 691 } else { 692 point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding; 693 } 694 point.y = mPositioner.getExpandedViewBottomForBubbleBar() - size.getHeight(); 695 return point; 696 } 697 getExpandedViewSize()698 private Size getExpandedViewSize() { 699 boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); 700 final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); 701 final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); 702 return new Size(width, height); 703 } 704 startNewAnimator(Animator animator)705 private void startNewAnimator(Animator animator) { 706 cancelAnimations(); 707 mRunningAnimator = animator; 708 animator.start(); 709 } 710 711 /** 712 * Animate the alpha of the expanded view between visible (1) and invisible (0). 713 * {@link BubbleBarExpandedView} requires 714 * {@link com.android.wm.shell.bubbles.BubbleExpandedView#setSurfaceZOrderedOnTop(boolean)} to 715 * be called before alpha can be applied. 716 * Only supports alpha of 1 or 0. Otherwise we can't reset surface z-order at the end. 717 */ createAlphaAnimator(BubbleBarExpandedView bubbleBarExpandedView, boolean visible)718 private ObjectAnimator createAlphaAnimator(BubbleBarExpandedView bubbleBarExpandedView, 719 boolean visible) { 720 ObjectAnimator animator = ObjectAnimator.ofFloat(bubbleBarExpandedView, TASK_VIEW_ALPHA, 721 visible ? 1f : 0f); 722 animator.addListener(new AnimatorListenerAdapter() { 723 @Override 724 public void onAnimationStart(Animator animation) { 725 // Move task view to the top of the window so alpha can be applied to it 726 bubbleBarExpandedView.setSurfaceZOrderedOnTop(true); 727 } 728 729 @Override 730 public void onAnimationEnd(Animator animation) { 731 bubbleBarExpandedView.setContentVisibility(visible); 732 if (!visible) { 733 // Hide the expanded view before we reset the z-ordering 734 bubbleBarExpandedView.setVisibility(INVISIBLE); 735 } 736 bubbleBarExpandedView.setSurfaceZOrderedOnTop(false); 737 } 738 }); 739 return animator; 740 } 741 setDragPivot(BubbleBarExpandedView bbev)742 private static void setDragPivot(BubbleBarExpandedView bbev) { 743 bbev.setPivotX(bbev.getWidth() / 2f); 744 bbev.setPivotY(0f); 745 } 746 setPivotToCenter(BubbleBarExpandedView bbev)747 private static void setPivotToCenter(BubbleBarExpandedView bbev) { 748 bbev.setPivotX(bbev.getWidth() / 2f); 749 bbev.setPivotY(bbev.getHeight() / 2f); 750 } 751 752 private static class DragAnimatorListenerAdapter extends AnimatorListenerAdapter { 753 754 private final BubbleBarExpandedView mBubbleBarExpandedView; 755 DragAnimatorListenerAdapter(BubbleBarExpandedView bbev)756 DragAnimatorListenerAdapter(BubbleBarExpandedView bbev) { 757 mBubbleBarExpandedView = bbev; 758 } 759 760 @Override onAnimationStart(Animator animation)761 public void onAnimationStart(Animator animation) { 762 mBubbleBarExpandedView.setAnimating(true); 763 } 764 @Override onAnimationEnd(Animator animation)765 public void onAnimationEnd(Animator animation) { 766 mBubbleBarExpandedView.setAnimating(false); 767 } 768 } 769 } 770