Enable left badge, hidden badge

Manually composit badge on icon bitmap

Follow up cls to render dot and badge in separate views

Fixes: 161168537

Test: drag collapsed stack left and right
- top bubble moves badge right and left
- no badge for bubbles below

Test: expand stack from left and right
- stack and overflow bubbles have right-side badge

Change-Id: I7da824ef120033c65c9a0faee23ef2c2e5dc89a7
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
index a86a469..9f7358b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -17,9 +17,12 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.PaintFlagsDrawFilter;
 import android.graphics.Path;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.PathParser;
 import android.widget.ImageView;
@@ -30,6 +33,9 @@
 
 import java.util.EnumSet;
 
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
 /**
  * View that displays an adaptive icon with an app-badge and a dot.
  *
@@ -43,6 +49,8 @@
     public static final float WHITE_SCRIM_ALPHA = 0.54f;
     /** Same as value in Launcher3 IconShape */
     public static final int DEFAULT_PATH_SIZE = 100;
+    /** Same as value in Launcher3 BaseIconFactory */
+    private static final float ICON_BADGE_SCALE = 0.444f;
 
     /**
      * Flags that suppress the visibility of the 'new' dot, for one reason or another. If any of
@@ -69,6 +77,7 @@
     private BubbleViewProvider mBubble;
 
     private int mBubbleBitmapSize;
+    private int mBubbleSize;
     private DotRenderer mDotRenderer;
     private DotRenderer.DrawParams mDrawParams;
     private boolean mOnLeft;
@@ -93,6 +102,7 @@
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mBubbleBitmapSize = getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
+        mBubbleSize = getResources().getDimensionPixelSize(R.dimen.individual_bubble_size);
         mDrawParams = new DotRenderer.DrawParams();
 
         Path iconPath = PathParser.createPathFromPathData(
@@ -108,7 +118,7 @@
      */
     public void setRenderedBubble(BubbleViewProvider bubble) {
         mBubble = bubble;
-        setImageBitmap(bubble.getBadgedImage());
+        showBadge();
         mDotColor = bubble.getDotColor();
         drawDot(bubble.getDotPath());
     }
@@ -161,14 +171,6 @@
     }
 
     /**
-     * Set whether the dot should appear on left or right side of the view.
-     */
-    void setDotOnLeft(boolean onLeft) {
-        mOnLeft = onLeft;
-        invalidate();
-    }
-
-    /**
      * @param iconPath The new icon path to use when calculating dot position.
      */
     void drawDot(Path iconPath) {
@@ -219,22 +221,29 @@
         return mDotColor;
     }
 
-    /** Sets the position of the 'new' dot, animating it out and back in if requested. */
-    void setDotPositionOnLeft(boolean onLeft, boolean animate) {
-        if (animate && onLeft != getDotOnLeft() && shouldDrawDot()) {
+    /** Sets the position of the dot and badge, animating them out and back in if requested. */
+    void animateDotBadgePositions(boolean onLeft) {
+        mOnLeft = onLeft;
+
+        if (onLeft != getDotOnLeft() && shouldDrawDot()) {
             animateDotScale(0f /* showDot */, () -> {
-                setDotOnLeft(onLeft);
+                invalidate();
                 animateDotScale(1.0f, null /* after */);
             });
-        } else {
-            setDotOnLeft(onLeft);
         }
+        // TODO animate badge
+        showBadge();
+
     }
 
-    boolean getDotPositionOnLeft() {
-        return getDotOnLeft();
+    /** Sets the position of the dot and badge. */
+    void setDotBadgeOnLeft(boolean onLeft) {
+        mOnLeft = onLeft;
+        invalidate();
+        showBadge();
     }
 
+
     /** Whether to draw the dot in onDraw(). */
     private boolean shouldDrawDot() {
         // Always render the dot if it's animating, since it could be animating out. Otherwise, show
@@ -276,4 +285,33 @@
                     }
                 }).start();
     }
