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.SCALE_X; 20 import static android.view.View.SCALE_Y; 21 import static android.view.View.TRANSLATION_X; 22 import static android.view.View.TRANSLATION_Y; 23 import static android.view.View.VISIBLE; 24 import static android.view.View.X; 25 import static android.view.View.Y; 26 27 import static com.android.wm.shell.animation.Interpolators.EMPHASIZED; 28 import static com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE; 29 import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS; 30 31 import android.animation.Animator; 32 import android.animation.AnimatorListenerAdapter; 33 import android.animation.AnimatorSet; 34 import android.animation.ObjectAnimator; 35 import android.animation.ValueAnimator; 36 import android.content.Context; 37 import android.graphics.Point; 38 import android.graphics.Rect; 39 import android.util.Log; 40 import android.util.Size; 41 import android.widget.FrameLayout; 42 43 import androidx.annotation.Nullable; 44 45 import com.android.wm.shell.animation.Interpolators; 46 import com.android.wm.shell.bubbles.BubbleOverflow; 47 import com.android.wm.shell.bubbles.BubblePositioner; 48 import com.android.wm.shell.bubbles.BubbleViewProvider; 49 import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix; 50 import com.android.wm.shell.common.magnetictarget.MagnetizedObject.MagneticTarget; 51 import com.android.wm.shell.shared.animation.PhysicsAnimator; 52 53 /** 54 * Helper class to animate a {@link BubbleBarExpandedView} on a bubble. 55 */ 56 public class BubbleBarAnimationHelper { 57 58 private static final String TAG = BubbleBarAnimationHelper.class.getSimpleName(); 59 60 private static final float EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT = 0.1f; 61 private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f; 62 private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150; 63 private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 400; 64 private static final int EXPANDED_VIEW_ANIMATE_TO_REST_DURATION = 400; 65 private static final int EXPANDED_VIEW_DISMISS_DURATION = 250; 66 private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 400; 67 /** 68 * Additional scale applied to expanded view when it is positioned inside a magnetic target. 69 */ 70 private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.2f; 71 private static final float EXPANDED_VIEW_DRAG_SCALE = 0.4f; 72 private static final float DISMISS_VIEW_SCALE = 1.25f; 73 private static final int HANDLE_ALPHA_ANIMATION_DURATION = 100; 74 75 /** Spring config for the expanded view scale-in animation. */ 76 private final PhysicsAnimator.SpringConfig mScaleInSpringConfig = 77 new PhysicsAnimator.SpringConfig(300f, 0.9f); 78 79 /** Spring config for the expanded view scale-out animation. */ 80 private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig = 81 new PhysicsAnimator.SpringConfig(900f, 1f); 82 83 /** Matrix used to scale the expanded view container with a given pivot point. */ 84 private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix(); 85 86 /** Animator for animating the expanded view's alpha (including the TaskView inside it). */ 87 private final ValueAnimator mExpandedViewAlphaAnimator = ValueAnimator.ofFloat(0f, 1f); 88 89 @Nullable 90 private Animator mRunningDragAnimator; 91 92 private final Context mContext; 93 private final BubbleBarLayerView mLayerView; 94 private final BubblePositioner mPositioner; 95 private final int[] mTmpLocation = new int[2]; 96 97 private BubbleViewProvider mExpandedBubble; 98 private boolean mIsExpanded = false; 99 BubbleBarAnimationHelper(Context context, BubbleBarLayerView bubbleBarLayerView, BubblePositioner positioner)100 public BubbleBarAnimationHelper(Context context, 101 BubbleBarLayerView bubbleBarLayerView, 102 BubblePositioner positioner) { 103 mContext = context; 104 mLayerView = bubbleBarLayerView; 105 mPositioner = positioner; 106 107 mExpandedViewAlphaAnimator.setDuration(EXPANDED_VIEW_ALPHA_ANIMATION_DURATION); 108 mExpandedViewAlphaAnimator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED); 109 mExpandedViewAlphaAnimator.addListener(new AnimatorListenerAdapter() { 110 @Override 111 public void onAnimationStart(Animator animation) { 112 BubbleBarExpandedView bbev = getExpandedView(); 113 if (bbev != null) { 114 // We need to be Z ordered on top in order for alpha animations to work. 115 bbev.setSurfaceZOrderedOnTop(true); 116 bbev.setAnimating(true); 117 } 118 } 119 120 @Override 121 public void onAnimationEnd(Animator animation) { 122 BubbleBarExpandedView bbev = getExpandedView(); 123 if (bbev != null) { 124 // The surface needs to be Z ordered on top for alpha values to work on the 125 // TaskView, and if we're temporarily hidden, we are still on the screen 126 // with alpha = 0f until we animate back. Stay Z ordered on top so the alpha 127 // = 0f remains in effect. 128 if (mIsExpanded) { 129 bbev.setSurfaceZOrderedOnTop(false); 130 } 131 132 bbev.setContentVisibility(mIsExpanded); 133 bbev.setAnimating(false); 134 } 135 } 136 }); 137 mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> { 138 BubbleBarExpandedView bbev = getExpandedView(); 139 if (bbev != null) { 140 float alpha = (float) valueAnimator.getAnimatedValue(); 141 bbev.setTaskViewAlpha(alpha); 142 bbev.setAlpha(alpha); 143 } 144 }); 145 } 146 147 /** 148 * Animates the provided bubble's expanded view to the expanded state. 149 */ animateExpansion(BubbleViewProvider expandedBubble, @Nullable Runnable afterAnimation)150 public void animateExpansion(BubbleViewProvider expandedBubble, 151 @Nullable Runnable afterAnimation) { 152 mExpandedBubble = expandedBubble; 153 final BubbleBarExpandedView bbev = getExpandedView(); 154 if (bbev == null) { 155 return; 156 } 157 mIsExpanded = true; 158 159 mExpandedViewContainerMatrix.setScaleX(0f); 160 mExpandedViewContainerMatrix.setScaleY(0f); 161 162 updateExpandedView(); 163 bbev.setAnimating(true); 164 bbev.setContentVisibility(false); 165 bbev.setAlpha(0f); 166 bbev.setTaskViewAlpha(0f); 167 bbev.setVisibility(VISIBLE); 168 169 setScaleFromBubbleBar(mExpandedViewContainerMatrix, 170 1f - EXPANDED_VIEW_ANIMATE_SCALE_AMOUNT); 171 172 bbev.setAnimationMatrix(mExpandedViewContainerMatrix); 173 174 mExpandedViewAlphaAnimator.start(); 175 176 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); 177 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) 178 .spring(AnimatableScaleMatrix.SCALE_X, 179 AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), 180 mScaleInSpringConfig) 181 .spring(AnimatableScaleMatrix.SCALE_Y, 182 AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f), 183 mScaleInSpringConfig) 184 .addUpdateListener((target, values) -> { 185 bbev.setAnimationMatrix(mExpandedViewContainerMatrix); 186 }) 187 .withEndActions(() -> { 188 bbev.setAnimationMatrix(null); 189 updateExpandedView(); 190 bbev.setSurfaceZOrderedOnTop(false); 191 if (afterAnimation != null) { 192 afterAnimation.run(); 193 } 194 }) 195 .start(); 196 } 197 198 /** 199 * Collapses the currently expanded bubble. 200 * 201 * @param endRunnable a runnable to run at the end of the animation. 202 */ animateCollapse(Runnable endRunnable)203 public void animateCollapse(Runnable endRunnable) { 204 mIsExpanded = false; 205 final BubbleBarExpandedView bbev = getExpandedView(); 206 if (bbev == null) { 207 Log.w(TAG, "Trying to animate collapse without a bubble"); 208 return; 209 } 210 bbev.setScaleX(1f); 211 bbev.setScaleY(1f); 212 213 setScaleFromBubbleBar(mExpandedViewContainerMatrix, 1f); 214 215 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); 216 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix) 217 .spring(AnimatableScaleMatrix.SCALE_X, 218 AnimatableScaleMatrix.getAnimatableValueForScaleFactor( 219 EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), 220 mScaleOutSpringConfig) 221 .spring(AnimatableScaleMatrix.SCALE_Y, 222 AnimatableScaleMatrix.getAnimatableValueForScaleFactor( 223 EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT), 224 mScaleOutSpringConfig) 225 .addUpdateListener((target, values) -> { 226 bbev.setAnimationMatrix(mExpandedViewContainerMatrix); 227 }) 228 .withEndActions(() -> { 229 bbev.setAnimationMatrix(null); 230 if (endRunnable != null) { 231 endRunnable.run(); 232 } 233 }) 234 .start(); 235 mExpandedViewAlphaAnimator.reverse(); 236 } 237 setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale)238 private void setScaleFromBubbleBar(AnimatableScaleMatrix matrix, float scale) { 239 // Set the pivot point for the scale, so the view animates out from the bubble bar. 240 Rect availableRect = mPositioner.getAvailableRect(); 241 float pivotX = mPositioner.isBubbleBarOnLeft() ? availableRect.left : availableRect.right; 242 float pivotY = mPositioner.getBubbleBarTopOnScreen(); 243 matrix.setScale(scale, scale, pivotX, pivotY); 244 } 245 246 /** 247 * Animate the expanded bubble when it is being dragged 248 */ animateStartDrag()249 public void animateStartDrag() { 250 final BubbleBarExpandedView bbev = getExpandedView(); 251 if (bbev == null) { 252 Log.w(TAG, "Trying to animate start drag without a bubble"); 253 return; 254 } 255 setDragPivot(bbev); 256 // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius 257 final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE; 258 259 AnimatorSet contentAnim = new AnimatorSet(); 260 contentAnim.playTogether( 261 ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE), 262 ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE), 263 ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius) 264 ); 265 contentAnim.setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION).setInterpolator(EMPHASIZED); 266 267 ObjectAnimator handleAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 0f) 268 .setDuration(HANDLE_ALPHA_ANIMATION_DURATION); 269 270 AnimatorSet animatorSet = new AnimatorSet(); 271 animatorSet.playTogether(contentAnim, handleAnim); 272 animatorSet.addListener(new DragAnimatorListenerAdapter(bbev)); 273 startNewDragAnimation(animatorSet); 274 } 275 276 /** 277 * Animates dismissal of currently expanded bubble 278 * 279 * @param endRunnable a runnable to run at the end of the animation 280 */ animateDismiss(Runnable endRunnable)281 public void animateDismiss(Runnable endRunnable) { 282 mIsExpanded = false; 283 final BubbleBarExpandedView bbev = getExpandedView(); 284 if (bbev == null) { 285 Log.w(TAG, "Trying to animate dismiss without a bubble"); 286 return; 287 } 288 289 int[] location = bbev.getLocationOnScreen(); 290 int diffFromBottom = mPositioner.getScreenRect().bottom - location[1]; 291 292 cancelAnimations(); 293 bbev.animate() 294 // 2x distance from bottom so the view flies out 295 .translationYBy(diffFromBottom * 2) 296 .setDuration(EXPANDED_VIEW_DISMISS_DURATION) 297 .withEndAction(endRunnable) 298 .start(); 299 } 300 301 /** 302 * Animate current expanded bubble back to its rest position 303 */ animateToRestPosition()304 public void animateToRestPosition() { 305 BubbleBarExpandedView bbev = getExpandedView(); 306 if (bbev == null) { 307 Log.w(TAG, "Trying to animate expanded view to rest position without a bubble"); 308 return; 309 } 310 Point restPoint = getExpandedViewRestPosition(getExpandedViewSize()); 311 312 AnimatorSet contentAnim = new AnimatorSet(); 313 contentAnim.playTogether( 314 ObjectAnimator.ofFloat(bbev, X, restPoint.x), 315 ObjectAnimator.ofFloat(bbev, Y, restPoint.y), 316 ObjectAnimator.ofFloat(bbev, SCALE_X, 1f), 317 ObjectAnimator.ofFloat(bbev, SCALE_Y, 1f), 318 ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, bbev.getRestingCornerRadius()) 319 ); 320 contentAnim.setDuration(EXPANDED_VIEW_ANIMATE_TO_REST_DURATION).setInterpolator(EMPHASIZED); 321 322 ObjectAnimator handleAlphaAnim = ObjectAnimator.ofFloat(bbev.getHandleView(), ALPHA, 1f) 323 .setDuration(HANDLE_ALPHA_ANIMATION_DURATION); 324 325 AnimatorSet animatorSet = new AnimatorSet(); 326 animatorSet.playTogether(contentAnim, handleAlphaAnim); 327 animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) { 328 @Override 329 public void onAnimationEnd(Animator animation) { 330 super.onAnimationEnd(animation); 331 bbev.resetPivot(); 332 } 333 }); 334 startNewDragAnimation(animatorSet); 335 } 336 337 /** 338 * Animates currently expanded bubble into the given {@link MagneticTarget}. 339 * 340 * @param target magnetic target to snap to 341 * @param endRunnable a runnable to run at the end of the animation 342 */ animateIntoTarget(MagneticTarget target, @Nullable Runnable endRunnable)343 public void animateIntoTarget(MagneticTarget target, @Nullable Runnable endRunnable) { 344 BubbleBarExpandedView bbev = getExpandedView(); 345 if (bbev == null) { 346 Log.w(TAG, "Trying to snap the expanded view to target without a bubble"); 347 return; 348 } 349 350 setDragPivot(bbev); 351 352 // When the view animates into the target, it is scaled down with the pivot at center top. 353 // Find the point on the view that would be the center of the view at its final scale. 354 // Once we know that, we can calculate x and y distance from the center of the target view 355 // and use that for the translation animation to ensure that the view at final scale is 356 // placed at the center of the target. 357 358 // Set mTmpLocation to the current location of the view on the screen, taking into account 359 // any scale applied. 360 bbev.getLocationOnScreen(mTmpLocation); 361 // Since pivotX is at the center of the x-axis, even at final scale, center of the view on 362 // x-axis will be the same as the center of the view at current size. 363 // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the 364 // center of the view at its current size. 365 float currentWidth = bbev.getWidth() * bbev.getScaleX(); 366 mTmpLocation[0] += (int) (currentWidth / 2f); 367 // Since pivotY is at the top of the view, at final scale, top coordinate of the view 368 // remains the same. 369 // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is 370 // moved down by half of the height at final scale. 371 float targetHeight = bbev.getHeight() * EXPANDED_VIEW_IN_TARGET_SCALE; 372 mTmpLocation[1] += (int) (targetHeight / 2f); 373 // mTmpLocation is now set to the point on the view that will be the center of the view once 374 // scale is applied. 375 376 // Calculate the difference between the target's center coordinates and mTmpLocation 377 float xDiff = target.getCenterOnScreen().x - mTmpLocation[0]; 378 float yDiff = target.getCenterOnScreen().y - mTmpLocation[1]; 379 380 // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius 381 final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_IN_TARGET_SCALE; 382 383 AnimatorSet animatorSet = new AnimatorSet(); 384 animatorSet.playTogether( 385 // Move expanded view to the center of dismiss view 386 ObjectAnimator.ofFloat(bbev, TRANSLATION_X, bbev.getTranslationX() + xDiff), 387 ObjectAnimator.ofFloat(bbev, TRANSLATION_Y, bbev.getTranslationY() + yDiff), 388 // Scale expanded view down 389 ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_IN_TARGET_SCALE), 390 ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_IN_TARGET_SCALE), 391 // Update corner radius for expanded view 392 ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius), 393 // Scale dismiss view up 394 ObjectAnimator.ofFloat(target.getTargetView(), SCALE_X, DISMISS_VIEW_SCALE), 395 ObjectAnimator.ofFloat(target.getTargetView(), SCALE_Y, DISMISS_VIEW_SCALE) 396 ); 397 animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator( 398 EMPHASIZED_DECELERATE); 399 animatorSet.addListener(new DragAnimatorListenerAdapter(bbev) { 400 @Override 401 public void onAnimationEnd(Animator animation) { 402 super.onAnimationEnd(animation); 403 if (endRunnable != null) { 404 endRunnable.run(); 405 } 406 } 407 }); 408 startNewDragAnimation(animatorSet); 409 } 410 411 /** 412 * Animate currently expanded view when it is released from dismiss view 413 */ animateUnstuckFromDismissView(MagneticTarget target)414 public void animateUnstuckFromDismissView(MagneticTarget target) { 415 BubbleBarExpandedView bbev = getExpandedView(); 416 if (bbev == null) { 417 Log.w(TAG, "Trying to unsnap the expanded view from dismiss without a bubble"); 418 return; 419 } 420 setDragPivot(bbev); 421 // Corner radius gets scaled, apply the reverse scale to ensure we have the desired radius 422 final float cornerRadius = bbev.getDraggedCornerRadius() / EXPANDED_VIEW_DRAG_SCALE; 423 AnimatorSet animatorSet = new AnimatorSet(); 424 animatorSet.playTogether( 425 ObjectAnimator.ofFloat(bbev, SCALE_X, EXPANDED_VIEW_DRAG_SCALE), 426 ObjectAnimator.ofFloat(bbev, SCALE_Y, EXPANDED_VIEW_DRAG_SCALE), 427 ObjectAnimator.ofFloat(bbev, CORNER_RADIUS, cornerRadius), 428 ObjectAnimator.ofFloat(target.getTargetView(), SCALE_X, 1f), 429 ObjectAnimator.ofFloat(target.getTargetView(), SCALE_Y, 1f) 430 ); 431 animatorSet.setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION).setInterpolator( 432 EMPHASIZED_DECELERATE); 433 animatorSet.addListener(new DragAnimatorListenerAdapter(bbev)); 434 startNewDragAnimation(animatorSet); 435 } 436 437 /** 438 * Cancel current animations 439 */ cancelAnimations()440 public void cancelAnimations() { 441 PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel(); 442 mExpandedViewAlphaAnimator.cancel(); 443 BubbleBarExpandedView bbev = getExpandedView(); 444 if (bbev != null) { 445 bbev.animate().cancel(); 446 } 447 if (mRunningDragAnimator != null) { 448 mRunningDragAnimator.cancel(); 449 mRunningDragAnimator = null; 450 } 451 } 452 getExpandedView()453 private @Nullable BubbleBarExpandedView getExpandedView() { 454 BubbleViewProvider bubble = mExpandedBubble; 455 if (bubble != null) { 456 return bubble.getBubbleBarExpandedView(); 457 } 458 return null; 459 } 460 updateExpandedView()461 private void updateExpandedView() { 462 BubbleBarExpandedView bbev = getExpandedView(); 463 if (bbev == null) { 464 Log.w(TAG, "Trying to update the expanded view without a bubble"); 465 return; 466 } 467 468 final Size size = getExpandedViewSize(); 469 Point position = getExpandedViewRestPosition(size); 470 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) bbev.getLayoutParams(); 471 lp.width = size.getWidth(); 472 lp.height = size.getHeight(); 473 bbev.setLayoutParams(lp); 474 bbev.setX(position.x); 475 bbev.setY(position.y); 476 bbev.updateLocation(); 477 bbev.maybeShowOverflow(); 478 } 479 getExpandedViewRestPosition(Size size)480 private Point getExpandedViewRestPosition(Size size) { 481 final int padding = mPositioner.getBubbleBarExpandedViewPadding(); 482 Point point = new Point(); 483 if (mPositioner.isBubbleBarOnLeft()) { 484 point.x = mPositioner.getInsets().left + padding; 485 } else { 486 point.x = mPositioner.getAvailableRect().width() - size.getWidth() - padding; 487 } 488 point.y = mPositioner.getExpandedViewBottomForBubbleBar() - size.getHeight(); 489 return point; 490 } 491 getExpandedViewSize()492 private Size getExpandedViewSize() { 493 boolean isOverflowExpanded = mExpandedBubble.getKey().equals(BubbleOverflow.KEY); 494 final int width = mPositioner.getExpandedViewWidthForBubbleBar(isOverflowExpanded); 495 final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded); 496 return new Size(width, height); 497 } 498 startNewDragAnimation(Animator animator)499 private void startNewDragAnimation(Animator animator) { 500 cancelAnimations(); 501 mRunningDragAnimator = animator; 502 animator.start(); 503 } 504 setDragPivot(BubbleBarExpandedView bbev)505 private static void setDragPivot(BubbleBarExpandedView bbev) { 506 bbev.setPivotX(bbev.getWidth() / 2f); 507 bbev.setPivotY(0f); 508 } 509 510 private class DragAnimatorListenerAdapter extends AnimatorListenerAdapter { 511 512 private final BubbleBarExpandedView mBubbleBarExpandedView; 513 DragAnimatorListenerAdapter(BubbleBarExpandedView bbev)514 DragAnimatorListenerAdapter(BubbleBarExpandedView bbev) { 515 mBubbleBarExpandedView = bbev; 516 } 517 518 @Override onAnimationStart(Animator animation)519 public void onAnimationStart(Animator animation) { 520 mBubbleBarExpandedView.setAnimating(true); 521 } 522 523 @Override onAnimationEnd(Animator animation)524 public void onAnimationEnd(Animator animation) { 525 mBubbleBarExpandedView.setAnimating(false); 526 mRunningDragAnimator = null; 527 } 528 } 529 } 530