Merge "setVisibility should not propagate focus on children views" into ub-launcher3-master
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index a318574..882af59 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -101,11 +101,11 @@
     }
 
     public BitmapInfo createIconBitmap(Bitmap icon) {
-        if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
-            return BitmapInfo.fromBitmap(icon);
+        if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) {
+            icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
         }
-        return BitmapInfo.fromBitmap(
-                createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
+
+        return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor);
     }
 
     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
diff --git a/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
new file mode 100644
index 0000000..6bc859a
--- /dev/null
+++ b/iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.icons;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Log;
+import android.view.ViewDebug;
+
+/**
+ * Used to draw a notification dot on top of an icon.
+ */
+public class DotRenderer {
+
+    private static final String TAG = "DotRenderer";
+
+    // The dot size is defined as a percentage of the app icon size.
+    private static final float SIZE_PERCENTAGE = 0.38f;
+
+    // Extra scale down of the dot
+    private static final float DOT_SCALE = 0.6f;
+
+    // Offset the dot slightly away from the icon if there's space.
+    private static final float OFFSET_PERCENTAGE = 0.02f;
+
+    private final float mDotCenterOffset;
+    private final int mOffset;
+    private final float mCircleRadius;
+    private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
+
+    private final Bitmap mBackgroundWithShadow;
+    private final float mBitmapOffset;
+
+    public DotRenderer(int iconSizePx) {
+        mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
+        mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
+
+        int size = (int) (DOT_SCALE * mDotCenterOffset);
+        ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
+        builder.ambientShadowAlpha = 88;
+        mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
+        mCircleRadius = builder.radius;
+
+        mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
+    }
+
+    /**
+     * Draw a circle on top of the canvas according to the given params.
+     */
+    public void draw(Canvas canvas, DrawParams params) {
+        if (params == null) {
+            Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
+            return;
+        }
+        canvas.save();
+        // We draw the dot relative to its center.
+        float dotCenterX = params.leftAlign
+                ? params.iconBounds.left + mDotCenterOffset / 2
+                : params.iconBounds.right - mDotCenterOffset / 2;
+        float dotCenterY = params.iconBounds.top + mDotCenterOffset / 2;
+
+        int offsetX = Math.min(mOffset, params.spaceForOffset.x);
+        int offsetY = Math.min(mOffset, params.spaceForOffset.y);
+        canvas.translate(dotCenterX + offsetX, dotCenterY - offsetY);
+        canvas.scale(params.scale, params.scale);
+
+        mCirclePaint.setColor(Color.BLACK);
+        canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
+        mCirclePaint.setColor(params.color);
+        canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
+        canvas.restore();
+    }
+
+    public static class DrawParams {
+        /** The color (possibly based on the icon) to use for the dot. */
+        @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true)
+        public int color;
+        /** The bounds of the icon that the dot is drawn on top of. */
+        @ViewDebug.ExportedProperty(category = "notification dot")
+        public Rect iconBounds = new Rect();
+        /** The progress of the animation, from 0 to 1. */
+        @ViewDebug.ExportedProperty(category = "notification dot")
+        public float scale;
+        /** Overrides internally calculated offset if specified value is smaller. */
+        @ViewDebug.ExportedProperty(category = "notification dot")
+        public Point spaceForOffset = new Point();
+        /** Whether the dot should align to the top left of the icon rather than the top right. */
+        @ViewDebug.ExportedProperty(category = "notification dot")
+        public boolean leftAlign;
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
index 54269f09..753f73a 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java
@@ -243,7 +243,7 @@
             fling = false;
         }
         float progress = mCurrentAnimation.getProgressFraction();