+
+    void showBadge() {
+        Drawable badge = mBubble.getAppBadge();
+        if (badge == null) {
+            setImageBitmap(mBubble.getBubbleIcon());
+            return;
+        }
+        Canvas bubbleCanvas = new Canvas();
+        Bitmap noBadgeBubble = mBubble.getBubbleIcon();
+        Bitmap bubble = noBadgeBubble.copy(noBadgeBubble.getConfig(), /* isMutable */ true);
+
+        bubbleCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+        bubbleCanvas.setBitmap(bubble);
+
+        final int badgeSize = (int) (ICON_BADGE_SCALE * mBubbleSize);
+        if (mOnLeft) {
+            badge.setBounds(0, mBubbleSize - badgeSize, badgeSize, mBubbleSize);
+        } else {
+            badge.setBounds(mBubbleSize - badgeSize, mBubbleSize - badgeSize,
+                    mBubbleSize, mBubbleSize);
+        }
+        badge.draw(bubbleCanvas);
+        bubbleCanvas.setBitmap(null);
+        setImageBitmap(bubble);
+    }
+
+    void hideBadge() {
+        setImageBitmap(mBubble.getBubbleIcon());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 62bc425..e6c1bc3 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -41,7 +41,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.InstanceId;
-import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 import java.io.FileDescriptor;
@@ -92,8 +91,9 @@
     }
 
     private FlyoutMessage mFlyoutMessage;
-    private Drawable mBadgedAppIcon;
-    private Bitmap mBadgedImage;
+    private Drawable mBadgeDrawable;
+    // Bitmap with no badge, no dot
+    private Bitmap mBubbleBitmap;
     private int mDotColor;
     private Path mDotPath;
     private int mFlags;
@@ -199,12 +199,13 @@
     }
 
     @Override
-    public Bitmap getBadgedImage() {
-        return mBadgedImage;
+    public Bitmap getBubbleIcon() {
+        return mBubbleBitmap;
     }
 
-    public Drawable getBadgedAppIcon() {
-        return mBadgedAppIcon;
+    @Override
+    public Drawable getAppBadge() {
+        return mBadgeDrawable;
     }
 
     @Override
@@ -340,8 +341,9 @@
         mAppName = info.appName;
         mFlyoutMessage = info.flyoutMessage;
 
-        mBadgedAppIcon = info.badgedAppIcon;
-        mBadgedImage = info.badgedBubbleImage;
+        mBadgeDrawable = info.badgeDrawable;
+        mBubbleBitmap = info.bubbleBitmap;
+
         mDotColor = info.dotColor;
         mDotPath = info.dotPath;
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
index d017bc0..371e849 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleIconFactory.java
@@ -156,17 +156,4 @@
         canvas.setBitmap(null);
         return bitmap;
     }
-
-    /**
-     * Returns a {@link BitmapInfo} for the entire bubble icon including the badge.
-     */
-    BitmapInfo getBubbleBitmap(Drawable bubble, BitmapInfo badge) {
-        BitmapInfo bubbleIconInfo = createBadgedIconBitmap(bubble,
-                null /* user */,
-                true /* shrinkNonAdaptiveIcons */);
-
-        badgeWithDrawable(bubbleIconInfo.icon,
-                new BitmapDrawable(mContext.getResources(), badge.icon));
-        return bubbleIconInfo;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
index 155b71b..6d3c2a6 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt
@@ -23,6 +23,7 @@
 import android.graphics.Path
 import android.graphics.drawable.AdaptiveIconDrawable
 import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
 import android.graphics.drawable.InsetDrawable
 import android.util.PathParser
 import android.util.TypedValue
@@ -36,8 +37,9 @@
     private val stack: BubbleStackView
 ) : BubbleViewProvider {
 
-    private var bitmap: Bitmap? = null
-    private var dotPath: Path? = null
+    private lateinit var bitmap : Bitmap
+    private lateinit var dotPath : Path
+
     private var bitmapSize = 0
     private var iconBitmapSize = 0
     private var dotColor = 0
@@ -80,40 +82,41 @@
         expandedView.updateDimensions()
     }
 
-    fun updateBtnTheme() {
+    private fun updateBtnTheme() {
         val res = context.resources
 
         // Set overflow button accent color, dot color
         val typedValue = TypedValue()
         context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true)
-
         val colorAccent = res.getColor(typedValue.resourceId)
-        overflowBtn.getDrawable()?.setTint(colorAccent)
+        overflowBtn.drawable?.setTint(colorAccent)
         dotColor = colorAccent
 
-        // Set button and activity background color
-        val nightMode = (res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
-                == Configuration.UI_MODE_NIGHT_YES)
-        val bg = ColorDrawable(res.getColor(
-                if (nightMode) R.color.bubbles_dark else R.color.bubbles_light))
-
-        // Set button icon
         val iconFactory = BubbleIconFactory(context)
-        val fg = InsetDrawable(overflowBtn.getDrawable(),
-                bitmapSize - iconBitmapSize /* inset */)
-        bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(bg, fg),
-                null /* user */, true /* shrinkNonAdaptiveIcons */).icon
 
-        // Set dot path
+        // Update bitmap
+        val nightMode = (res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+            == Configuration.UI_MODE_NIGHT_YES)
+        val bg = ColorDrawable(res.getColor(
+            if (nightMode) R.color.bubbles_dark else R.color.bubbles_light))
+
+        val fg = InsetDrawable(overflowBtn.drawable,
+            bitmapSize - iconBitmapSize /* inset */)
+        bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(bg, fg),
+            null /* user */, true /* shrinkNonAdaptiveIcons */).icon
+
+        // Update dot path
         dotPath = PathParser.createPathFromPathData(
-                res.getString(com.android.internal.R.string.config_icon_mask))
+            res.getString(com.android.internal.R.string.config_icon_mask))
         val scale = iconFactory.normalizer.getScale(overflowBtn.getDrawable(),
-                null /* outBounds */, null /* path */, null /* outMaskShape */)
+            null /* outBounds */, null /* path */, null /* outMaskShape */)
         val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
         val matrix = Matrix()
         matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
