Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 77e70997 authored by Sunny Goyal's avatar Sunny Goyal Committed by Android (Google) Code Review
Browse files

Merge "Add customization options for flagging and shaping full-bleed icons" into main

parents 9a72acfc 0f21878a
Loading
Loading
Loading
Loading
+88 −22
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@ import static android.graphics.Paint.DITHER_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;

import static com.android.launcher3.icons.BitmapInfo.FLAG_FULL_BLEED;
import static com.android.launcher3.icons.BitmapInfo.FLAG_INSTANT;
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
@@ -86,6 +87,7 @@ public class BaseIconFactory implements AutoCloseable {
    @NonNull
    private final PackageManager mPm;

    public final boolean mDrawFullBleedIcons;
    protected final int mFullResIconDpi;
    protected final int mIconBitmapSize;

@@ -103,15 +105,16 @@ public class BaseIconFactory implements AutoCloseable {

    private static int PLACEHOLDER_BACKGROUND_COLOR = Color.rgb(245, 245, 245);

    protected BaseIconFactory(Context context, int fullResIconDpi, int iconBitmapSize,
            boolean unused) {
        this(context, fullResIconDpi, iconBitmapSize);
    public BaseIconFactory(Context context, int fullResIconDpi, int iconBitmapSize) {
        this(context, fullResIconDpi, iconBitmapSize, /* drawFullBleedIcons */ false);
    }

    public BaseIconFactory(Context context, int fullResIconDpi, int iconBitmapSize) {
    public BaseIconFactory(Context context, int fullResIconDpi, int iconBitmapSize,
            boolean drawFullBleedIcons) {
        mContext = context.getApplicationContext();
        mFullResIconDpi = fullResIconDpi;
        mIconBitmapSize = iconBitmapSize;
        mDrawFullBleedIcons = drawFullBleedIcons;

        mPm = mContext.getPackageManager();

@@ -171,7 +174,7 @@ public class BaseIconFactory implements AutoCloseable {
                new ColorDrawable(PLACEHOLDER_BACKGROUND_COLOR),
                new CenterTextDrawable(placeholder, color));
        Bitmap icon = createIconBitmap(drawable, ICON_VISIBLE_AREA_FACTOR);
        return BitmapInfo.of(icon, color);
        return BitmapInfo.of(icon, color, getDefaultIconShape());
    }

    public BitmapInfo createIconBitmap(Bitmap icon) {
@@ -179,7 +182,8 @@ public class BaseIconFactory implements AutoCloseable {
            icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
        }

        return BitmapInfo.of(icon, ColorExtractor.findDominantColorByHue(icon));
        return BitmapInfo.of(icon, ColorExtractor.findDominantColorByHue(icon),
                getDefaultIconShape());
    }

    /**
@@ -223,10 +227,12 @@ public class BaseIconFactory implements AutoCloseable {
        }
        AdaptiveIconDrawable adaptiveIcon = normalizeAndWrapToAdaptiveIcon(tempIcon, scale);
        Bitmap bitmap = createIconBitmap(adaptiveIcon, scale[0],
                options == null ? MODE_WITH_SHADOW : options.mGenerationMode);
                options == null ? MODE_WITH_SHADOW : options.mGenerationMode, mDrawFullBleedIcons);
        int color = (options != null && options.mExtractedColor != null)
                ? options.mExtractedColor : ColorExtractor.findDominantColorByHue(bitmap);
        BitmapInfo info = BitmapInfo.of(bitmap, color);

        BitmapInfo info = BitmapInfo.of(bitmap, color, getDefaultIconShape());
        if (mDrawFullBleedIcons) info.flags |= FLAG_FULL_BLEED;

        if (adaptiveIcon instanceof Extender extender) {
            info = extender.getExtendedInfo(bitmap, color, this, scale[0]);
@@ -248,6 +254,18 @@ public class BaseIconFactory implements AutoCloseable {
        return info;
    }

    /**
     * Generates an IconShape based on the current bitmap size and default icon mask.
     */
    public IconShape getDefaultIconShape() {
        if (!mDrawFullBleedIcons) return IconShape.EMPTY;
        AdaptiveIconDrawable tempAdaptiveIcon =
                new AdaptiveIconDrawable(new ColorDrawable(BLACK), null);
        tempAdaptiveIcon.setBounds(0, 0, mIconBitmapSize, mIconBitmapSize);
        return new IconShape(mIconBitmapSize, tempAdaptiveIcon.getIconMask(),
                getWhiteShadowLayer());
    }

    @NonNull
    public FlagOp getBitmapFlagOp(@Nullable IconOptions options) {
        FlagOp op = FlagOp.NO_OP;
@@ -340,7 +358,7 @@ public class BaseIconFactory implements AutoCloseable {
    public Bitmap createScaledBitmap(@NonNull Drawable icon, @BitmapGenerationMode int mode) {
        float[] scale = new float[1];
        icon = normalizeAndWrapToAdaptiveIcon(icon, scale);
        return createIconBitmap(icon, Math.min(scale[0], ICON_SCALE_FOR_SHADOWS), mode);
        return createIconBitmap(icon, Math.min(scale[0], ICON_SCALE_FOR_SHADOWS), mode, false);
    }

    /**
@@ -397,12 +415,12 @@ public class BaseIconFactory implements AutoCloseable {

    @NonNull
    public Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale) {
        return createIconBitmap(icon, scale, MODE_DEFAULT);
        return createIconBitmap(icon, scale, MODE_DEFAULT, mDrawFullBleedIcons);
    }

    @NonNull
    public Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale,
            @BitmapGenerationMode int bitmapGenerationMode) {
            @BitmapGenerationMode int bitmapGenerationMode, boolean isFullBleed) {
        final int size = mIconBitmapSize;
        final Bitmap bitmap;
        switch (bitmapGenerationMode) {
@@ -412,7 +430,8 @@ public class BaseIconFactory implements AutoCloseable {
            case MODE_HARDWARE:
            case MODE_HARDWARE_WITH_SHADOW: {
                return BitmapRenderer.createHardwareBitmap(size, size, canvas ->
                        drawIconBitmap(canvas, icon, scale, bitmapGenerationMode, null));
                        drawIconBitmap(canvas, icon, scale, bitmapGenerationMode, null,
                                isFullBleed));
            }
            case MODE_WITH_SHADOW:
            default:
@@ -423,36 +442,42 @@ public class BaseIconFactory implements AutoCloseable {
            return bitmap;
        }
        mCanvas.setBitmap(bitmap);
        drawIconBitmap(mCanvas, icon, scale, bitmapGenerationMode, bitmap);
        drawIconBitmap(mCanvas, icon, scale, bitmapGenerationMode, bitmap, isFullBleed);
        mCanvas.setBitmap(null);
        return bitmap;
    }

    private void drawIconBitmap(@NonNull Canvas canvas, @Nullable Drawable icon,
            final float scale, @BitmapGenerationMode int bitmapGenerationMode,
            @Nullable Bitmap targetBitmap) {
            @Nullable Bitmap targetBitmap, boolean isFullBleed) {
        final int size = mIconBitmapSize;
        mOldBounds.set(icon.getBounds());
        boolean isFullBleedEnabled = isFullBleed
                && Flags.enableLauncherIconShapes();
        if (icon instanceof AdaptiveIconDrawable aid) {
            // We are ignoring KEY_SHADOW_DISTANCE because regular icons ignore this at the
            // moment b/298203449
            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
            int offset = isFullBleedEnabled
                    ? 0
                    : Math.max((int) Math.ceil(BLUR_FACTOR * size),
                            Math.round(size * (1 - scale) / 2));
            // b/211896569: AdaptiveIconDrawable do not work properly for non top-left bounds
            int newBounds = size - offset * 2;
            icon.setBounds(0, 0, newBounds, newBounds);
            Path shapePath = getShapePath(aid, icon.getBounds());
            int count = canvas.save();
            canvas.translate(offset, offset);
            if (bitmapGenerationMode == MODE_WITH_SHADOW
                    || bitmapGenerationMode == MODE_HARDWARE_WITH_SHADOW) {
                getShadowGenerator().addPathShadow(shapePath, canvas);

            if ((bitmapGenerationMode == MODE_WITH_SHADOW
                    || bitmapGenerationMode == MODE_HARDWARE_WITH_SHADOW)
                    && !isFullBleedEnabled) {
                getShadowGenerator().addPathShadow(aid.getIconMask(), canvas);
            }

            if (icon instanceof Extender) {
                ((Extender) icon).drawForPersistence(canvas);
            } else {
                drawAdaptiveIcon(canvas, aid, shapePath);
                drawAdaptiveIcon(canvas, aid, isFullBleedEnabled,
                        getShapePath(aid, icon.getBounds()));
            }

            canvas.restoreToCount(count);
@@ -508,8 +533,11 @@ public class BaseIconFactory implements AutoCloseable {
     * @param canvas    canvas to draw on
     * @param drawable  AdaptiveIconDrawable to draw
     * @param shapePath path to clip icon with for shapes
     *
     * @deprecated b/421884219 use mDrawFullBleedIcons and shape it using {@link IconShape} instead
     */
    protected void drawAdaptiveIcon(
    @Deprecated
    private void drawShapedAdaptiveIcon(
            @NonNull Canvas canvas,
            @NonNull AdaptiveIconDrawable drawable,
            @NonNull Path shapePath
@@ -526,6 +554,44 @@ public class BaseIconFactory implements AutoCloseable {
        canvas.drawPath(shapePath, paint);
    }

    /**
     * Draws AdaptiveIconDrawable onto canvas with either default shape, or
     * as Full-bleed.
     *
     * @param canvas    canvas to draw on
     * @param drawable  AdaptiveIconDrawable to draw
     * @param isFullBleed whether to draw as full-bleed.
     */
    private void drawAdaptiveIcon(
            @NonNull Canvas canvas,
            @NonNull AdaptiveIconDrawable drawable,
            boolean isFullBleed,
            Path shape
    ) {
        Drawable background = drawable.getBackground();
        Drawable foreground = drawable.getForeground();
        boolean shouldNotDrawFullBleed = !isFullBleed || (background == null && foreground == null);
        if (shouldNotDrawFullBleed) {
            boolean shouldDrawDefaultShape = !isFullBleed && mDrawFullBleedIcons;
            // TODO: b/421884219 Temporarily keep old icon shape implementation until migrated
            if (shouldDrawDefaultShape) {
                // New Icon shapes path, used for non-full bleed icons
                drawable.draw(canvas);
            } else {
                // Old Icon shapes path, to get old shape effect if mDrawFullBleedIcons is false
                drawShapedAdaptiveIcon(canvas, drawable, shape);
            }
            return;
        }
        canvas.drawColor(Color.BLACK);
        if (background != null) {
            background.draw(canvas);
        }
        if (foreground != null) {
            foreground.draw(canvas);
        }
    }

    @Override
    public void close() {
        clear();
+34 −18
Original line number Diff line number Diff line
@@ -18,7 +18,6 @@ package com.android.launcher3.icons
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Path
import android.graphics.drawable.Drawable
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
@@ -30,12 +29,21 @@ import com.android.launcher3.util.FlagOp
open class BitmapInfo(
    @JvmField val icon: Bitmap,
    @JvmField val color: Int,
    @JvmField val defaultIconShape: IconShape = IconShape.EMPTY,
    @BitmapInfoFlags @JvmField var flags: Int = 0,
    var themedBitmap: ThemedBitmap? = null,
) {
    @IntDef(
        flag = true,
        value = [FLAG_WORK, FLAG_INSTANT, FLAG_CLONE, FLAG_PRIVATE, FLAG_WRAPPED_NON_ADAPTIVE],
        value =
            [
                FLAG_WORK,
                FLAG_INSTANT,
                FLAG_CLONE,
                FLAG_PRIVATE,
                FLAG_WRAPPED_NON_ADAPTIVE,
                FLAG_FULL_BLEED,
            ],
    )
    internal annotation class BitmapInfoFlags

@@ -59,7 +67,7 @@ open class BitmapInfo(

    @Override
    open fun clone(): BitmapInfo {
        return copyInternalsTo(BitmapInfo(icon, color))
        return copyInternalsTo(BitmapInfo(icon, color, defaultIconShape))
    }

    protected fun copyInternalsTo(target: BitmapInfo): BitmapInfo {
@@ -101,17 +109,21 @@ open class BitmapInfo(
     *
     * @param context Context
     * @param creationFlags Flags for creating the FastBitmapDrawable
     * @param badgeShape Optional Path for masking icon badges to a shape. Should be 100x100.
     * @param iconShape information for custom Icon Shapes, to use with Full-bleed icons.
     * @return FastBitmapDrawable
     */
    open fun newIcon(
        context: Context,
        @DrawableCreationFlags creationFlags: Int,
        badgeShape: Path?,
        iconShape: IconShape?,
    ): FastBitmapDrawable {
        val drawable: FastBitmapDrawable =
            if (isLowRes) {
                FastBitmapDrawable(this, PlaceHolderDelegateFactory(context))
                FastBitmapDrawable(
                    this,
                    iconShape ?: defaultIconShape,
                    PlaceHolderDelegateFactory(context),
                )
            } else if (
                (creationFlags and FLAG_THEMED) != 0 &&
                    themedBitmap != null &&
@@ -119,9 +131,9 @@ open class BitmapInfo(
            ) {
                themedBitmap!!.newDrawable(this, context)
            } else {
                FastBitmapDrawable(this)
                FastBitmapDrawable(this, iconShape ?: defaultIconShape)
            }
        applyFlags(context, drawable, creationFlags, badgeShape)
        applyFlags(context, drawable, creationFlags)
        return drawable
    }

@@ -129,7 +141,6 @@ open class BitmapInfo(
        context: Context,
        drawable: FastBitmapDrawable,
        @DrawableCreationFlags creationFlags: Int,
        badgeShape: Path?,
    ) {
        this.creationFlags = creationFlags
        drawable.disabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f)
@@ -140,7 +151,6 @@ open class BitmapInfo(
                    context,
                    (creationFlags and FLAG_THEMED) != 0,
                    (creationFlags and FLAG_SKIP_USER_BADGE) != 0,
                    badgeShape,
                )
            if (badge != null) {
                drawable.badge = badge
@@ -156,8 +166,8 @@ open class BitmapInfo(
     * @param badgeShape Optional Path to mask badges to a shape. Should be 100x100.
     * @return Drawable for the badge.
     */
    fun getBadgeDrawable(context: Context, isThemed: Boolean, badgeShape: Path?): Drawable? {
        return getBadgeDrawable(context, isThemed, false, badgeShape)
    fun getBadgeDrawable(context: Context, isThemed: Boolean): Drawable? {
        return getBadgeDrawable(context, isThemed, false)
    }

    /**
@@ -173,20 +183,19 @@ open class BitmapInfo(
        context: Context,
        isThemed: Boolean,
        skipUserBadge: Boolean,
        badgeShape: Path?,
    ): Drawable? {
        if (badgeInfo != null) {
            var creationFlag = if (isThemed) FLAG_THEMED else 0
            if (skipUserBadge) {
                creationFlag = creationFlag or FLAG_SKIP_USER_BADGE
            }
            return badgeInfo!!.newIcon(context, creationFlag, badgeShape)
            return badgeInfo!!.newIcon(context, creationFlag, null)
        }
        if (skipUserBadge) {
            return null
        } else {
            getBadgeDrawableInfo()?.let {
                return UserBadgeDrawable(context, it.drawableRes, it.colorRes, isThemed, badgeShape)
                return UserBadgeDrawable(context, it.drawableRes, it.colorRes, isThemed)
            }
        }
        return null
@@ -245,6 +254,7 @@ open class BitmapInfo(
        const val FLAG_CLONE: Int = 1 shl 2
        const val FLAG_PRIVATE: Int = 1 shl 3
        const val FLAG_WRAPPED_NON_ADAPTIVE: Int = 1 shl 4
        const val FLAG_FULL_BLEED: Int = 1 shl 5

        // Drawable creation flags
        const val FLAG_THEMED: Int = 1 shl 0
@@ -256,12 +266,18 @@ open class BitmapInfo(

        @JvmStatic
        fun fromBitmap(bitmap: Bitmap): BitmapInfo {
            return of(bitmap, 0)
            return of(bitmap, 0, IconShape.EMPTY)
        }

        @JvmStatic
        fun of(bitmap: Bitmap, color: Int): BitmapInfo {
            return BitmapInfo(bitmap, color)
        fun of(bitmap: Bitmap, color: Int, defaultShape: IconShape = IconShape.EMPTY): BitmapInfo {
            val flags = if (defaultShape == IconShape.EMPTY) 0 else FLAG_FULL_BLEED
            return BitmapInfo(
                icon = bitmap,
                color = color,
                defaultIconShape = defaultShape,
                flags = flags,
            )
        }
    }
}
+2 −2
Original line number Diff line number Diff line
@@ -78,7 +78,7 @@ public class BubbleIconFactory extends BaseIconFactory {
            outScale = new float[1];
        }
        icon = normalizeAndWrapToAdaptiveIcon(icon, outScale);
        return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
        return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW, mDrawFullBleedIcons);
    }

    /**
@@ -95,7 +95,7 @@ public class BubbleIconFactory extends BaseIconFactory {
            userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
        }
        Bitmap userBadgedBitmap = mBadgeFactory.createIconBitmap(
                userBadgedAppIcon, 1, MODE_WITH_SHADOW);
                userBadgedAppIcon, 1, MODE_WITH_SHADOW, mDrawFullBleedIcons);
        return mBadgeFactory.createIconBitmap(userBadgedBitmap);
    }

+13 −8
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
import android.graphics.Paint
import android.graphics.Path
import android.graphics.Rect
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.ColorDrawable
@@ -170,7 +169,7 @@ class ClockDrawableWrapper private constructor(base: AdaptiveIconDrawable) :
        val mFlattenedBackground: Bitmap,
        val themeData: AnimationInfo?,
        val themeBackground: Bitmap?,
    ) : BitmapInfo(icon, color, /* flags */ 0, /* themedBitmap */ null) {
    ) : BitmapInfo(icon, color, flags = 0, themedBitmap = null) {
        val boundsOffset: Float =
            max(ShadowGenerator.BLUR_FACTOR.toDouble(), ((1 - scale) / 2).toDouble()).toFloat()

@@ -178,7 +177,7 @@ class ClockDrawableWrapper private constructor(base: AdaptiveIconDrawable) :
        override fun newIcon(
            context: Context,
            @DrawableCreationFlags creationFlags: Int,
            badgeShape: Path?,
            iconShape: IconShape?,
        ): FastBitmapDrawable {
            val bg: Bitmap
            val themedFgColor: Int
@@ -197,11 +196,10 @@ class ClockDrawableWrapper private constructor(base: AdaptiveIconDrawable) :
                bg = mFlattenedBackground
                bgFilter = null
            }

            val delegateInfo =
                ClockDelegateInfo(themedFgColor, boundsOffset, animInfo, bg, bgFilter)
            val d = FastBitmapDrawable(this, delegateInfo)
            applyFlags(context, d, creationFlags, null)
            val d = FastBitmapDrawable(this, iconShape ?: defaultIconShape, delegateInfo)
            applyFlags(context, d, creationFlags)
            return d
        }

@@ -235,6 +233,7 @@ class ClockDrawableWrapper private constructor(base: AdaptiveIconDrawable) :
    ) : DelegateFactory {
        override fun newDelegate(
            bitmapInfo: BitmapInfo,
            iconShape: IconShape,
            paint: Paint,
            host: FastBitmapDrawable,
        ): FastBitmapDrawableDelegate {
@@ -289,9 +288,15 @@ class ClockDrawableWrapper private constructor(base: AdaptiveIconDrawable) :
            mFullDrawable.setBounds(0, 0, bounds.width(), bounds.height())
        }

        override fun drawContent(info: BitmapInfo, canvas: Canvas, bounds: Rect, paint: Paint) {
        override fun drawContent(
            info: BitmapInfo,
            host: FastBitmapDrawable,
            canvas: Canvas,
            bounds: Rect,
            paint: Paint,
        ) {
            if (mAnimInfo == null) {
                super.drawContent(info, canvas, bounds, paint)
                super.drawContent(info, mHost, canvas, bounds, paint)
                return
            }
            canvas.drawBitmap(mBG, null, bounds, mBgPaint)
+43 −4
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.launcher3.icons
import android.R
import android.animation.ObjectAnimator
import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
@@ -28,6 +29,7 @@ import android.graphics.Paint.ANTI_ALIAS_FLAG
import android.graphics.Paint.FILTER_BITMAP_FLAG
import android.graphics.PixelFormat
import android.graphics.Rect
import android.graphics.Shader.TileMode.CLAMP
import android.graphics.drawable.Drawable
import android.graphics.drawable.Drawable.Callback
import android.util.FloatProperty
@@ -36,26 +38,32 @@ import android.view.animation.DecelerateInterpolator
import android.view.animation.Interpolator
import android.view.animation.PathInterpolator
import androidx.annotation.VisibleForTesting
import com.android.launcher3.icons.BitmapInfo.Companion.FLAG_FULL_BLEED
import com.android.launcher3.icons.BitmapInfo.Companion.LOW_RES_INFO
import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags
import com.android.launcher3.icons.FastBitmapDrawableDelegate.DelegateFactory
import com.android.launcher3.icons.FastBitmapDrawableDelegate.SimpleDelegateFactory
import com.android.launcher3.icons.ShadowGenerator.ICON_SCALE_FOR_SHADOWS
import kotlin.math.min

class FastBitmapDrawable
@JvmOverloads
constructor(
    info: BitmapInfo?,
    private val iconShape: IconShape = IconShape.EMPTY,
    private val delegateFactory: DelegateFactory = SimpleDelegateFactory,
) : Drawable(), Callback {

    @JvmOverloads constructor(b: Bitmap, iconColor: Int = 0) : this(BitmapInfo.of(b, iconColor))

    @JvmField val bitmapInfo: BitmapInfo = info ?: BitmapInfo.LOW_RES_INFO
    // b/404578798 - mBitmapInfo isn't expected to be null, but it is in some cases.
    @JvmField val bitmapInfo: BitmapInfo = info ?: LOW_RES_INFO
    var isAnimationEnabled: Boolean = true

    @JvmField protected val paint: Paint = Paint(FILTER_BITMAP_FLAG or ANTI_ALIAS_FLAG)
    private val shader: BitmapShader = BitmapShader(bitmapInfo.icon, CLAMP, CLAMP)

    val delegate = delegateFactory.newDelegate(bitmapInfo, paint, this)
    val delegate = delegateFactory.newDelegate(bitmapInfo, iconShape, paint, this)

    @JvmField @VisibleForTesting var isPressed: Boolean = false
    @JvmField @VisibleForTesting var isHovered: Boolean = false
@@ -117,10 +125,39 @@ constructor(
    }

    private fun drawInternal(canvas: Canvas, bounds: Rect) {
        delegate.drawContent(bitmapInfo, canvas, bounds, paint)
        delegate.drawContent(bitmapInfo, this, canvas, bounds, paint)
        badge?.draw(canvas)
    }

    fun drawContent(canvas: Canvas, bounds: Rect) {
        if ((bitmapInfo.flags and FLAG_FULL_BLEED) != 0) {
            drawShapedInternal(canvas, bounds)
        } else {
            canvas.drawBitmap(bitmapInfo.icon, null, bounds, paint)
        }
    }

    private fun drawShapedInternal(canvas: Canvas, bounds: Rect) {
        canvas.save()
        canvas.drawBitmap(iconShape.shadowLayer, null, bounds, paint)

        canvas.translate(bounds.left.toFloat(), bounds.top.toFloat())
        val iconWidth: Int = bitmapInfo.icon.width
        val iconHeight: Int = bitmapInfo.icon.height

        canvas.scale(bounds.width().toFloat() / iconWidth, bounds.height().toFloat() / iconHeight)
        canvas.scale(
            ICON_SCALE_FOR_SHADOWS,
            ICON_SCALE_FOR_SHADOWS,
            (iconWidth / 2).toFloat(),
            (iconHeight / 2).toFloat(),
        )
        paint.shader = shader
        canvas.drawPath(iconShape.path, paint)
        paint.shader = null
        canvas.restore()
    }

    /** Returns the primary icon color, slightly tinted white */
    fun getIconColor(): Int = delegate.getIconColor(bitmapInfo)

@@ -249,6 +286,7 @@ constructor(
            bitmapInfo,
            isDisabled,
            badge?.constantState,
            iconShape,
            creationFlags,
            delegateFactory,
            level,
@@ -277,13 +315,14 @@ constructor(
        val bitmapInfo: BitmapInfo,
        val isDisabled: Boolean,
        val badgeConstantState: ConstantState?,
        val iconShape: IconShape,
        val creationFlags: Int,
        val delegateFactory: DelegateFactory,
        val level: Int,
    ) : ConstantState() {

        override fun newDrawable(): FastBitmapDrawable {
            val drawable = FastBitmapDrawable(bitmapInfo, delegateFactory)
            val drawable = FastBitmapDrawable(bitmapInfo, iconShape, delegateFactory)
            drawable.isDisabled = isDisabled
            if (badgeConstantState != null) {
                drawable.badge = badgeConstantState.newDrawable()
Loading