-        float interpolatedProgress = mCurrentAnimation.getInterpolator().getInterpolation(progress);
+        float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
         if (fling) {
             logAction = Touch.FLING;
             boolean goingUp = velocity < 0;
diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
index 02af0d6..b96f9f0 100644
--- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java
+++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java
@@ -29,6 +29,7 @@
 import static com.android.quickstep.views.RecentsView.CONTENT_ALPHA;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_BACK;
 import static com.android.systemui.shared.system.NavigationBarCompat.HIT_TARGET_ROTATION;
+
 import android.animation.Animator;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
@@ -43,8 +44,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.view.View;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
+
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.DeviceProfile;
 import com.android.launcher3.Launcher;
@@ -77,6 +77,9 @@
 import java.util.function.BiPredicate;
 import java.util.function.Consumer;
 
+import androidx.annotation.Nullable;
+import androidx.annotation.UiThread;
+
 /**
  * Utility class which abstracts out the logical differences between Launcher and RecentsActivity.
  */
@@ -305,9 +308,10 @@
                     AnimatorPlaybackController.wrap(anim, transitionLength * 2);
 
             // Since we are changing the start position of the UI, reapply the state, at the end
-            controller.setEndAction(() ->
+            controller.setEndAction(() -> {
                 activity.getStateManager().goToState(
-                        controller.getProgressFraction() > 0.5 ? endState : fromState, false));
+                        controller.getInterpolatedProgress() > 0.5 ? endState : fromState, false);
+            });
             callback.accept(controller);
         }
 
diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
index b0d7ff3..fe3300d 100644
--- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -825,6 +825,7 @@
                 return;
             }
             if (start == end || duration <= 0) {
+                mLauncherTransitionController.dispatchSetInterpolator(t -> end);
                 mLauncherTransitionController.getAnimationPlayer().end();
             } else {
                 // Adjust start progress and duration in case we are on a different thread.
diff --git a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
index 4e89353..4e7a0c4 100644
--- a/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
+++ b/quickstep/src/com/android/quickstep/views/DigitalWellBeingToast.java
@@ -34,7 +34,7 @@
 
 public final class DigitalWellBeingToast extends LinearLayout {
     public interface InitializeCallback {
-        void call(long t, boolean b);
+        void call(long remainingTimeMs, boolean isGroupLimit, String contentDescription);
     }
 
     private static final String TAG = DigitalWellBeingToast.class.getSimpleName();
@@ -66,7 +66,10 @@
             final boolean isGroupLimit = true;
             post(() -> {
                 setRemainingTime(appRemainingTimeMs, isGroupLimit);
-                callback.call(appRemainingTimeMs, isGroupLimit);
+                callback.call(
+                        appRemainingTimeMs,
+                        isGroupLimit,
+                        getContentDescriptionForTask(task, appRemainingTimeMs, isGroupLimit));
             });
         });
     }
@@ -94,4 +97,14 @@
                     + mTask.getTopComponent().getPackageName(), e);
         }
     }