-                radius /* pivot y */)
-        dotPath?.transform(matrix)
+            radius /* pivot y */)
+        dotPath.transform(matrix)
+
+        // Attach BubbleOverflow to BadgedImageView
         overflowBtn.setRenderedBubble(this)
     }
 
@@ -129,7 +132,11 @@
         return dotColor
     }
 
-    override fun getBadgedImage(): Bitmap? {
+    override fun getAppBadge(): Drawable? {
+        return null
+    }
+
+    override fun getBubbleIcon(): Bitmap {
         return bitmap
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index c1b6882..55f9631 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -16,13 +16,6 @@
 
 package com.android.systemui.bubbles;
 
-import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
-
-import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
-import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
-
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -99,6 +92,12 @@
 import java.util.List;
 import java.util.function.Consumer;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
 /**
  * Renders bubbles in a stack and handles animating expanded and collapsed states.
  */
@@ -658,13 +657,10 @@
                     mStackOnLeftOrWillBe =
                             mStackAnimationController.flingStackThenSpringToEdge(
                                     viewInitialX + dx, velX, velY) <= 0;
-
-                    updateBubbleZOrdersAndDotPosition(true /* animate */);
-
+                    updateBubbleIcons();
                     logBubbleEvent(null /* no bubble associated with bubble stack move */,
                             SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
                 }
-
                 mDismissView.hide();
             }
 
@@ -1468,8 +1464,7 @@
 
         // Set the dot position to the opposite of the side the stack is resting on, since the stack
         // resting slightly off-screen would result in the dot also being off-screen.
-        bubble.getIconView().setDotPositionOnLeft(
-                !mStackOnLeftOrWillBe /* onLeft */, false /* animate */);
+        bubble.getIconView().setDotBadgeOnLeft(!mStackOnLeftOrWillBe /* onLeft */);
 
         bubble.getIconView().setOnClickListener(mBubbleClickListener);
         bubble.getIconView().setOnTouchListener(mBubbleTouchListener);
@@ -1520,7 +1515,7 @@
             Bubble bubble = bubbles.get(i);
             mBubbleContainer.reorderView(bubble.getIconView(), i);
         }
