Bubble bar expland/collapse animation when on left

Fix bubble bar collapse and expand animations when bubble bar is pinned
to the left.
When system language is set to an RTL language, bubble bar gets pinned
to the left edge of the screen.
Bubble bar should expand to the right and collapse to the left. Bubbles
in the bubble bar should be ordered from right to left. The most recent
bubble should be at the right in the bubble bar.

Known issue:
  - when the most recent bubble is removed, the arrow animates to a new
    position, but the background is not animated, resulting in arrow
    getting detached from the container. Will be fixed by animating the
    background.

Flag: LEGACY persist.wm.debug.bubble_bar DEVELOPMENT
Bug: 273310265
Test: with LTR language set as the system language
  - expand bubble bar with 1 bubble
  - expand bubble bar with multiple bubbles
  - select second bubble, observe after collapse it is set as first
  - dismiss a bubble from bubble bar by dragging
Test: with RTL language set as the system language
  - expand bubble bar with 1 bubble
  - expand bubble bar with multiple bubbles
  - select second bubble, observe after collapse it is set as first
  - dismiss a bubble from bubble bar by dragging

Change-Id: Ic46a5b1a6e45ad225ba509a61147cc6a8cdd0397
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
index 1e3f4f1..aa2b29d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarBackground.kt
@@ -50,6 +50,22 @@
 
     var width: Float = 0f
 