+
+    private String getContentDescriptionForTask(
+            Task task, long appRemainingTimeMs, boolean isGroupLimit) {
+        return appRemainingTimeMs > 0 ?
+                getResources().getString(
+                        R.string.task_contents_description_with_remaining_time,
+                        task.titleDescription,
+                        getText(appRemainingTimeMs, isGroupLimit)) :
+                task.titleDescription;
+    }
 }
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index a6a3b85..9b26d08 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -268,14 +268,9 @@
                         setIcon(task.icon);
                         mDigitalWellBeingToast.initialize(
                                 mTask,
-                                (appRemainingTimeMs, isGroupLimit) -> {
+                                (appRemainingTimeMs, isGroupLimit, contentDescription) -> {
                                     mAppRemainingTimeMs = appRemainingTimeMs;
-                                    setContentDescription(
-                                            hasRemainingTime() ?
-                                                    task.titleDescription + ". "
-                                                            + DigitalWellBeingToast.getText(
-                                                            appRemainingTimeMs, isGroupLimit) :
-                                                    task.titleDescription);
+                                    setContentDescription(contentDescription);
                                 });
                     });
         } else {
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7e5784d..fb6eb43 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -350,4 +350,6 @@
 
     <!-- Failed action error message: e.g. Failed: Pause -->
     <string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
+
+    <string name="task_contents_description_with_remaining_time" translatable="false"><xliff:g id="task_description" example="GMail">%1$s</xliff:g>, <xliff:g id="remaining_time" example="7 minutes">%2$s</xliff:g></string>
 </resources>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index f20ded3..9b73daf 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -27,7 +27,6 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -44,11 +43,11 @@
 
 import com.android.launcher3.Launcher.OnResumeCallback;
 import com.android.launcher3.badge.BadgeInfo;
-import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DrawableFactory;
 import com.android.launcher3.graphics.IconPalette;
 import com.android.launcher3.graphics.PreloadIconDrawable;
+import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconCache.IconLoadRequest;
 import com.android.launcher3.icons.IconCache.ItemInfoUpdateReceiver;
 import com.android.launcher3.model.PackageItemInfo;
@@ -74,12 +73,12 @@
             = new Property<BubbleTextView, Float>(Float.TYPE, "badgeScale") {
         @Override
         public Float get(BubbleTextView bubbleTextView) {
-            return bubbleTextView.mBadgeScale;
+            return bubbleTextView.mDotParams.scale;
         }
 
         @Override
         public void set(BubbleTextView bubbleTextView, Float value) {
-            bubbleTextView.mBadgeScale = value;
+            bubbleTextView.mDotParams.scale = value;
             bubbleTextView.invalidate();
         }
     };
@@ -117,14 +116,11 @@
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private BadgeInfo mBadgeInfo;
-    private BadgeRenderer mBadgeRenderer;
-    private int mBadgeColor;
-    @ViewDebug.ExportedProperty(category = "launcher")
-    private float mBadgeScale;
+    private DotRenderer mDotRenderer;
+    @ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
+    private DotRenderer.DrawParams mDotParams;
     private Animator mBadgeScaleAnim;
     private boolean mForceHideBadge;
-    private Point mTempSpaceForBadgeOffset = new Point();
-    private Rect mTempIconBounds = new Rect();
 
     @ViewDebug.ExportedProperty(category = "launcher")
     private boolean mStayPressed;
@@ -176,6 +172,8 @@
         mLongPressHelper = new CheckLongPressHelper(this);
         mStylusEventHelper = new StylusEventHelper(new SimpleOnStylusPressListener(this), this);
 
+        mDotParams = new DotRenderer.DrawParams();
+
         setEllipsize(TruncateAt.END);
         setAccessibilityDelegate(mActivity.getAccessibilityDelegate());
         setTextAlpha(1f);
@@ -193,9 +191,9 @@
      */
     public void reset() {
         mBadgeInfo = null;
-        mBadgeColor = Color.TRANSPARENT;
+        mDotParams.color = Color.TRANSPARENT;
         cancelBadgeScaleAnim();
-        mBadgeScale = 0f;
+        mDotParams.scale = 0f;
         mForceHideBadge = false;
     }
 
@@ -259,7 +257,7 @@
     private void applyIconAndLabel(ItemInfoWithIcon info) {
         FastBitmapDrawable iconDrawable = DrawableFactory.INSTANCE.get(getContext())
                 .newIcon(getContext(), info);
-        mBadgeColor = IconPalette.getMutedColor(info.iconColor, 0.54f);
+        mDotParams.color = IconPalette.getMutedColor(info.iconColor, 0.54f);
 
         setIcon(iconDrawable);
         setText(info.title);
@@ -383,14 +381,13 @@
      * @param canvas The canvas to draw to.
      */
     protected void drawBadgeIfNecessary(Canvas canvas) {
-        if (!mForceHideBadge && (hasBadge() || mBadgeScale > 0)) {
-            getIconBounds(mTempIconBounds);
-            mTempSpaceForBadgeOffset.set((getWidth() - mIconSize) / 2, getPaddingTop());
+        if (!mForceHideBadge && (hasBadge() || mDotParams.scale > 0)) {
+            getIconBounds(mDotParams.iconBounds);
+            mDotParams.spaceForOffset.set((getWidth() - mIconSize) / 2, getPaddingTop());
             final int scrollX = getScrollX();
             final int scrollY = getScrollY();
             canvas.translate(scrollX, scrollY);
-            mBadgeRenderer.draw(canvas, mBadgeColor, mTempIconBounds, mBadgeScale,
-                    mTempSpaceForBadgeOffset);
+            mDotRenderer.draw(canvas, mDotParams);
             canvas.translate(-scrollX, -scrollY);
         }
     }
@@ -545,14 +542,14 @@
             mBadgeInfo = mActivity.getBadgeInfoForItem(itemInfo);
             boolean isBadged = mBadgeInfo != null;
             float newBadgeScale = isBadged ? 1f : 0;
-            mBadgeRenderer = mActivity.getDeviceProfile().mBadgeRenderer;
+            mDotRenderer = mActivity.getDeviceProfile().mDotRenderer;
             if (wasBadged || isBadged) {
                 // Animate when a badge is first added or when it is removed.
                 if (animate && (wasBadged ^ isBadged) && isShown()) {
                     animateBadgeScale(newBadgeScale);
                 } else {
                     cancelBadgeScaleAnim();
-                    mBadgeScale = newBadgeScale;
+                    mDotParams.scale = newBadgeScale;
                     invalidate();
                 }
             }
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 256fd6c..42f913c 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -29,7 +29,7 @@
 import android.view.WindowManager;
 
 import com.android.launcher3.CellLayout.ContainerType;
-import com.android.launcher3.badge.BadgeRenderer;
+import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 
 public class DeviceProfile {
@@ -129,7 +129,7 @@
     private boolean mIsSeascape;
 
     // Icon badges
-    public BadgeRenderer mBadgeRenderer;
+    public DotRenderer mDotRenderer;
 
     public DeviceProfile(Context context, InvariantDeviceProfile inv,
             Point minSize, Point maxSize,
@@ -233,7 +233,7 @@
         updateWorkspacePadding();
 
         // This is done last, after iconSizePx is calculated above.
-        mBadgeRenderer = new BadgeRenderer(iconSizePx);
+        mDotRenderer = new DotRenderer(iconSizePx);
     }
 
     public DeviceProfile copy(Context context) {
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 164728a..819c843 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -154,6 +154,10 @@
         return mCurrentFraction;
     }
 
+    public float getInterpolatedProgress() {
+        return getInterpolator().getInterpolation(mCurrentFraction);
+    }
+
     /**
      * Sets the action to be called when the animation is completed. Also clears any
      * previously set action.
diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java
deleted file mode 100644
index 5d38aad..0000000
--- a/src/com/android/launcher3/badge/BadgeRenderer.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.launcher3.badge;
-
-import static android.graphics.Paint.ANTI_ALIAS_FLAG;
-import static android.graphics.Paint.FILTER_BITMAP_FLAG;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Log;
-
-import com.android.launcher3.icons.ShadowGenerator;
-
-/**
- * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
- * @see BadgeInfo for the data to draw
- */
-public class BadgeRenderer {
-
-    private static final String TAG = "BadgeRenderer";
-
-    // The badge sizes are defined as percentages of the app icon size.
-    private static final float SIZE_PERCENTAGE = 0.38f;
-
-    // Extra scale down of the dot
-    private static final float DOT_SCALE = 0.6f;
-
-    // Used to expand the width of the badge for each additional digit.
-    private static final float OFFSET_PERCENTAGE = 0.02f;
-
-    private final float mDotCenterOffset;
-    private final int mOffset;
-    private final float mCircleRadius;
-    private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
-
-    private final Bitmap mBackgroundWithShadow;
-    private final float mBitmapOffset;
-
-    public BadgeRenderer(int iconSizePx) {
-        mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
-        mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx);
-
-        int size = (int) (DOT_SCALE * mDotCenterOffset);
-        ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT);
-        builder.ambientShadowAlpha = 88;
-        mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size);
-        mCircleRadius = builder.radius;
-
-        mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width.
-    }
-
-    /**
-     * Draw a circle in the top right corner of the given bounds, and draw
-     * {@link BadgeInfo#getNotificationCount()} on top of the circle.
-     * @param color The color (based on the icon) to use for the badge.
-     * @param iconBounds The bounds of the icon being badged.
-     * @param badgeScale The progress of the animation, from 0 to 1.
-     * @param spaceForOffset How much space is available to offset the badge up and to the right.
-     */
-    public void draw(
-            Canvas canvas, int color, Rect iconBounds, float badgeScale, Point spaceForOffset) {
-        if (iconBounds == null || spaceForOffset == null) {
-            Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
-            return;
-        }
-        canvas.save();
-        // We draw the badge relative to its center.
-        float badgeCenterX = iconBounds.right - mDotCenterOffset / 2;
-        float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
-
-        int offsetX = Math.min(mOffset, spaceForOffset.x);
-        int offsetY = Math.min(mOffset, spaceForOffset.y);
-        canvas.translate(badgeCenterX + offsetX, badgeCenterY - offsetY);
-        canvas.scale(badgeScale, badgeScale);
-
-        mCirclePaint.setColor(Color.BLACK);
-        canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint);
-        mCirclePaint.setColor(color);
-        canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
-        canvas.restore();
-    }
-}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 429d44f..50d1442 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -24,7 +24,6 @@
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -57,11 +56,11 @@
 import com.android.launcher3.Utilities;
 import com.android.launcher3.Workspace;
 import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.badge.BadgeRenderer;
 import com.android.launcher3.badge.FolderBadgeInfo;
 import com.android.launcher3.dragndrop.BaseItemDragListener;
 import com.android.launcher3.dragndrop.DragLayer;
 import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.touch.ItemClickHandler;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddShortcutInfo;