-        updateBubbleZOrdersAndDotPosition(false /* animate */);
+        updateBubbleIcons();
         updatePointerPosition();
     }
 
@@ -2363,7 +2358,7 @@
         // name and icon.
         if (show && mBubbleData.hasBubbleInStackWithKey(mExpandedBubble.getKey())) {
             final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey());
-            mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon());
+            mManageSettingsIcon.setImageDrawable(bubble.getAppBadge());
             mManageSettingsText.setText(getResources().getString(
                     R.string.bubbles_app_settings, bubble.getAppName()));
         }
@@ -2551,28 +2546,31 @@
         }
 
         mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
-        updateBubbleZOrdersAndDotPosition(false);
+        updateBubbleIcons();
     }
 
-    /** Sets the appropriate Z-order and dot position for each bubble in the stack. */
-    private void updateBubbleZOrdersAndDotPosition(boolean animate) {
+    /**
+     * Sets the appropriate Z-order, badge, and dot position for each bubble in the stack.
+     * Animate dot and badge changes.
+     */
+    private void updateBubbleIcons() {
         int bubbleCount = getBubbleCount();
         for (int i = 0; i < bubbleCount; i++) {
             BadgedImageView bv = (BadgedImageView) mBubbleContainer.getChildAt(i);
             bv.setZ((mMaxBubbles * mBubbleElevation) - i);
 
-            // If the dot is on the left, and so is the stack, we need to change the dot position.
-            if (bv.getDotPositionOnLeft() == mStackOnLeftOrWillBe) {
-                bv.setDotPositionOnLeft(!mStackOnLeftOrWillBe, animate);
-            }
-
-            if (!mIsExpanded && i > 0) {
-                // If we're collapsed and this bubble is behind other bubbles, suppress its dot.
-                bv.addDotSuppressionFlag(
-                        BadgedImageView.SuppressionFlag.BEHIND_STACK);
-            } else {
+            if (mIsExpanded) {
                 bv.removeDotSuppressionFlag(
                         BadgedImageView.SuppressionFlag.BEHIND_STACK);
+                bv.animateDotBadgePositions(false /* onLeft */);
+            } else if (i == 0) {
+                bv.removeDotSuppressionFlag(
+                        BadgedImageView.SuppressionFlag.BEHIND_STACK);
+                bv.animateDotBadgePositions(!mStackOnLeftOrWillBe);
+            } else {
+                bv.addDotSuppressionFlag(
+                        BadgedImageView.SuppressionFlag.BEHIND_STACK);
+                bv.hideBadge();
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 5749169..28757fa 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -120,8 +120,8 @@
         BubbleExpandedView expandedView;
         ShortcutInfo shortcutInfo;
         String appName;
-        Bitmap badgedBubbleImage;
-        Drawable badgedAppIcon;
+        Bitmap bubbleBitmap;
+        Drawable badgeDrawable;
         int dotColor;
         Path dotPath;
         Bubble.FlyoutMessage flyoutMessage;
@@ -179,9 +179,10 @@
 
             BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
                     b.isImportantConversation());
-            info.badgedAppIcon = badgedIcon;
-            info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
-                    badgeBitmapInfo).icon;
+            info.badgeDrawable = badgedIcon;
+            info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable,
+                    null /* user */,
+                    true /* shrinkNonAdaptiveIcons */).icon;
 
             // Dot color & placement
             Path iconPath = PathParser.createPathFromPathData(
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
index f1a01be..916ad18 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Path;
+import android.graphics.drawable.Drawable;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -34,12 +35,17 @@
 
     String getKey();
 
-    Bitmap getBadgedImage();
+    /** Bubble icon bitmap with no badge and no dot. */
+    Bitmap getBubbleIcon();
+
+    /** App badge drawable to draw above bubble icon. */
+    @Nullable Drawable getAppBadge();
+
+    /** Path of normalized bubble icon to draw dot on. */
+    Path getDotPath();
 
     int getDotColor();
 
-    Path getDotPath();
-
     boolean showDot();
 
     int getDisplayId();