• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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