@@ -111,11 +110,11 @@
 
     @ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
     private FolderBadgeInfo mBadgeInfo = new FolderBadgeInfo();
-    private BadgeRenderer mBadgeRenderer;
-    @ViewDebug.ExportedProperty(category = "launcher")
+    private DotRenderer mDotRenderer;
+    @ViewDebug.ExportedProperty(category = "launcher", deepExport = true)
+    private DotRenderer.DrawParams mDotParams;
     private float mBadgeScale;
     private Animator mBadgeScaleAnim;
-    private Point mTempSpaceForBadgeOffset = new Point();
 
     private static final Property<FolderIcon, Float> BADGE_SCALE_PROPERTY
             = new Property<FolderIcon, Float>(Float.TYPE, "badgeScale") {
@@ -147,6 +146,7 @@
         mPreviewLayoutRule = new ClippedFolderIconLayoutRule();
         mSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
         mPreviewItemManager = new PreviewItemManager(this);
+        mDotParams = new DotRenderer.DrawParams();
     }
 
     public static FolderIcon fromXml(int resId, Launcher launcher, ViewGroup group,
@@ -174,7 +174,7 @@
         icon.setOnClickListener(ItemClickHandler.INSTANCE);
         icon.mInfo = folderInfo;
         icon.mLauncher = launcher;
-        icon.mBadgeRenderer = launcher.getDeviceProfile().mBadgeRenderer;
+        icon.mDotRenderer = launcher.getDeviceProfile().mDotRenderer;
         icon.setContentDescription(launcher.getString(R.string.folder_name_format, folderInfo.title));
         Folder folder = Folder.fromXml(launcher);
         folder.setDragController(launcher.getDragController());
@@ -492,13 +492,14 @@
 
     public void drawBadge(Canvas canvas) {
         if ((mBadgeInfo != null && mBadgeInfo.hasBadge()) || mBadgeScale > 0) {
-            BubbleTextView.getIconBounds(this, mTempBounds, mLauncher.getDeviceProfile().iconSizePx);
+            Rect iconBounds = mDotParams.iconBounds;
+            BubbleTextView.getIconBounds(this, iconBounds, mLauncher.getDeviceProfile().iconSizePx);
 
             // If we are animating to the accepting state, animate the badge out.
-            float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
-            mTempSpaceForBadgeOffset.set(getWidth() - mTempBounds.right, mTempBounds.top);
-            mBadgeRenderer.draw(canvas, mBackground.getBadgeColor(), mTempBounds,
-                    badgeScale, mTempSpaceForBadgeOffset);
+            mDotParams.scale = Math.max(0, mBadgeScale - mBackground.getScaleProgress());
+            mDotParams.spaceForOffset.set(getWidth() - iconBounds.right, iconBounds.top);
+            mDotParams.color = mBackground.getBadgeColor();
+            mDotRenderer.draw(canvas, mDotParams);
         }
     }
 
diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java
index 4b869cf..f0a63ba 100644
--- a/src/com/android/launcher3/icons/LauncherIcons.java
+++ b/src/com/android/launcher3/icons/LauncherIcons.java
@@ -114,7 +114,7 @@
     }
 
     public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo,
