Animate badges when they are added or removed

- Scale the badge and text or icon up or down, respectively.
- Only animate if the badge is visible, and don't animate when
  applying shortcut or app info.
- Animate folder badge out when folder enters accepting state.

Bug: 34838365
Bug: 32410600
Change-Id: Ie60cb1fc54fe60d72734d833040545d27660d645
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 95d2daf..1f74c88 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -30,7 +30,7 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
-import android.util.Log;
+import android.util.Property;
 import android.util.SparseArray;
 import android.view.animation.DecelerateInterpolator;
 
@@ -107,6 +107,21 @@
     private BadgeInfo mBadgeInfo;
     private BadgeRenderer mBadgeRenderer;
     private IconPalette mIconPalette;
+    private float mBadgeScale;
+
+    private static final Property<FastBitmapDrawable, Float> BADGE_SCALE_PROPERTY
+            = new Property<FastBitmapDrawable, Float>(Float.TYPE, "badgeScale") {
+        @Override
+        public Float get(FastBitmapDrawable fastBitmapDrawable) {
+            return fastBitmapDrawable.mBadgeScale;
+        }
+
+        @Override
+        public void set(FastBitmapDrawable fastBitmapDrawable, Float value) {
+            fastBitmapDrawable.mBadgeScale = value;
+            fastBitmapDrawable.invalidateSelf();
+        }
+    };
 
     // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
     // as a result, can be used to compose the key for the cached ColorMatrixColorFilters
@@ -123,14 +138,22 @@
         setFilterBitmap(true);
     }
 
-    public void applyIconBadge(BadgeInfo badgeInfo, BadgeRenderer badgeRenderer) {
+    public void applyIconBadge(final BadgeInfo badgeInfo, BadgeRenderer badgeRenderer,
+            boolean animate) {
         boolean wasBadged = mBadgeInfo != null;
         boolean isBadged = badgeInfo != null;
+        float newBadgeScale = isBadged ? 1f : 0;
         mBadgeInfo = badgeInfo;
         mBadgeRenderer = badgeRenderer;
         if (wasBadged || isBadged) {
             mIconPalette = getIconPalette();
-            invalidateSelf();
+            // Animate when a badge is first added or when it is removed.
+            if (animate && (wasBadged ^ isBadged) && isVisible()) {
+                ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start();
+            } else {
+                mBadgeScale = newBadgeScale;
+                invalidateSelf();
+            }
         }
     }
 
@@ -154,7 +177,7 @@
 
     protected void drawBadgeIfNecessary(Canvas canvas) {
         if (hasBadge()) {
-            mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, getBounds());
+            mBadgeRenderer.draw(canvas, mIconPalette, mBadgeInfo, getBounds(), mBadgeScale);
         }
     }
 
@@ -167,7 +190,7 @@
     }
 
     private boolean hasBadge() {
-        return mBadgeInfo != null && mBadgeInfo.getNotificationCount() != 0;
+        return (mBadgeInfo != null && mBadgeInfo.getNotificationCount() > 0) || mBadgeScale > 0;
     }
 
     @Override