Animate bubble bar location changes

When it is not the initial bubble data, animate bubble bar location
change.

Bug: 313661121
Flag: ACONFIG com.android.wm.shell.enable_bubble_bar DEVELOPMENT
Test: manual, move expanded view from one side to the other, observe the
  location change animates

Change-Id: I52117a31d02e7160ca1d3dc3e724bda2e38f6cbf
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
index 2b69e96..b6c248c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java
@@ -156,6 +156,7 @@
      * {@link BubbleBarBubble}s so that it can be used to update the views.
      */
     private static class BubbleBarViewUpdate {
+        final boolean initialState;
         boolean expandedChanged;
         boolean expanded;
         boolean shouldShowEducation;
@@ -172,6 +173,7 @@
         List<BubbleBarBubble> currentBubbles;
 
         BubbleBarViewUpdate(BubbleBarUpdate update) {
+            initialState = update.initialState;
             expandedChanged = update.expandedChanged;
             expanded = update.expanded;
             shouldShowEducation = update.shouldShowEducation;
@@ -405,7 +407,9 @@
         }
         if (update.bubbleBarLocation != null) {
             if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) {
-                mBubbleBarViewController.setBubbleBarLocation(update.bubbleBarLocation);
+                // Animate when receiving updates. Skip it if we received the initial state.
+                boolean animate = !update.initialState;
+                mBubbleBarViewController.setBubbleBarLocation(update.bubbleBarLocation, animate);
                 mBubbleStashController.setBubbleBarLocation(update.bubbleBarLocation);
             }
         }
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index 61ae965..a328c90 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -15,7 +15,13 @@
  */
 package com.android.launcher3.taskbar.bubbles;
 
+import static com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE;
+import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X;
+
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -29,7 +35,10 @@
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
 
+import androidx.dynamicanimation.animation.SpringForce;
+
 import com.android.launcher3.R;
+import com.android.launcher3.anim.SpringAnimationBuilder;
 import com.android.launcher3.taskbar.TaskbarActivityContext;
 import com.android.launcher3.views.ActivityContext;
 import com.android.wm.shell.common.bubbles.BubbleBarLocation;
@@ -73,6 +82,18 @@
     private static final int ARROW_POSITION_ANIMATION_DURATION_MS = 200;
     private static final int WIDTH_ANIMATION_DURATION_MS = 200;
 
+    private static final long FADE_OUT_ANIM_ALPHA_DURATION_MS = 50L;
+    private static final long FADE_OUT_ANIM_ALPHA_DELAY_MS = 50L;
+    private static final long FADE_OUT_ANIM_POSITION_DURATION_MS = 100L;
+    // During fade out animation we shift the bubble bar 1/80th of the screen width
+    private static final float FADE_OUT_ANIM_POSITION_SHIFT = 1 / 80f;
+
+    private static final long FADE_IN_ANIM_ALPHA_DURATION_MS = 100L;
+    // Use STIFFNESS_MEDIUMLOW which is not defined in the API constants
+    private static final float FADE_IN_ANIM_POSITION_SPRING_STIFFNESS = 400f;
+    // During fade in animation we shift the bubble bar 1/60th of the screen width
+    private static final float FADE_IN_ANIM_POSITION_SHIFT = 1 / 60f;
+
     private final BubbleBarBackground mBubbleBarBackground;
 
     /**
@@ -106,6 +127,9 @@
     // collapsed state and 1 to the fully expanded state.
     private final ValueAnimator mWidthAnimator = ValueAnimator.ofFloat(0, 1);
 
+    @Nullable
+    private Animator mBubbleBarLocationAnimator = null;
+
     // We don't reorder the bubbles when they are expanded as it could be jarring for the user
     // this runnable will be populated with any reordering of the bubbles that should be applied
     // once they are collapsed.
@@ -236,13 +260,86 @@
     /**
      * Update {@link BubbleBarLocation}
      */
-    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation, boolean animate) {
+        if (animate) {
+            animateToBubbleBarLocation(bubbleBarLocation);
+        } else {
+            setBubbleBarLocationInternal(bubbleBarLocation);
+        }
+    }
+
+    private void setBubbleBarLocationInternal(BubbleBarLocation bubbleBarLocation) {
         if (bubbleBarLocation != mBubbleBarLocation) {
             mBubbleBarLocation = bubbleBarLocation;
             onBubbleBarLocationChanged();
         }
     }
 
+    private void animateToBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
+        if (bubbleBarLocation == mBubbleBarLocation) {
+            // nothing to do, already at expected location
+            return;
+        }
+        if (mBubbleBarLocationAnimator != null && mBubbleBarLocationAnimator.isRunning()) {
+            mBubbleBarLocationAnimator.cancel();
+        }
+
+        // Location animation uses two separate animators.
+        // First animator hides the bar.
+        // After it completes, location update is sent to layout the bar in the new location.
+        // Second animator is started to show the bar.
+        mBubbleBarLocationAnimator = getLocationUpdateFadeOutAnimator();
+        mBubbleBarLocationAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                // Bubble bar is not visible, update the location
+                setBubbleBarLocationInternal(bubbleBarLocation);
+                // Animate it in
+                mBubbleBarLocationAnimator = getLocationUpdateFadeInAnimator();
+                mBubbleBarLocationAnimator.start();
+            }
+        });
+        mBubbleBarLocationAnimator.start();
+    }
+
+    private AnimatorSet getLocationUpdateFadeOutAnimator() {
+        final float shift =
+                getResources().getDisplayMetrics().widthPixels * FADE_OUT_ANIM_POSITION_SHIFT;
+        final float tx = mBubbleBarLocation.isOnLeft(isLayoutRtl()) ? shift : -shift;
+
+        ObjectAnimator positionAnim = ObjectAnimator.ofFloat(this, TRANSLATION_X, tx)
+                .setDuration(FADE_OUT_ANIM_POSITION_DURATION_MS);
+        positionAnim.setInterpolator(EMPHASIZED_ACCELERATE);
+
+        ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 0f)
+                .setDuration(FADE_OUT_ANIM_ALPHA_DURATION_MS);
+        alphaAnim.setStartDelay(FADE_OUT_ANIM_ALPHA_DELAY_MS);
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(positionAnim, alphaAnim);
+        return animatorSet;
+    }
+
+    private Animator getLocationUpdateFadeInAnimator() {
+        final float shift =
+                getResources().getDisplayMetrics().widthPixels * FADE_IN_ANIM_POSITION_SHIFT;
+        final float startTx = mBubbleBarLocation.isOnLeft(isLayoutRtl()) ? shift : -shift;
+
+        ValueAnimator positionAnim = new SpringAnimationBuilder(getContext())
+                .setStartValue(startTx)
+                .setEndValue(0)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+                .setStiffness(FADE_IN_ANIM_POSITION_SPRING_STIFFNESS)
+                .build(this, VIEW_TRANSLATE_X);
+
+        ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(this, ALPHA, 1f)
+                .setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
+
+        AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(positionAnim, alphaAnim);
+        return animatorSet;
+    }
+
     /**
      * Updates the bounds with translation that may have been applied and returns the result.
      */
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 36e208e..cc5859e 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -179,8 +179,8 @@
     /**
      * Update bar {@link BubbleBarLocation}
      */
-    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
-        mBarView.setBubbleBarLocation(bubbleBarLocation);
+    public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation, boolean animate) {
+        mBarView.setBubbleBarLocation(bubbleBarLocation, animate);
     }
 
     /**