-            boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
+            boolean badged, @Nullable Provider<ItemInfoWithIcon> fallbackIconProvider) {
         Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
                 .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
         IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
@@ -125,9 +125,12 @@
         } else {
             if (fallbackIconProvider != null) {
                 // Fallback icons are already badged and with appropriate shadow
-                Bitmap fullIcon = fallbackIconProvider.get();
-                if (fullIcon != null) {
-                    return createIconBitmap(fullIcon);
+                ItemInfoWithIcon fullIcon = fallbackIconProvider.get();
+                if (fullIcon != null && fullIcon.iconBitmap != null) {
+                    BitmapInfo result = new BitmapInfo();
+                    result.icon = fullIcon.iconBitmap;
+                    result.color = fullIcon.iconColor;
+                    return result;
                 }
             }
             unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index 3aeb1c0..8efebf1 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -165,6 +165,15 @@
      * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
      */
     protected boolean loadIcon(ShortcutInfo info) {
+        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+            return loadIcon(info, li);
+        }
+    }
+
+    /**
+     * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource.
+     */
+    protected boolean loadIcon(ShortcutInfo info, LauncherIcons li) {
         if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
             String packageName = getString(iconPackageIndex);
             String resourceName = getString(iconResourceIndex);
@@ -172,9 +181,7 @@
                 info.iconResource = new ShortcutIconResource();
                 info.iconResource.packageName = packageName;
                 info.iconResource.resourceName = resourceName;
-                LauncherIcons li = LauncherIcons.obtain(mContext);
                 BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
-                li.recycle();
                 if (iconInfo != null) {
                     info.applyFrom(iconInfo);
                     return true;
@@ -184,11 +191,11 @@
 
         // Failed to load from resource, try loading from DB.
         byte[] data = getBlob(iconIndex);
-        try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+        try {
             info.applyFrom(li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)));
             return true;
         } catch (Exception e) {
-            Log.e(TAG, "Failed to load icon for info " + info, e);
+            Log.e(TAG, "Failed to decode byte array for info " + info, e);
             return false;
         }
     }
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 2ecebb7..f711787 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -43,6 +43,7 @@
 import com.android.launcher3.AllAppsList;
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfoWithIcon;
 import com.android.launcher3.icons.ComponentWithLabel;
 import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
 import com.android.launcher3.icons.cache.IconCacheUpdateHandler;
