Update icon badges to match spec

- Size defined as percentage of app icon size
- Width changes when there are 2 or 3 digits (round rect)
- Offset slightly away from the app icon
  - Had to move drawing to BubbleTextView instead of
    FastBitmapDrawable
- Hide badge when dragging and while popup is open
- Tweaks for some color/text parameters

Bug: 35744066
Change-Id: Ibb15ca634abaa0729aea637c904c4c6889a58c7c
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index bad7018..97e93a0 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,6 +16,7 @@
 
 package com.android.launcher3;
 
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
@@ -23,9 +24,12 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Property;
 import android.util.TypedValue;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -42,6 +46,7 @@
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.HolographicOutlineHelper;
+import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.PreloadIconDrawable;
 import com.android.launcher3.model.PackageItemInfo;
 import com.android.launcher3.popup.PopupContainerWithArrow;
@@ -90,6 +95,28 @@
     @ViewDebug.ExportedProperty(category = "launcher")
     private int mTextColor;
 
+    private BadgeInfo mBadgeInfo;
+    private BadgeRenderer mBadgeRenderer;
+    private IconPalette mIconPalette;
+    private float mBadgeScale;
+    private boolean mForceHideBadge;
+    private Point mTempSpaceForBadgeOffset = new Point();
+    private Rect mTempIconBounds = new Rect();
+
+    private static final Property<BubbleTextView, Float> BADGE_SCALE_PROPERTY
+            = new Property<BubbleTextView, Float>(Float.TYPE, "badgeScale") {
+        @Override
+        public Float get(BubbleTextView bubbleTextView) {
+            return bubbleTextView.mBadgeScale;
+        }
+
+        @Override
+        public void set(BubbleTextView bubbleTextView, Float value) {
+            bubbleTextView.mBadgeScale = value;
+            bubbleTextView.invalidate();
+        }
+    };
+
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mStayPressed;
     @ViewDebug.ExportedProperty(category = "launcher")
@@ -369,6 +396,7 @@
     public void draw(Canvas canvas) {
         if (!mCustomShadowsEnabled) {
             super.draw(canvas);
+            drawBadgeIfNecessary(canvas);
             return;
         }
 
@@ -395,6 +423,7 @@
         if ((getCurrentTextColor() >> 24) == 0) {
             getPaint().clearShadowLayer();
             super.draw(canvas);
+            drawBadgeIfNecessary(canvas);
             return;
         }
 
@@ -410,6 +439,50 @@
                 density * KEY_SHADOW_RADIUS, 0.0f, density * KEY_SHADOW_OFFSET, KEY_SHADOW_COLOR);
         super.draw(canvas);
         canvas.restore();
+
+        drawBadgeIfNecessary(canvas);
+    }
+
+    /**
+     * Draws the icon badge in the top right corner of the icon bounds.
+     * @param canvas The canvas to draw to.
+     */
+    private void drawBadgeIfNecessary(Canvas canvas) {
+        if (!mForceHideBadge && (hasBadge() || mBadgeScale > 0)) {
+            getIconBounds(mTempIconBounds);
+            mTempSpaceForBadgeOffset.set((getWidth() - mIconSize) / 2, getPaddingTop());
+            final int scrollX = getScrollX();
+            final int scrollY = getScrollY();
+            canvas.translate(scrollX, scrollY);
+            mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, mTempIconBounds, mBadgeScale,
+                    mTempSpaceForBadgeOffset);
+            canvas.translate(-scrollX, -scrollY);
+        }
+    }
+
+    public void forceHideBadge(boolean forceHideBadge) {
+        if (mForceHideBadge == forceHideBadge) {
+            return;
+        }
+        mForceHideBadge = forceHideBadge;
+
+        if (forceHideBadge) {
+            invalidate();
+        } else if (hasBadge()) {
+            ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, 0, 1).start();
+        }
+    }
+
+    private boolean hasBadge() {
+        return (mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0);
+    }
+
+    public void getIconBounds(Rect outBounds) {
+        int top = getPaddingTop();
+        int left = (getWidth() - mIconSize) / 2;
+        int right = left + mIconSize;
+        int bottom = top + mIconSize;
+        outBounds.set(left, top, right, bottom);
     }
 
     @Override
@@ -506,7 +579,22 @@
             if (popup != null) {
                 popup.updateNotificationHeader(badgeInfo, itemInfo);
             }
-            ((FastBitmapDrawable) mIcon).applyIconBadge(badgeInfo, badgeRenderer, animate);
+
+            boolean wasBadged = mBadgeInfo != null;
+            boolean isBadged = badgeInfo != null;
+            float newBadgeScale = isBadged ? 1f : 0;
+            mBadgeInfo = badgeInfo;
+            mBadgeRenderer = badgeRenderer;
+            if (wasBadged || isBadged) {
+                mIconPalette = ((FastBitmapDrawable) mIcon).getIconPalette();
+                // Animate when a badge is first added or when it is removed.
+                if (animate && (wasBadged ^ isBadged) && isShown()) {
+                    ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
+                } else {
+                    mBadgeScale = newBadgeScale;
+                    invalidate();
+                }
+            }
         }
     }