Create iconloader library

Bug: 115891474
Test: Builds everything

Change-Id: I1d75702d4e5a10d694eeb839784a629de2f74dd2
diff --git a/iconloaderlib/Android.bp b/iconloaderlib/Android.bp
new file mode 100644
index 0000000..8a71f94
--- /dev/null
+++ b/iconloaderlib/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2018 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.
+
+android_library {
+    name: "iconloader",
+    sdk_version: "28",
+    min_sdk_version: "21",
+    static_libs: [
+        "androidx.core_core",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+}
diff --git a/iconloaderlib/AndroidManifest.xml b/iconloaderlib/AndroidManifest.xml
new file mode 100644
index 0000000..b30258d
--- /dev/null
+++ b/iconloaderlib/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2018 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.launcher3.icons">
+</manifest>
diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle
new file mode 100644
index 0000000..d080293
--- /dev/null
+++ b/iconloaderlib/build.gradle
@@ -0,0 +1,50 @@
+buildscript {
+    repositories {
+        mavenCentral()
+        google()
+    }
+    dependencies {
+        classpath GRADLE_CLASS_PATH
+    }
+}
+
+apply plugin: 'com.android.library'
+
+android {
+    compileSdkVersion COMPILE_SDK.toInteger()
+    buildToolsVersion BUILD_TOOLS_VERSION
+    publishNonDefault true
+
+    defaultConfig {
+        minSdkVersion 21
+        targetSdkVersion 28
+        versionCode 1
+        versionName "1.0"
+    }
+
+    sourceSets {
+        main {
+            java.srcDirs = ['src']
+            manifest.srcFile 'AndroidManifest.xml'
+            res.srcDirs = ['res']
+        }
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+
+    tasks.withType(JavaCompile) {
+        options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
+    }
+}
+
+
+repositories {
+    mavenCentral()
+    google()
+}
+
+dependencies {
+    implementation "androidx.core:core:${ANDROID_X_VERSION}"
+}
diff --git a/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
new file mode 100644
index 0000000..9f13cf5
--- /dev/null
+++ b/iconloaderlib/res/drawable-v26/adaptive_icon_drawable_wrapper.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     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.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/legacy_icon_background"/>
+    <foreground>
+        <com.android.launcher3.icons.FixedScaleDrawable />
+    </foreground>
+</adaptive-icon>
diff --git a/iconloaderlib/res/drawable/ic_instant_app_badge.xml b/iconloaderlib/res/drawable/ic_instant_app_badge.xml
new file mode 100644
index 0000000..b74317e
--- /dev/null
+++ b/iconloaderlib/res/drawable/ic_instant_app_badge.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="@dimen/profile_badge_size"
+    android:height="@dimen/profile_badge_size"
+    android:viewportWidth="18"
+    android:viewportHeight="18">
+
+    <path
+        android:fillColor="@android:color/black"
+        android:strokeWidth="1"
+        android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:strokeWidth="1"
+        android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
+    <path
+        android:fillColor="@android:color/white"
+        android:strokeWidth="1"
+        android:pathData="M 9 0 C 13.9705627485 0 18 4.02943725152 18 9 C 18 13.9705627485 13.9705627485 18 9 18 C 4.02943725152 18 0 13.9705627485 0 9 C 0 4.02943725152 4.02943725152 0 9 0 Z" />
+    <path
+        android:fillColor="@android:color/black"
+        android:fillAlpha="0.87"
+        android:strokeWidth="1"
+        android:pathData="M 6 10.4123279 L 8.63934949 10.4123279 L 8.63934949 15.6 L 12.5577168 7.84517705 L 9.94547194 7.84517705 L 9.94547194 2 Z" />
+</vector>
diff --git a/iconloaderlib/res/values/colors.xml b/iconloaderlib/res/values/colors.xml
new file mode 100644
index 0000000..873b2fc
--- /dev/null
+++ b/iconloaderlib/res/values/colors.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2018, 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.
+*/
+-->
+<resources>
+    <color name="legacy_icon_background">#FFFFFF</color>
+</resources>
diff --git a/iconloaderlib/res/values/dimens.xml b/iconloaderlib/res/values/dimens.xml
new file mode 100644
index 0000000..e8c0c44
--- /dev/null
+++ b/iconloaderlib/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 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.
+-->
+
+<resources>
+    <dimen name="profile_badge_size">24dp</dimen>
+</resources>
diff --git a/iconloaderlib/src/com/android.launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android.launcher3/icons/BaseIconFactory.java
new file mode 100644
index 0000000..681c03c
--- /dev/null
+++ b/iconloaderlib/src/com/android.launcher3/icons/BaseIconFactory.java
@@ -0,0 +1,303 @@
+package com.android.launcher3.icons;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+
+import com.android.launcher3.icons.R;
+
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
+
+/**
+ * This class will be moved to androidx library. There shouldn't be any dependency outside
+ * this package.
+ */
+public class BaseIconFactory {
+
+    private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
+    public static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+
+    private final Rect mOldBounds = new Rect();
+    private final Context mContext;
+    private final Canvas mCanvas;
+    private final PackageManager mPm;
+    private final ColorExtractor mColorExtractor;
+    private boolean mDisableColorExtractor;
+
+    private int mFillResIconDpi;
+    private int mIconBitmapSize;
+
+    private IconNormalizer mNormalizer;
+    private ShadowGenerator mShadowGenerator;
+
+    private Drawable mWrapperIcon;
+    private int mWrapperBackgroundColor;
+
+    protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
+        mContext = context.getApplicationContext();
+
+        mFillResIconDpi = fillResIconDpi;
+        mIconBitmapSize = iconBitmapSize;
+
+        mPm = mContext.getPackageManager();
+        mColorExtractor = new ColorExtractor();
+
+        mCanvas = new Canvas();
+        mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+    }
+
+    protected void clear() {
+        mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+        mDisableColorExtractor = false;
+    }
+
+    public ShadowGenerator getShadowGenerator() {
+        if (mShadowGenerator == null) {
+            mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
+        }
+        return mShadowGenerator;
+    }
+
+    public IconNormalizer getNormalizer() {
+        if (mNormalizer == null) {
+            mNormalizer = new IconNormalizer(mContext, mIconBitmapSize);
+        }
+        return mNormalizer;
+    }
+
+    public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
+        try {
+            Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
+            if (resources != null) {
+                final int id = resources.getIdentifier(iconRes.resourceName, null, null);
+                // do not stamp old legacy shortcuts as the app may have already forgotten about it
+                return createBadgedIconBitmap(
+                        resources.getDrawableForDensity(id, mFillResIconDpi),
+                        Process.myUserHandle() /* only available on primary user */,
+                        false /* do not apply legacy treatment */);
+            }
+        } catch (Exception e) {
+            // Icon not found.
+        }
+        return null;
+    }
+
+    public BitmapInfo createIconBitmap(Bitmap icon) {
+        if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
+            return BitmapInfo.fromBitmap(icon);
+        }
+        return BitmapInfo.fromBitmap(
+                createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
+    }
+
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            boolean shrinkNonAdaptiveIcons) {
+        return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
+    }
+
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            boolean shrinkNonAdaptiveIcons, boolean isInstantApp) {
+        return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, null);
+    }
+
+    /**
+     * Creates bitmap using the source drawable and various parameters.
+     * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
+     *
+     * @param icon                      source of the icon
+     * @param user                      info can be used for a badge
+     * @param shrinkNonAdaptiveIcons    {@code true} if non adaptive icons should be treated
+     * @param isInstantApp              info can be used for a badge
+     * @param scale                     returns the scale result from normalization
+     * @return a bitmap suitable for disaplaying as an icon at various system UIs.
+     */
+    public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
+            boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
+        if (scale == null) {
+            scale = new float[1];
+        }
+        icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
+        Bitmap bitmap = createIconBitmap(icon, scale[0]);
+        if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+            mCanvas.setBitmap(bitmap);
+            getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
+            mCanvas.setBitmap(null);
+        }
+
+        final Bitmap result;
+        if (user != null && !Process.myUserHandle().equals(user)) {
+            BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
+            Drawable badged = mPm.getUserBadgedIcon(drawable, user);
+            if (badged instanceof BitmapDrawable) {
+                result = ((BitmapDrawable) badged).getBitmap();
+            } else {
+                result = createIconBitmap(badged, 1f);
+            }
+        } else if (isInstantApp) {
+            badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
+            result = bitmap;
+        } else {
+            result = bitmap;
+        }
+        return BitmapInfo.fromBitmap(result, mDisableColorExtractor ? null : mColorExtractor);
+    }
+
+    public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
+        RectF iconBounds = new RectF();
+        float[] scale = new float[1];
+        icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
+        return createIconBitmap(icon,
+                Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
+    }
+
+    /**
+     * Sets the background color used for wrapped adaptive icon
+     */
+    public void setWrapperBackgroundColor(int color) {
+        mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
+    }
+
+    /**
+     * Disables the dominant color extraction for all icons loaded.
+     */
+    public void disableColorExtraction() {
+        mDisableColorExtractor = true;
+    }
+
+    private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, boolean shrinkNonAdaptiveIcons,
+            RectF outIconBounds, float[] outScale) {
+        float scale = 1f;
+
+        if (shrinkNonAdaptiveIcons) {
+            boolean[] outShape = new boolean[1];
+            if (mWrapperIcon == null) {
+                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
+                        .mutate();
+            }
+            AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
+            dr.setBounds(0, 0, 1, 1);
+            scale = getNormalizer().getScale(icon, outIconBounds);
+            if (ATLEAST_OREO && !(icon instanceof AdaptiveIconDrawable)) {
+                FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
+                fsd.setDrawable(icon);
+                fsd.setScale(scale);
+                icon = dr;
+                scale = getNormalizer().getScale(icon, outIconBounds);
+
+                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
+            }
+        } else {
+            scale = getNormalizer().getScale(icon, outIconBounds);
+        }
+
+        outScale[0] = scale;
+        return icon;
+    }
+
+    /**
+     * Adds the {@param badge} on top of {@param target} using the badge dimensions.
+     */
+    public void badgeWithDrawable(Bitmap target, Drawable badge) {
+        mCanvas.setBitmap(target);
+        badgeWithDrawable(mCanvas, badge);
+        mCanvas.setBitmap(null);
+    }
+
+    /**
+     * Adds the {@param badge} on top of {@param target} using the badge dimensions.
+     */
+    public void badgeWithDrawable(Canvas target, Drawable badge) {
+        int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+        badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
+                mIconBitmapSize, mIconBitmapSize);
+        badge.draw(target);
+    }
+
+    /**
+     * @param scale the scale to apply before drawing {@param icon} on the canvas
+     */
+    private Bitmap createIconBitmap(Drawable icon, float scale) {
+        Bitmap bitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
+                Bitmap.Config.ARGB_8888);
+        mCanvas.setBitmap(bitmap);
+        mOldBounds.set(icon.getBounds());
+
+        if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * mIconBitmapSize),
+                    Math.round(mIconBitmapSize * (1 - scale) / 2 ));
+            icon.setBounds(offset, offset, mIconBitmapSize - offset, mIconBitmapSize - offset);
+            icon.draw(mCanvas);
+        } else {
+            if (icon instanceof BitmapDrawable) {
+                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+                Bitmap b = bitmapDrawable.getBitmap();
+                if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
+                    bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
+                }
+            }
+            int width = mIconBitmapSize;
+            int height = mIconBitmapSize;
+
+            int intrinsicWidth = icon.getIntrinsicWidth();
+            int intrinsicHeight = icon.getIntrinsicHeight();
+            if (intrinsicWidth > 0 && intrinsicHeight > 0) {
+                // Scale the icon proportionally to the icon dimensions
+                final float ratio = (float) intrinsicWidth / intrinsicHeight;
+                if (intrinsicWidth > intrinsicHeight) {
+                    height = (int) (width / ratio);
+                } else if (intrinsicHeight > intrinsicWidth) {
+                    width = (int) (height * ratio);
+                }
+            }
+            final int left = (mIconBitmapSize - width) / 2;
+            final int top = (mIconBitmapSize - height) / 2;
+            icon.setBounds(left, top, left + width, top + height);
+            mCanvas.save();
+            mCanvas.scale(scale, scale, mIconBitmapSize / 2, mIconBitmapSize / 2);
+            icon.draw(mCanvas);
+            mCanvas.restore();
+
+        }
+        icon.setBounds(mOldBounds);
+        mCanvas.setBitmap(null);
+        return bitmap;
+    }
+
+    /**
+     * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
+     * This allows the badging to be done based on the action bitmap size rather than
+     * the scaled bitmap size.
+     */
+    private static class FixedSizeBitmapDrawable extends BitmapDrawable {
+
+        public FixedSizeBitmapDrawable(Bitmap bitmap) {
+            super(null, bitmap);
+        }
+
+        @Override
+        public int getIntrinsicHeight() {
+            return getBitmap().getWidth();
+        }
+
+        @Override
+        public int getIntrinsicWidth() {
+            return getBitmap().getWidth();
+        }
+    }
+}
diff --git a/iconloaderlib/src/com/android.launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android.launcher3/icons/BitmapInfo.java
new file mode 100644
index 0000000..245561e
--- /dev/null
+++ b/iconloaderlib/src/com/android.launcher3/icons/BitmapInfo.java
@@ -0,0 +1,49 @@
+/*
+ * 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 android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+
+public class BitmapInfo {
+
+    public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
+
+    public Bitmap icon;
+    public int color;
+
+    public void applyTo(BitmapInfo info) {
+        info.icon = icon;
+        info.color = color;
+    }
+
+    public final boolean isLowRes() {
+        return LOW_RES_ICON == icon;
+    }
+
+    public static BitmapInfo fromBitmap(Bitmap bitmap) {
+        return fromBitmap(bitmap, null);
+    }
+
+    public static BitmapInfo fromBitmap(Bitmap bitmap, ColorExtractor dominantColorExtractor) {
+        BitmapInfo info = new BitmapInfo();
+        info.icon = bitmap;
+        info.color = dominantColorExtractor != null
+                ? dominantColorExtractor.findDominantColorByHue(bitmap)
+                : 0;
+        return info;
+    }
+}
diff --git a/iconloaderlib/src/com/android.launcher3/icons/ColorExtractor.java b/iconloaderlib/src/com/android.launcher3/icons/ColorExtractor.java
new file mode 100644
index 0000000..87bda82
--- /dev/null
+++ b/iconloaderlib/src/com/android.launcher3/icons/ColorExtractor.java
@@ -0,0 +1,127 @@
+/*
+ * 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 android.graphics.Bitmap;
+import android.graphics.Color;
+import android.util.SparseArray;
+import java.util.Arrays;
+
+/**
+ * Utility class for extracting colors from a bitmap.
+ */
+public class ColorExtractor {
+
+    private final int NUM_SAMPLES = 20;
+    private final float[] mTmpHsv = new float[3];
+    private final float[] mTmpHueScoreHistogram = new float[360];
+    private final int[] mTmpPixels = new int[NUM_SAMPLES];
+    private final SparseArray<Float> mTmpRgbScores = new SparseArray<>();
+
+    /**
+     * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
+     * @param bitmap The bitmap to scan
+     */
+    public int findDominantColorByHue(Bitmap bitmap) {
+        return findDominantColorByHue(bitmap, NUM_SAMPLES);
+    }
+
+    /**
+     * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
+     * @param bitmap The bitmap to scan
+     */
+    public int findDominantColorByHue(Bitmap bitmap, int samples) {
+        final int height = bitmap.getHeight();
+        final int width = bitmap.getWidth();
+        int sampleStride = (int) Math.sqrt((height * width) / samples);
+        if (sampleStride < 1) {
+            sampleStride = 1;
+        }
+
+        // This is an out-param, for getting the hsv values for an rgb
+        float[] hsv = mTmpHsv;
+        Arrays.fill(hsv, 0);
+
+        // First get the best hue, by creating a histogram over 360 hue buckets,
+        // where each pixel contributes a score weighted by saturation, value, and alpha.
+        float[] hueScoreHistogram = mTmpHueScoreHistogram;
+        Arrays.fill(hueScoreHistogram, 0);
+        float highScore = -1;
+        int bestHue = -1;
+
+        int[] pixels = mTmpPixels;
+        Arrays.fill(pixels, 0);
+        int pixelCount = 0;
+
+        for (int y = 0; y < height; y += sampleStride) {
+            for (int x = 0; x < width; x += sampleStride) {
+                int argb = bitmap.getPixel(x, y);
+                int alpha = 0xFF & (argb >> 24);
+                if (alpha < 0x80) {
+                    // Drop mostly-transparent pixels.
+                    continue;
+                }
+                // Remove the alpha channel.
+                int rgb = argb | 0xFF000000;
+                Color.colorToHSV(rgb, hsv);
+                // Bucket colors by the 360 integer hues.
+                int hue = (int) hsv[0];
+                if (hue < 0 || hue >= hueScoreHistogram.length) {
+                    // Defensively avoid array bounds violations.
+                    continue;
+                }
+                if (pixelCount < samples) {
+                    pixels[pixelCount++] = rgb;
+                }
+                float score = hsv[1] * hsv[2];
+                hueScoreHistogram[hue] += score;
+                if (hueScoreHistogram[hue] > highScore) {
+                    highScore = hueScoreHistogram[hue];
+                    bestHue = hue;
+                }
+            }
+        }
+
+        SparseArray<Float> rgbScores = mTmpRgbScores;
+        rgbScores.clear();
+        int bestColor = 0xff000000;
+        highScore = -1;
+        // Go back over the RGB colors that match the winning hue,
+        // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
+        // The highest-scoring RGB color wins.
+        for (int i = 0; i < pixelCount; i++) {
+            int rgb = pixels[i];
+            Color.colorToHSV(rgb, hsv);
+            int hue = (int) hsv[0];
+            if (hue == bestHue) {
+                float s = hsv[1];
+                float v = hsv[2];
+                int bucket = (int) (s * 100) + (int) (v * 10000);
+                // Score by cumulative saturation * value.
+                float score = s * v;
+                Float oldTotal = rgbScores.get(bucket);
+                float newTotal = oldTotal == null ? score : oldTotal + score;
+                rgbScores.put(bucket, newTotal);
+                if (newTotal > highScore) {
+                    highScore = newTotal;
+                    // All the colors in the winning bucket are very similar. Last in wins.
+                    bestColor = rgb;
+                }
+            }
+        }
+        return bestColor;
+    }
+}
diff --git a/iconloaderlib/src/com/android.launcher3/icons/FixedScaleDrawable.java b/iconloaderlib/src/com/android.launcher3/icons/FixedScaleDrawable.java
new file mode 100644
index 0000000..e594f47
--- /dev/null
+++ b/iconloaderlib/src/com/android.launcher3/icons/FixedScaleDrawable.java
@@ -0,0 +1,56 @@
+package com.android.launcher3.icons;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Canvas;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.DrawableWrapper;
+import android.os.Build;
+import android.util.AttributeSet;
+
+import org.xmlpull.v1.XmlPullParser;
+
+/**
+ * Extension of {@link DrawableWrapper} which scales the child drawables by a fixed amount.
+ */
+@TargetApi(Build.VERSION_CODES.N)
+public class FixedScaleDrawable extends DrawableWrapper {
+
+    // TODO b/33553066 use the constant defined in MaskableIconDrawable
+    private static final float LEGACY_ICON_SCALE = .7f * .6667f;
+    private float mScaleX, mScaleY;
+
+    public FixedScaleDrawable() {
+        super(new ColorDrawable());
+        mScaleX = LEGACY_ICON_SCALE;
+        mScaleY = LEGACY_ICON_SCALE;
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        int saveCount = canvas.save();
+        canvas.scale(mScaleX, mScaleY,
+                getBounds().exactCenterX(), getBounds().exactCenterY());
+        super.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) { }
+
+    @Override
+    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) { }
+
+    public void setScale(float scale) {
+        float h = getIntrinsicHeight();
+        float w = getIntrinsicWidth();
+        mScaleX = scale * LEGACY_ICON_SCALE;
+        mScaleY = scale * LEGACY_ICON_SCALE;
+        if (h > w && w > 0) {
+            mScaleX *= w / h;
+        } else if (w > h && h > 0) {
+            mScaleY *= h / w;
+        }
+    }
+}
diff --git a/iconloaderlib/src/com/android.launcher3/icons/IconNormalizer.java b/iconloaderlib/src/com/android.launcher3/icons/IconNormalizer.java
new file mode 100644
index 0000000..05908df
--- /dev/null
+++ b/iconloaderlib/src/com/android.launcher3/icons/IconNormalizer.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2015 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 android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.Drawable;
+
+import java.nio.ByteBuffer;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class IconNormalizer {
+
+    private static final String TAG = "IconNormalizer";
+    private static final boolean DEBUG = false;
+    // Ratio of icon visible area to full icon size for a square shaped icon
+    private static final float MAX_SQUARE_AREA_FACTOR = 375.0f / 576;
+    // Ratio of icon visible area to full icon size for a circular shaped icon
+    private static final float MAX_CIRCLE_AREA_FACTOR = 380.0f / 576;
+
+    private static final float CIRCLE_AREA_BY_RECT = (float) Math.PI / 4;
+
+    // Slope used to calculate icon visible area to full icon size for any generic shaped icon.
+    private static final float LINEAR_SCALE_SLOPE =
+            (MAX_CIRCLE_AREA_FACTOR - MAX_SQUARE_AREA_FACTOR) / (1 - CIRCLE_AREA_BY_RECT);
+
+    private static final int MIN_VISIBLE_ALPHA = 40;
+
+    private static final float SCALE_NOT_INITIALIZED = 0;
+
+    // Ratio of the diameter of an normalized circular icon to the actual icon size.
+    public static final float ICON_VISIBLE_AREA_FACTOR = 0.92f;
+
+    private final int mMaxSize;
+    private final Bitmap mBitmap;
+    private final Canvas mCanvas;
+    private final byte[] mPixels;
+
+    private final Rect mAdaptiveIconBounds;
+    private float mAdaptiveIconScale;
+
+    // for each y, stores the position of the leftmost x and the rightmost x
+    private final float[] mLeftBorder;
+    private final float[] mRightBorder;
+    private final Rect mBounds;
+
+    /** package private **/
+    IconNormalizer(Context context, int iconBitmapSize) {
+        // Use twice the icon size as maximum size to avoid scaling down twice.
+        mMaxSize = iconBitmapSize * 2;
+        mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
+        mCanvas = new Canvas(mBitmap);
+        mPixels = new byte[mMaxSize * mMaxSize];
+        mLeftBorder = new float[mMaxSize];
+        mRightBorder = new float[mMaxSize];
+        mBounds = new Rect();
+        mAdaptiveIconBounds = new Rect();
+
+        mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
+    }
+
+    /**
+     * Returns the amount by which the {@param d} should be scaled (in both dimensions) so that it
+     * matches the design guidelines for a launcher icon.
+     *
+     * We first calculate the convex hull of the visible portion of the icon.
+     * This hull then compared with the bounding rectangle of the hull to find how closely it
+     * resembles a circle and a square, by comparing the ratio of the areas. Note that this is not an
+     * ideal solution but it gives satisfactory result without affecting the performance.
+     *
+     * This closeness is used to determine the ratio of hull area to the full icon size.
+     * Refer {@link #MAX_CIRCLE_AREA_FACTOR} and {@link #MAX_SQUARE_AREA_FACTOR}
+     *
+     * @param outBounds optional rect to receive the fraction distance from each edge.
+     */
+    public synchronized float getScale(@NonNull Drawable d, @Nullable RectF outBounds) {
+        if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable) {
+            if (mAdaptiveIconScale != SCALE_NOT_INITIALIZED) {
+                if (outBounds != null) {
+                    outBounds.set(mAdaptiveIconBounds);
+                }
+                return mAdaptiveIconScale;
+            }
+        }
+        int width = d.getIntrinsicWidth();
+        int height = d.getIntrinsicHeight();
+        if (width <= 0 || height <= 0) {
+            width = width <= 0 || width > mMaxSize ? mMaxSize : width;
+            height = height <= 0 || height > mMaxSize ? mMaxSize : height;
+        } else if (width > mMaxSize || height > mMaxSize) {
+            int max = Math.max(width, height);
+            width = mMaxSize * width / max;
+            height = mMaxSize * height / max;
+        }
+
+        mBitmap.eraseColor(Color.TRANSPARENT);
+        d.setBounds(0, 0, width, height);
+        d.draw(mCanvas);
+
+        ByteBuffer buffer = ByteBuffer.wrap(mPixels);
+        buffer.rewind();
+        mBitmap.copyPixelsToBuffer(buffer);
+
+        // Overall bounds of the visible icon.
+        int topY = -1;
+        int bottomY = -1;
+        int leftX = mMaxSize + 1;
+        int rightX = -1;
+
+        // Create border by going through all pixels one row at a time and for each row find
+        // the first and the last non-transparent pixel. Set those values to mLeftBorder and
+        // mRightBorder and use -1 if there are no visible pixel in the row.
+
+        // buffer position
+        int index = 0;
+        // buffer shift after every row, width of buffer = mMaxSize
+        int rowSizeDiff = mMaxSize - width;
+        // first and last position for any row.
+        int firstX, lastX;
+
+        for (int y = 0; y < height; y++) {
+            firstX = lastX = -1;
+            for (int x = 0; x < width; x++) {
+                if ((mPixels[index] & 0xFF) > MIN_VISIBLE_ALPHA) {
+                    if (firstX == -1) {
+                        firstX = x;
+                    }
+                    lastX = x;
+                }
+                index++;
+            }
+            index += rowSizeDiff;
+
+            mLeftBorder[y] = firstX;
+            mRightBorder[y] = lastX;
+
+            // If there is at least one visible pixel, update the overall bounds.
+            if (firstX != -1) {
+                bottomY = y;
+                if (topY == -1) {
+                    topY = y;
+                }
+
+                leftX = Math.min(leftX, firstX);
+                rightX = Math.max(rightX, lastX);
+            }
+        }
+
+        if (topY == -1 || rightX == -1) {
+            // No valid pixels found. Do not scale.
+            return 1;
+        }
+
+        convertToConvexArray(mLeftBorder, 1, topY, bottomY);
+        convertToConvexArray(mRightBorder, -1, topY, bottomY);
+
+        // Area of the convex hull
+        float area = 0;
+        for (int y = 0; y < height; y++) {
+            if (mLeftBorder[y] <= -1) {
+                continue;
+            }
+            area += mRightBorder[y] - mLeftBorder[y] + 1;
+        }
+
+        // Area of the rectangle required to fit the convex hull
+        float rectArea = (bottomY + 1 - topY) * (rightX + 1 - leftX);
+        float hullByRect = area / rectArea;
+
+        float scaleRequired;
+        if (hullByRect < CIRCLE_AREA_BY_RECT) {
+            scaleRequired = MAX_CIRCLE_AREA_FACTOR;
+        } else {
+            scaleRequired = MAX_SQUARE_AREA_FACTOR + LINEAR_SCALE_SLOPE * (1 - hullByRect);
+        }
+        mBounds.left = leftX;
+        mBounds.right = rightX;
+
+        mBounds.top = topY;
+        mBounds.bottom = bottomY;
+
+        if (outBounds != null) {
+            outBounds.set(((float) mBounds.left) / width, ((float) mBounds.top) / height,
+                    1 - ((float) mBounds.right) / width,
+                    1 - ((float) mBounds.bottom) / height);
+        }
+        float areaScale = area / (width * height);
+        // Use sqrt of the final ratio as the images is scaled across both width and height.
+        float scale = areaScale > scaleRequired ? (float) Math.sqrt(scaleRequired / areaScale) : 1;
+        if (BaseIconFactory.ATLEAST_OREO && d instanceof AdaptiveIconDrawable &&
+                mAdaptiveIconScale == SCALE_NOT_INITIALIZED) {
+            mAdaptiveIconScale = scale;
+            mAdaptiveIconBounds.set(mBounds);
+        }
+        return scale;
+    }
+
+    /**
+     * Modifies {@param xCoordinates} to represent a convex border. Fills in all missing values
+     * (except on either ends) with appropriate values.
+     * @param xCoordinates map of x coordinate per y.
+     * @param direction 1 for left border and -1 for right border.
+     * @param topY the first Y position (inclusive) with a valid value.
+     * @param bottomY the last Y position (inclusive) with a valid value.
+     */
+    private static void convertToConvexArray(
+            float[] xCoordinates, int direction, int topY, int bottomY) {
+        int total = xCoordinates.length;
+        // The tangent at each pixel.
+        float[] angles = new float[total - 1];
+
+        int first = topY; // First valid y coordinate
+        int last = -1;    // Last valid y coordinate which didn't have a missing value
+
+        float lastAngle = Float.MAX_VALUE;
+
+        for (int i = topY + 1; i <= bottomY; i++) {
+            if (xCoordinates[i] <= -1) {
+                continue;
+            }
+            int start;
+
+            if (lastAngle == Float.MAX_VALUE) {
+                start = first;
+            } else {
+                float currentAngle = (xCoordinates[i] - xCoordinates[last]) / (i - last);
+                start = last;
+                // If this position creates a concave angle, keep moving up until we find a
+                // position which creates a convex angle.
+                if ((currentAngle - lastAngle) * direction < 0) {
+                    while (start > first) {
+                        start --;
+                        currentAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+                        if ((currentAngle - angles[start]) * direction >= 0) {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            // Reset from last check
+            lastAngle = (xCoordinates[i] - xCoordinates[start]) / (i - start);
+            // Update all the points from start.
+            for (int j = start; j < i; j++) {
+                angles[j] = lastAngle;
+                xCoordinates[j] = xCoordinates[start] + lastAngle * (j - start);
+            }
+            last = i;
+        }
+    }
+
+    /**
+     * @return The diameter of the normalized circle that fits inside of the square (size x size).
+     */
+    public static int getNormalizedCircleSize(int size) {
+        float area = size * size * MAX_CIRCLE_AREA_FACTOR;
+        return (int) Math.round(Math.sqrt((4 * area) / Math.PI));
+    }
+}
diff --git a/iconloaderlib/src/com/android.launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android.launcher3/icons/ShadowGenerator.java
new file mode 100644
index 0000000..6491b7e
--- /dev/null
+++ b/iconloaderlib/src/com/android.launcher3/icons/ShadowGenerator.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2016 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 android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.BlurMaskFilter;
+import android.graphics.BlurMaskFilter.Blur;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.RectF;
+
+import androidx.core.graphics.ColorUtils;
+
+/**
+ * Utility class to add shadows to bitmaps.
+ */
+public class ShadowGenerator {
+    public static final float BLUR_FACTOR = 0.5f/48;
+
+    // Percent of actual icon size
+    public static final float KEY_SHADOW_DISTANCE = 1f/48;
+    private static final int KEY_SHADOW_ALPHA = 61;
+    // Percent of actual icon size
+    private static final float HALF_DISTANCE = 0.5f;
+    private static final int AMBIENT_SHADOW_ALPHA = 30;
+
+    private final int mIconSize;
+
+    private final Paint mBlurPaint;
+    private final Paint mDrawPaint;
+    private final BlurMaskFilter mDefaultBlurMaskFilter;
+
+    public ShadowGenerator(int iconSize) {
+        mIconSize = iconSize;
+        mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+        mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL);
+    }
+
+    public synchronized void recreateIcon(Bitmap icon, Canvas out) {
+        recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
+    }
+
+    public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
+            int ambientAlpha, int keyAlpha, Canvas out) {
+        int[] offset = new int[2];
+        mBlurPaint.setMaskFilter(blurMaskFilter);
+        Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
+
+        // Draw ambient shadow
+        mDrawPaint.setAlpha(ambientAlpha);
+        out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
+
+        // Draw key shadow
+        mDrawPaint.setAlpha(keyAlpha);
+        out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);
+
+        // Draw the icon
+        mDrawPaint.setAlpha(255);
+        out.drawBitmap(icon, 0, 0, mDrawPaint);
+    }
+
+    /**
+     * Returns the minimum amount by which an icon with {@param bounds} should be scaled
+     * so that the shadows do not get clipped.
+     */
+    public static float getScaleForBounds(RectF bounds) {
+        float scale = 1;
+
+        // For top, left & right, we need same space.
+        float minSide = Math.min(Math.min(bounds.left, bounds.right), bounds.top);
+        if (minSide < BLUR_FACTOR) {
+            scale = (HALF_DISTANCE - BLUR_FACTOR) / (HALF_DISTANCE - minSide);
+        }
+
+        float bottomSpace = BLUR_FACTOR + KEY_SHADOW_DISTANCE;
+        if (bounds.bottom < bottomSpace) {
+            scale = Math.min(scale, (HALF_DISTANCE - bottomSpace) / (HALF_DISTANCE - bounds.bottom));
+        }
+        return scale;
+    }
+
+    public static class Builder {
+
+        public final RectF bounds = new RectF();
+        public final int color;
+
+        public int ambientShadowAlpha = AMBIENT_SHADOW_ALPHA;
+
+        public float shadowBlur;
+
+        public float keyShadowDistance;
+        public int keyShadowAlpha = KEY_SHADOW_ALPHA;
+        public float radius;
+
+        public Builder(int color) {
+            this.color = color;
+        }
+
+        public Builder setupBlurForSize(int height) {
+            shadowBlur = height * 1f / 24;
+            keyShadowDistance = height * 1f / 16;
+            return this;
+        }
+
+        public Bitmap createPill(int width, int height) {
+            radius = height / 2f;
+
+            int centerX = Math.round(width / 2f + shadowBlur);
+            int centerY = Math.round(radius + shadowBlur + keyShadowDistance);
+            int center = Math.max(centerX, centerY);
+            bounds.set(0, 0, width, height);
+            bounds.offsetTo(center - width / 2f, center - height / 2f);
+
+            int size = center * 2;
+            Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
+            drawShadow(new Canvas(result));
+            return result;
+        }
+
+        public void drawShadow(Canvas c) {
+            Paint p = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+            p.setColor(color);
+
+            // Key shadow
+            p.setShadowLayer(shadowBlur, 0, keyShadowDistance,
+                    ColorUtils.setAlphaComponent(Color.BLACK, keyShadowAlpha));
+            c.drawRoundRect(bounds, radius, radius, p);
+
+            // Ambient shadow
+            p.setShadowLayer(shadowBlur, 0, 0,
+                    ColorUtils.setAlphaComponent(Color.BLACK, ambientShadowAlpha));
+            c.drawRoundRect(bounds, radius, radius, p);
+
+            if (Color.alpha(color) < 255) {
+                // Clear any content inside the pill-rect for translucent fill.
+                p.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
+                p.clearShadowLayer();
+                p.setColor(Color.BLACK);
+                c.drawRoundRect(bounds, radius, radius, p);
+
+                p.setXfermode(null);
+                p.setColor(color);
+                c.drawRoundRect(bounds, radius, radius, p);
+            }
+        }
+    }
+}