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