@@ -493,12 +494,12 @@
                                     }
                                     info = new ShortcutInfo(pinnedShortcut, context);
                                     final ShortcutInfo finalInfo = info;
-                                    // If the pinned deep shortcut is no longer published,
-                                    // use the last saved icon instead of the default.
-                                    Provider<Bitmap> fallbackIconProvider = () ->
-                                            c.loadIcon(finalInfo) ? finalInfo.iconBitmap : null;
 
                                     LauncherIcons li = LauncherIcons.obtain(context);
+                                    // If the pinned deep shortcut is no longer published,
+                                    // use the last saved icon instead of the default.
+                                    Provider<ItemInfoWithIcon> fallbackIconProvider = () ->
+                                            c.loadIcon(finalInfo, li) ? finalInfo : null;
                                     info.applyFrom(li.createShortcutIcon(pinnedShortcut,
                                             true /* badged */, fallbackIconProvider));
                                     li.recycle();
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index e99fed9..4e699f7 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -97,7 +97,7 @@
                     // keep the current icon instead of reverting to the default icon.
                     LauncherIcons li = LauncherIcons.obtain(context);
                     shortcutInfo.applyFrom(li.createShortcutIcon(fullDetails, true,
-                            Provider.of(shortcutInfo.iconBitmap)));
+                            Provider.of(shortcutInfo)));
                     li.recycle();
                     updatedShortcutInfos.add(shortcutInfo);
                 }
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index 8e7557a..59f4284 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -95,7 +95,7 @@
                     // If the shortcut is pinned but no longer has an icon in the system,
                     // keep the current icon instead of reverting to the default icon.
                     LauncherIcons li = LauncherIcons.obtain(context);
-                    si.applyFrom(li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)));
+                    si.applyFrom(li.createShortcutIcon(shortcut, true, Provider.of(si)));
                     li.recycle();
                 } else {
                     si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index ce1cc89..a7bd243 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -372,8 +372,7 @@
 
         final LauncherState targetState;
         final float progress = mCurrentAnimation.getProgressFraction();
-        final float interpolatedProgress = mCurrentAnimation.getInterpolator()
-                .getInterpolation(progress);
+        final float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
         if (fling) {
             targetState =
                     Float.compare(Math.signum(velocity), Math.signum(mProgressMultiplier)) == 0