+    /**
+     * Set whether the drawable is anchored to the left or right edge of the container.
+     *
+     * When `anchorLeft` is set to `true`, drawable left edge aligns up with the container left
+     * edge. Drawable can be drawn outside container bounds on the right edge. When it is set to
+     * `false` (the default), drawable right edge aligns up with the container right edge. Drawable
+     * can be drawn outside container bounds on the left edge.
+     */
+    var anchorLeft: Boolean = false
+        set(value) {
+            if (field != value) {
+                field = value
+                invalidateSelf()
+            }
+        }
+
     init {
         paint.color = context.getColor(R.color.taskbar_background)
         paint.flags = Paint.ANTI_ALIAS_FLAG
@@ -106,15 +122,9 @@
 
         // Draw background.
         val radius = backgroundHeight / 2f
-        canvas.drawRoundRect(
-            canvas.width.toFloat() - width,
-            0f,
-            canvas.width.toFloat(),
-            canvas.height.toFloat(),
-            radius,
-            radius,
-            paint
-        )
+        val left = if (anchorLeft) 0f else canvas.width.toFloat() - width
+        val right = if (anchorLeft) width else canvas.width.toFloat()
+        canvas.drawRoundRect(left, 0f, right, canvas.height.toFloat(), radius, radius, paint)
 
         if (showingArrow) {
             // Draw arrow.
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
index ec9f4e5..5ca2991 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarView.java
@@ -197,6 +197,16 @@
         updateChildrenRenderNodeProperties();
     }
 
+    @Override
+    public void onRtlPropertiesChanged(int layoutDirection) {
+        // TODO(b/273310265): set this based on bubble bar position and not LTR or RTL
+        mBubbleBarBackground.setAnchorLeft(layoutDirection == LAYOUT_DIRECTION_RTL);
+    }
+
+    private boolean isOnLeft() {
+        return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
+    }
+
     /**
      * Updates the bounds with translation that may have been applied and returns the result.
      */
@@ -275,18 +285,31 @@
         int bubbleCount = getChildCount();
         final float ty = (mBubbleBarBounds.height() - mIconSize) / 2f;
         final boolean animate = getVisibility() == VISIBLE;
+        final boolean onLeft = isOnLeft();
         for (int i = 0; i < bubbleCount; i++) {
             BubbleView bv = (BubbleView) getChildAt(i);
             bv.setTranslationY(ty);
 
             // the position of the bubble when the bar is fully expanded
-            final float expandedX = i * (mIconSize + mIconSpacing);
+            final float expandedX;
             // the position of the bubble when the bar is fully collapsed
-            final float collapsedX = i == 0 ? 0 : mIconOverlapAmount;
+            final float collapsedX;
+            if (onLeft) {
+                // If bar is on the left, bubbles are ordered right to left
+                expandedX = (bubbleCount - i - 1) * (mIconSize + mIconSpacing);
+                // Shift the first bubble only if there are more bubbles in addition to overflow
+                collapsedX = i == 0 && bubbleCount > 2 ? mIconOverlapAmount : 0;
+            } else {
+                // Bubbles ordered left to right, don't move the first bubble
+                expandedX = i * (mIconSize + mIconSpacing);
+                collapsedX = i == 0 ? 0 : mIconOverlapAmount;
+            }
 
             if (mIsBarExpanded) {
+                // If bar is on the right, account for bubble bar expanding and shifting left
+                final float expandedBarShift = onLeft ? 0 : currentWidth - expandedWidth;
                 // where the bubble will end up when the animation ends
-                final float targetX = currentWidth - expandedWidth + expandedX;
+                final float targetX = expandedX + expandedBarShift;
                 bv.setTranslationX(widthState * (targetX - collapsedX) + collapsedX);
                 // if we're fully expanded, set the z level to 0 or to bubble elevation if dragged
                 if (widthState == 1f) {
@@ -296,7 +319,9 @@
                 bv.setBehindStack(false, animate);
                 bv.setAlpha(1);
             } else {
-                final float targetX = currentWidth - collapsedWidth + collapsedX;
+                // If bar is on the right, account for bubble bar expanding and shifting left
+                final float collapsedBarShift = onLeft ? 0 : currentWidth - collapsedWidth;
+                final float targetX = collapsedX + collapsedBarShift;
                 bv.setTranslationX(widthState * (expandedX - targetX) + targetX);
                 bv.setZ((MAX_BUBBLES * mBubbleElevation) - i);
                 // If we're not the first bubble we're behind the stack
@@ -318,18 +343,22 @@
         final float expandedArrowPosition = arrowPositionForSelectedWhenExpanded();
         final float interpolatedWidth =
                 widthState * (expandedWidth - collapsedWidth) + collapsedWidth;
-        if (mIsBarExpanded) {
-            // when the bar is expanding, the selected bubble is always the first, so the arrow
-            // always shifts with the interpolated width.
-            final float arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
-            mBubbleBarBackground.setArrowPosition(arrowPosition);
+        final float arrowPosition;
+        if (onLeft) {
+            float interpolatedShift = (expandedArrowPosition - collapsedArrowPosition) * widthState;
+            arrowPosition = collapsedArrowPosition + interpolatedShift;
         } else {
-            final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
-            final float arrowPosition =
-                    targetPosition + widthState * (expandedArrowPosition - targetPosition);
-            mBubbleBarBackground.setArrowPosition(arrowPosition);
+            if (mIsBarExpanded) {
+                // when the bar is expanding, the selected bubble is always the first, so the arrow
+                // always shifts with the interpolated width.
+                arrowPosition = currentWidth - interpolatedWidth + collapsedArrowPosition;
+            } else {
+                final float targetPosition = currentWidth - collapsedWidth + collapsedArrowPosition;
+                arrowPosition =
+                        targetPosition + widthState * (expandedArrowPosition - targetPosition);
+            }
         }
-
+        mBubbleBarBackground.setArrowPosition(arrowPosition);
         mBubbleBarBackground.setArrowAlpha((int) (255 * widthState));
         mBubbleBarBackground.setWidth(interpolatedWidth);
     }
@@ -394,9 +423,8 @@
             Log.w(TAG, "trying to update selection arrow without a selected view!");
             return;
         }
-        final int index = indexOfChild(mSelectedBubbleView);
         // Find the center of the bubble when it's expanded, set the arrow position to it.
-        final float tx = getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
+        final float tx = arrowPositionForSelectedWhenExpanded();
 
         if (shouldAnimate) {
             final float currentArrowPosition = mBubbleBarBackground.getArrowPositionX();
@@ -416,12 +444,27 @@
 
     private float arrowPositionForSelectedWhenExpanded() {
         final int index = indexOfChild(mSelectedBubbleView);
-        return getPaddingStart() + index * (mIconSize + mIconSpacing) + mIconSize / 2f;
+        final int bubblePosition;
+        if (isOnLeft()) {
+            // Bubble positions are reversed. First bubble is on the right.
+            bubblePosition = getChildCount() - index - 1;
+        } else {
+            bubblePosition = index;
+        }
+        return getPaddingStart() + bubblePosition * (mIconSize + mIconSpacing) + mIconSize / 2f;
     }
 
     private float arrowPositionForSelectedWhenCollapsed() {
         final int index = indexOfChild(mSelectedBubbleView);
-        return getPaddingStart() + index * (mIconOverlapAmount) + mIconSize / 2f;
+        final int bubblePosition;
+        if (isOnLeft()) {
+            // Bubble positions are reversed. First bubble may be shifted, if there are more
+            // bubbles than the current bubble and overflow.
+            bubblePosition = index == 0 && getChildCount() > 2 ? 1 : 0;
+        } else {
+            bubblePosition = index;
+        }
+        return getPaddingStart() + bubblePosition * (mIconOverlapAmount) + mIconSize / 2f;
     }
 
     @Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
index 065dd58..6bb7b04 100644
--- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java
@@ -21,6 +21,7 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Log;
+import android.view.Gravity;
 import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -289,7 +290,8 @@
      */
     public void addBubble(BubbleBarItem b) {
         if (b != null) {
-            mBarView.addView(b.getView(), 0, new FrameLayout.LayoutParams(mIconSize, mIconSize));
+            mBarView.addView(b.getView(), 0,
+                    new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
             b.getView().setOnClickListener(mBubbleClickListener);
             mBubbleDragController.setupBubbleView(b.getView());
         } else {