Loading iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +88 −22 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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) { Loading @@ -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()); } /** Loading Loading @@ -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]); Loading @@ -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; Loading Loading @@ -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); } /** Loading Loading @@ -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) { Loading @@ -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: Loading @@ -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); Loading Loading @@ -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 Loading @@ -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(); Loading iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.kt +34 −18 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 { Loading Loading @@ -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 && Loading @@ -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 } Loading @@ -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) Loading @@ -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 Loading @@ -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) } /** Loading @@ -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 Loading Loading @@ -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 Loading @@ -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, ) } } } iconloaderlib/src/com/android/launcher3/icons/BubbleIconFactory.java +2 −2 Original line number Diff line number Diff line Loading @@ -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); } /** Loading @@ -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); } Loading iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.kt +13 −8 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() Loading @@ -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 Loading @@ -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 } Loading Loading @@ -235,6 +233,7 @@ class ClockDrawableWrapper private constructor(base: AdaptiveIconDrawable) : ) : DelegateFactory { override fun newDelegate( bitmapInfo: BitmapInfo, iconShape: IconShape, paint: Paint, host: FastBitmapDrawable, ): FastBitmapDrawableDelegate { Loading Loading @@ -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) Loading iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.kt +43 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading Loading @@ -249,6 +286,7 @@ constructor( bitmapInfo, isDisabled, badge?.constantState, iconShape, creationFlags, delegateFactory, level, Loading Loading @@ -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 Loading
iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +88 −22 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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) { Loading @@ -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()); } /** Loading Loading @@ -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]); Loading @@ -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; Loading Loading @@ -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); } /** Loading Loading @@ -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) { Loading @@ -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: Loading @@ -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); Loading Loading @@ -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 Loading @@ -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(); Loading
iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.kt +34 −18 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 { Loading Loading @@ -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 && Loading @@ -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 } Loading @@ -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) Loading @@ -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 Loading @@ -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) } /** Loading @@ -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 Loading Loading @@ -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 Loading @@ -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, ) } } }
iconloaderlib/src/com/android/launcher3/icons/BubbleIconFactory.java +2 −2 Original line number Diff line number Diff line Loading @@ -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); } /** Loading @@ -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); } Loading
iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.kt +13 −8 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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() Loading @@ -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 Loading @@ -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 } Loading Loading @@ -235,6 +233,7 @@ class ClockDrawableWrapper private constructor(base: AdaptiveIconDrawable) : ) : DelegateFactory { override fun newDelegate( bitmapInfo: BitmapInfo, iconShape: IconShape, paint: Paint, host: FastBitmapDrawable, ): FastBitmapDrawableDelegate { Loading Loading @@ -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) Loading
iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.kt +43 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading Loading @@ -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) Loading Loading @@ -249,6 +286,7 @@ constructor( bitmapInfo, isDisabled, badge?.constantState, iconShape, creationFlags, delegateFactory, level, Loading Loading @@ -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