Loading iconloaderlib/Android.bp +6 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,9 @@ android_library { "src/**/*.java", "src/**/*.kt", ], kotlincflags: [ "-Xjvm-default=all", ], } android_library { Loading @@ -56,4 +59,7 @@ android_library { "//apex_available:platform", "com.android.permission", ], kotlincflags: [ "-Xjvm-default=all", ], } iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.kt +35 −40 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.IntDef import com.android.launcher3.icons.PlaceHolderDrawableDelegate.PlaceHolderDelegateFactory import com.android.launcher3.icons.cache.CacheLookupFlag import com.android.launcher3.util.FlagOp Loading Loading @@ -110,7 +111,7 @@ open class BitmapInfo( ): FastBitmapDrawable { val drawable: FastBitmapDrawable = if (isLowRes) { PlaceHolderIconDrawable(this, context) FastBitmapDrawable(this, PlaceHolderDelegateFactory(context)) } else if ( (creationFlags and FLAG_THEMED) != 0 && themedBitmap != null && Loading @@ -125,16 +126,21 @@ open class BitmapInfo( } protected fun applyFlags( context: Context, drawable: FastBitmapDrawable, @DrawableCreationFlags creationFlags: Int, badgeShape: Path? context: Context, drawable: FastBitmapDrawable, @DrawableCreationFlags creationFlags: Int, badgeShape: Path?, ) { this.creationFlags = creationFlags drawable.disabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f) drawable.creationFlags = creationFlags if ((creationFlags and FLAG_NO_BADGE) == 0) { val badge = getBadgeDrawable( context, (creationFlags and FLAG_THEMED) != 0, (creationFlags and FLAG_SKIP_USER_BADGE) != 0, badgeShape val badge = getBadgeDrawable( context, (creationFlags and FLAG_THEMED) != 0, (creationFlags and FLAG_SKIP_USER_BADGE) != 0, badgeShape, ) if (badge != null) { drawable.badge = badge Loading @@ -156,6 +162,7 @@ open class BitmapInfo( /** * Creates a Drawable for an icon badge for this BitmapInfo * * @param context Context * @param isThemed If the drawable is themed. * @param skipUserBadge If should skip User Profile badging. Loading @@ -163,7 +170,10 @@ open class BitmapInfo( * @return Drawable for an icon Badge. */ private fun getBadgeDrawable( context: Context, isThemed: Boolean, skipUserBadge: Boolean, badgeShape: Path? context: Context, isThemed: Boolean, skipUserBadge: Boolean, badgeShape: Path?, ): Drawable? { if (badgeInfo != null) { var creationFlag = if (isThemed) FLAG_THEMED else 0 Loading @@ -176,44 +186,30 @@ open class BitmapInfo( return null } else { getBadgeDrawableInfo()?.let { return UserBadgeDrawable( context, it.drawableRes, it.colorRes, isThemed, badgeShape ) return UserBadgeDrawable(context, it.drawableRes, it.colorRes, isThemed, badgeShape) } } return null } /** * Returns information about the badge to apply based on current flags. */ /** Returns information about the badge to apply based on current flags. */ fun getBadgeDrawableInfo(): BadgeDrawableInfo? { return when { (flags and FLAG_INSTANT) != 0 -> BadgeDrawableInfo( R.drawable.ic_instant_app_badge, R.color.badge_tint_instant ) (flags and FLAG_WORK) != 0 -> BadgeDrawableInfo( R.drawable.ic_work_app_badge, R.color.badge_tint_work ) (flags and FLAG_CLONE) != 0 -> BadgeDrawableInfo( R.drawable.ic_clone_app_badge, R.color.badge_tint_clone ) (flags and FLAG_PRIVATE) != 0 -> BadgeDrawableInfo( (flags and FLAG_INSTANT) != 0 -> BadgeDrawableInfo(R.drawable.ic_instant_app_badge, R.color.badge_tint_instant) (flags and FLAG_WORK) != 0 -> BadgeDrawableInfo(R.drawable.ic_work_app_badge, R.color.badge_tint_work) (flags and FLAG_CLONE) != 0 -> BadgeDrawableInfo(R.drawable.ic_clone_app_badge, R.color.badge_tint_clone) (flags and FLAG_PRIVATE) != 0 -> BadgeDrawableInfo( R.drawable.ic_private_profile_app_badge, R.color.badge_tint_private R.color.badge_tint_private, ) else -> null } } /** Interface to be implemented by drawables to provide a custom BitmapInfo */ interface Extender { /** Called for creating a custom BitmapInfo */ Loading @@ -230,13 +226,14 @@ open class BitmapInfo( /** * Drawables backing a specific badge shown on app icons. * * @param drawableRes Drawable resource for the badge. * @param colorRes Color resource to tint the badge. */ @JvmRecord data class BadgeDrawableInfo( @field:DrawableRes @param:DrawableRes val drawableRes: Int, @field:ColorRes @param:ColorRes val colorRes: Int @field:ColorRes @param:ColorRes val colorRes: Int, ) companion object { Loading @@ -254,10 +251,8 @@ open class BitmapInfo( const val FLAG_NO_BADGE: Int = 1 shl 1 const val FLAG_SKIP_USER_BADGE: Int = 1 shl 2 @JvmField val LOW_RES_ICON: Bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8) @JvmField val LOW_RES_INFO: BitmapInfo = fromBitmap(LOW_RES_ICON) @JvmField val LOW_RES_ICON: Bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8) @JvmField val LOW_RES_INFO: BitmapInfo = fromBitmap(LOW_RES_ICON) @JvmStatic fun fromBitmap(bitmap: Bitmap): BitmapInfo { Loading iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java +48 −64 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ package com.android.launcher3.icons; import static com.android.launcher3.icons.FastBitmapDrawable.FULLY_OPAQUE; import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter; import static com.android.launcher3.icons.IconProvider.ATLEAST_T; import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; Loading @@ -41,8 +43,11 @@ import android.os.Bundle; import android.os.SystemClock; import android.util.Log; import androidx.annotation.NonNull; import com.android.launcher3.icons.FastBitmapDrawableDelegate.DelegateFactory; import com.android.launcher3.icons.cache.CacheLookupFlag; import com.android.launcher3.icons.mono.ThemedIconDrawable; import com.android.launcher3.icons.mono.ThemedIconDelegate; import java.util.Calendar; import java.util.concurrent.TimeUnit; Loading Loading @@ -292,7 +297,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap int themedFgColor; ColorFilter bgFilter; if ((creationFlags & FLAG_THEMED) != 0 && themeData != null) { int[] colors = ThemedIconDrawable.getColors(context); int[] colors = ThemedIconDelegate.getColors(context); Drawable tintedDrawable = themeData.baseDrawableState.newDrawable().mutate(); themedFgColor = colors[1]; tintedDrawable.setTint(colors[1]); Loading @@ -308,9 +313,10 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap if (info == null) { return super.newIcon(context, creationFlags); } ClockIconDrawable.ClockConstantState cs = new ClockIconDrawable.ClockConstantState( this, themedFgColor, boundsOffset, info, bg, bgFilter); FastBitmapDrawable d = cs.newDrawable(); ClockDelegateInfo delegateInfo = new ClockDelegateInfo(themedFgColor, boundsOffset, animInfo, bg, bgFilter); FastBitmapDrawable d = new FastBitmapDrawable(this, delegateInfo); applyFlags(context, d, creationFlags, null); return d; } Loading @@ -333,10 +339,23 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap } } private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable { private record ClockDelegateInfo( int themeFgColor, float boundsOffset, AnimationInfo animInfo, Bitmap bg, ColorFilter bgFilter) implements DelegateFactory { @NonNull @Override public FastBitmapDrawableDelegate newDelegate(@NonNull BitmapInfo bitmapInfo, @NonNull Paint paint, @NonNull FastBitmapDrawable host) { return new ClockDrawableDelegate(this, host); } } private static class ClockDrawableDelegate implements FastBitmapDrawableDelegate, Runnable { private final Calendar mTime = Calendar.getInstance(); private final FastBitmapDrawable mHost; private final float mBoundsOffset; private final AnimationInfo mAnimInfo; Loading @@ -349,15 +368,15 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap private final LayerDrawable mFG; private final float mCanvasScale; ClockIconDrawable(ClockConstantState cs) { super(cs.getBitmapInfo()); mBoundsOffset = cs.mBoundsOffset; mAnimInfo = cs.mAnimInfo; ClockDrawableDelegate(ClockDelegateInfo cs, FastBitmapDrawable host) { mHost = host; mBoundsOffset = cs.boundsOffset; mAnimInfo = cs.animInfo; mBG = cs.mBG; mBgFilter = cs.mBgFilter; mBgPaint.setColorFilter(cs.mBgFilter); mThemedFgColor = cs.mThemedFgColor; mBG = cs.bg; mBgFilter = cs.bgFilter; mBgPaint.setColorFilter(cs.bgFilter); mThemedFgColor = cs.themeFgColor; mFullDrawable = (AdaptiveIconDrawable) mAnimInfo.baseDrawableState.newDrawable().mutate(); Loading @@ -371,24 +390,22 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap @Override public void setAlpha(int alpha) { super.setAlpha(alpha); mBgPaint.setAlpha(alpha); mFG.setAlpha(alpha); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); public void onBoundsChange(Rect bounds) { // b/211896569 AdaptiveIcon does not work properly when bounds // are not aligned to top/left corner mFullDrawable.setBounds(0, 0, bounds.width(), bounds.height()); } @Override public void drawInternal(Canvas canvas, Rect bounds) { public void drawContent(@NonNull BitmapInfo info, @NonNull Canvas canvas, @NonNull Rect bounds, @NonNull Paint paint) { if (mAnimInfo == null) { super.drawInternal(canvas, bounds); FastBitmapDrawableDelegate.super.drawContent(info, canvas, bounds, paint); return; } canvas.drawBitmap(mBG, null, bounds, mBgPaint); Loading @@ -411,9 +428,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap } @Override protected void updateFilter() { super.updateFilter(); boolean isDisabled = isDisabled(); public void updateFilter(boolean isDisabled, float disabledAlpha) { int alpha = isDisabled ? (int) (disabledAlpha * FULLY_OPAQUE) : FULLY_OPAQUE; setAlpha(alpha); mBgPaint.setColorFilter(isDisabled ? getDisabledColorFilter() : mBgFilter); Loading @@ -421,68 +436,37 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap } @Override public int getIconColor() { return isThemed() ? mThemedFgColor : super.getIconColor(); public int getIconColor(@NonNull BitmapInfo info) { return isThemed() ? mThemedFgColor : FastBitmapDrawableDelegate.super.getIconColor(info); } @Override public void run() { if (mAnimInfo.applyTime(mTime, mFG)) { invalidateSelf(); mHost.invalidateSelf(); } else { reschedule(); } } @Override public boolean setVisible(boolean visible, boolean restart) { boolean result = super.setVisible(visible, restart); if (visible) { public void onVisibilityChanged(boolean isVisible) { if (isVisible) { reschedule(); } else { unscheduleSelf(this); mHost.unscheduleSelf(this); } return result; } private void reschedule() { if (!isVisible()) { if (!mHost.isVisible()) { return; } unscheduleSelf(this); mHost.unscheduleSelf(this); final long upTime = SystemClock.uptimeMillis(); final long step = TICK_MS; /* tick every 200 ms */ scheduleSelf(this, upTime - ((upTime % step)) + step); } @Override public FastBitmapConstantState newConstantState() { return new ClockConstantState(bitmapInfo, mThemedFgColor, mBoundsOffset, mAnimInfo, mBG, mBgPaint.getColorFilter()); } private static class ClockConstantState extends FastBitmapConstantState { private final float mBoundsOffset; private final AnimationInfo mAnimInfo; private final Bitmap mBG; private final ColorFilter mBgFilter; private final int mThemedFgColor; ClockConstantState(BitmapInfo info, int themedFgColor, float boundsOffset, AnimationInfo animInfo, Bitmap bg, ColorFilter bgFilter) { super(info); mBoundsOffset = boundsOffset; mAnimInfo = animInfo; mBG = bg; mBgFilter = bgFilter; mThemedFgColor = themedFgColor; } @Override public FastBitmapDrawable createDrawable() { return new ClockIconDrawable(this); } mHost.scheduleSelf(this, upTime - ((upTime % step)) + step); } } } iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.kt +47 −42 Original line number Diff line number Diff line Loading @@ -36,11 +36,17 @@ import android.view.animation.DecelerateInterpolator import android.view.animation.Interpolator import android.view.animation.PathInterpolator import androidx.annotation.VisibleForTesting import androidx.core.graphics.ColorUtils import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags import com.android.launcher3.icons.FastBitmapDrawableDelegate.DelegateFactory import com.android.launcher3.icons.FastBitmapDrawableDelegate.SimpleDelegateFactory import kotlin.math.min open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { class FastBitmapDrawable @JvmOverloads constructor( info: BitmapInfo?, private val delegateFactory: DelegateFactory = SimpleDelegateFactory, ) : Drawable(), Callback { @JvmOverloads constructor(b: Bitmap, iconColor: Int = 0) : this(BitmapInfo.of(b, iconColor)) Loading @@ -49,6 +55,8 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { @JvmField protected val paint: Paint = Paint(FILTER_BITMAP_FLAG or ANTI_ALIAS_FLAG) val delegate = delegateFactory.newDelegate(bitmapInfo, paint, this) @JvmField @VisibleForTesting var isPressed: Boolean = false @JvmField @VisibleForTesting var isHovered: Boolean = false Loading Loading @@ -93,6 +101,7 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { override fun onBoundsChange(bounds: Rect) { super.onBoundsChange(bounds) badge?.setBadgeBounds(bounds) delegate.onBoundsChange(bounds) } override fun draw(canvas: Canvas) { Loading @@ -101,27 +110,27 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { val bounds = bounds canvas.scale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) drawInternal(canvas, bounds) badge?.draw(canvas) canvas.restoreToCount(count) } else { drawInternal(canvas, bounds) badge?.draw(canvas) } } protected open fun drawInternal(canvas: Canvas, bounds: Rect) { canvas.drawBitmap(bitmapInfo.icon, null, bounds, paint) private fun drawInternal(canvas: Canvas, bounds: Rect) { delegate.drawContent(bitmapInfo, canvas, bounds, paint) badge?.draw(canvas) } /** Returns the primary icon color, slightly tinted white */ open fun getIconColor(): Int = ColorUtils.compositeColors( GraphicsUtils.setColorAlphaBound(Color.WHITE, WHITE_SCRIM_ALPHA), bitmapInfo.color, ) fun getIconColor(): Int = delegate.getIconColor(bitmapInfo) /** Returns if this represents a themed icon */ open fun isThemed(): Boolean = false fun isThemed(): Boolean = delegate.isThemed() override fun setVisible(visible: Boolean, restart: Boolean): Boolean = super.setVisible(visible, restart).also { delegate.onVisibilityChanged(visible) } override fun onLevelChange(level: Int) = delegate.onLevelChange(level) /** * Returns true if the drawable was created with theme, even if it doesn't support theming Loading @@ -145,6 +154,7 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { paint.alpha = alpha invalidateSelf() badge?.alpha = alpha delegate.setAlpha(alpha) } } Loading Loading @@ -227,23 +237,22 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { } /** Updates the paint to reflect the current brightness and saturation. */ protected open fun updateFilter() { private fun updateFilter() { paint.setColorFilter(if (isDisabled) getDisabledColorFilter(disabledAlpha) else paintFilter) badge?.colorFilter = colorFilter delegate.updateFilter(isDisabled, disabledAlpha) invalidateSelf() } protected open fun newConstantState(): FastBitmapConstantState { return FastBitmapConstantState(bitmapInfo) } override fun getConstantState(): ConstantState { val cs = newConstantState() cs.mIsDisabled = isDisabled cs.mBadgeConstantState = badge?.constantState cs.mCreationFlags = creationFlags return cs } override fun getConstantState() = FastBitmapConstantState( bitmapInfo, isDisabled, badge?.constantState, creationFlags, delegateFactory, level, ) // Returns if the FastBitmapDrawable contains a badge. fun hasBadge(): Boolean = (creationFlags and BitmapInfo.FLAG_NO_BADGE) == 0 Loading @@ -264,27 +273,23 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { unscheduleSelf(what) } open class FastBitmapConstantState(val bitmapInfo: BitmapInfo) : ConstantState() { // These are initialized later so that subclasses don't need to // pass everything in constructor var mIsDisabled: Boolean = false var mBadgeConstantState: ConstantState? = null @DrawableCreationFlags var mCreationFlags: Int = 0 constructor(bitmap: Bitmap, color: Int) : this(BitmapInfo.of(bitmap, color)) protected open fun createDrawable(): FastBitmapDrawable { return FastBitmapDrawable(bitmapInfo) } data class FastBitmapConstantState( val bitmapInfo: BitmapInfo, val isDisabled: Boolean, val badgeConstantState: ConstantState?, val creationFlags: Int, val delegateFactory: DelegateFactory, val level: Int, ) : ConstantState() { override fun newDrawable(): FastBitmapDrawable { val drawable = createDrawable() drawable.isDisabled = mIsDisabled if (mBadgeConstantState != null) { drawable.badge = mBadgeConstantState!!.newDrawable() val drawable = FastBitmapDrawable(bitmapInfo, delegateFactory) drawable.isDisabled = isDisabled if (badgeConstantState != null) { drawable.badge = badgeConstantState.newDrawable() } drawable.creationFlags = mCreationFlags drawable.creationFlags = creationFlags drawable.level = level return drawable } Loading iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawableDelegate.kt 0 → 100644 +75 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import androidx.core.graphics.ColorUtils /** A delegate for changing the rendering of [FastBitmapDrawable], to support multi-inheritance */ interface FastBitmapDrawableDelegate { /** [android.graphics.drawable.Drawable.onBoundsChange] */ fun onBoundsChange(bounds: Rect) {} /** [android.graphics.drawable.Drawable.draw] */ fun drawContent(info: BitmapInfo, canvas: Canvas, bounds: Rect, paint: Paint) { canvas.drawBitmap(info.icon, null, bounds, paint) } /** [FastBitmapDrawable.getIconColor] */ fun getIconColor(info: BitmapInfo): Int = ColorUtils.compositeColors( GraphicsUtils.setColorAlphaBound(Color.WHITE, FastBitmapDrawable.WHITE_SCRIM_ALPHA), info.color, ) /** [FastBitmapDrawable.isThemed] */ fun isThemed() = false /** [android.graphics.drawable.Drawable.setAlpha] */ fun setAlpha(alpha: Int) {} /** [android.graphics.drawable.Drawable.setColorFilter] */ fun updateFilter(isDisabled: Boolean, disabledAlpha: Float) {} /** [android.graphics.drawable.Drawable.setVisible] */ fun onVisibilityChanged(isVisible: Boolean) {} /** [android.graphics.drawable.Drawable.onLevelChange] */ fun onLevelChange(level: Int): Boolean = false /** * Interface for creating new delegates. This should not store any state information and can * safely be stored in a [android.graphics.drawable.Drawable.ConstantState] */ fun interface DelegateFactory { fun newDelegate( bitmapInfo: BitmapInfo, paint: Paint, host: FastBitmapDrawable, ): FastBitmapDrawableDelegate } object SimpleDelegateFactory : DelegateFactory { override fun newDelegate(bitmapInfo: BitmapInfo, paint: Paint, host: FastBitmapDrawable) = object : FastBitmapDrawableDelegate {} } } Loading
iconloaderlib/Android.bp +6 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,9 @@ android_library { "src/**/*.java", "src/**/*.kt", ], kotlincflags: [ "-Xjvm-default=all", ], } android_library { Loading @@ -56,4 +59,7 @@ android_library { "//apex_available:platform", "com.android.permission", ], kotlincflags: [ "-Xjvm-default=all", ], }
iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.kt +35 −40 Original line number Diff line number Diff line Loading @@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable import androidx.annotation.ColorRes import androidx.annotation.DrawableRes import androidx.annotation.IntDef import com.android.launcher3.icons.PlaceHolderDrawableDelegate.PlaceHolderDelegateFactory import com.android.launcher3.icons.cache.CacheLookupFlag import com.android.launcher3.util.FlagOp Loading Loading @@ -110,7 +111,7 @@ open class BitmapInfo( ): FastBitmapDrawable { val drawable: FastBitmapDrawable = if (isLowRes) { PlaceHolderIconDrawable(this, context) FastBitmapDrawable(this, PlaceHolderDelegateFactory(context)) } else if ( (creationFlags and FLAG_THEMED) != 0 && themedBitmap != null && Loading @@ -125,16 +126,21 @@ open class BitmapInfo( } protected fun applyFlags( context: Context, drawable: FastBitmapDrawable, @DrawableCreationFlags creationFlags: Int, badgeShape: Path? context: Context, drawable: FastBitmapDrawable, @DrawableCreationFlags creationFlags: Int, badgeShape: Path?, ) { this.creationFlags = creationFlags drawable.disabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f) drawable.creationFlags = creationFlags if ((creationFlags and FLAG_NO_BADGE) == 0) { val badge = getBadgeDrawable( context, (creationFlags and FLAG_THEMED) != 0, (creationFlags and FLAG_SKIP_USER_BADGE) != 0, badgeShape val badge = getBadgeDrawable( context, (creationFlags and FLAG_THEMED) != 0, (creationFlags and FLAG_SKIP_USER_BADGE) != 0, badgeShape, ) if (badge != null) { drawable.badge = badge Loading @@ -156,6 +162,7 @@ open class BitmapInfo( /** * Creates a Drawable for an icon badge for this BitmapInfo * * @param context Context * @param isThemed If the drawable is themed. * @param skipUserBadge If should skip User Profile badging. Loading @@ -163,7 +170,10 @@ open class BitmapInfo( * @return Drawable for an icon Badge. */ private fun getBadgeDrawable( context: Context, isThemed: Boolean, skipUserBadge: Boolean, badgeShape: Path? context: Context, isThemed: Boolean, skipUserBadge: Boolean, badgeShape: Path?, ): Drawable? { if (badgeInfo != null) { var creationFlag = if (isThemed) FLAG_THEMED else 0 Loading @@ -176,44 +186,30 @@ open class BitmapInfo( return null } else { getBadgeDrawableInfo()?.let { return UserBadgeDrawable( context, it.drawableRes, it.colorRes, isThemed, badgeShape ) return UserBadgeDrawable(context, it.drawableRes, it.colorRes, isThemed, badgeShape) } } return null } /** * Returns information about the badge to apply based on current flags. */ /** Returns information about the badge to apply based on current flags. */ fun getBadgeDrawableInfo(): BadgeDrawableInfo? { return when { (flags and FLAG_INSTANT) != 0 -> BadgeDrawableInfo( R.drawable.ic_instant_app_badge, R.color.badge_tint_instant ) (flags and FLAG_WORK) != 0 -> BadgeDrawableInfo( R.drawable.ic_work_app_badge, R.color.badge_tint_work ) (flags and FLAG_CLONE) != 0 -> BadgeDrawableInfo( R.drawable.ic_clone_app_badge, R.color.badge_tint_clone ) (flags and FLAG_PRIVATE) != 0 -> BadgeDrawableInfo( (flags and FLAG_INSTANT) != 0 -> BadgeDrawableInfo(R.drawable.ic_instant_app_badge, R.color.badge_tint_instant) (flags and FLAG_WORK) != 0 -> BadgeDrawableInfo(R.drawable.ic_work_app_badge, R.color.badge_tint_work) (flags and FLAG_CLONE) != 0 -> BadgeDrawableInfo(R.drawable.ic_clone_app_badge, R.color.badge_tint_clone) (flags and FLAG_PRIVATE) != 0 -> BadgeDrawableInfo( R.drawable.ic_private_profile_app_badge, R.color.badge_tint_private R.color.badge_tint_private, ) else -> null } } /** Interface to be implemented by drawables to provide a custom BitmapInfo */ interface Extender { /** Called for creating a custom BitmapInfo */ Loading @@ -230,13 +226,14 @@ open class BitmapInfo( /** * Drawables backing a specific badge shown on app icons. * * @param drawableRes Drawable resource for the badge. * @param colorRes Color resource to tint the badge. */ @JvmRecord data class BadgeDrawableInfo( @field:DrawableRes @param:DrawableRes val drawableRes: Int, @field:ColorRes @param:ColorRes val colorRes: Int @field:ColorRes @param:ColorRes val colorRes: Int, ) companion object { Loading @@ -254,10 +251,8 @@ open class BitmapInfo( const val FLAG_NO_BADGE: Int = 1 shl 1 const val FLAG_SKIP_USER_BADGE: Int = 1 shl 2 @JvmField val LOW_RES_ICON: Bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8) @JvmField val LOW_RES_INFO: BitmapInfo = fromBitmap(LOW_RES_ICON) @JvmField val LOW_RES_ICON: Bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8) @JvmField val LOW_RES_INFO: BitmapInfo = fromBitmap(LOW_RES_ICON) @JvmStatic fun fromBitmap(bitmap: Bitmap): BitmapInfo { Loading
iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java +48 −64 Original line number Diff line number Diff line Loading @@ -15,6 +15,8 @@ */ package com.android.launcher3.icons; import static com.android.launcher3.icons.FastBitmapDrawable.FULLY_OPAQUE; import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter; import static com.android.launcher3.icons.IconProvider.ATLEAST_T; import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG; Loading @@ -41,8 +43,11 @@ import android.os.Bundle; import android.os.SystemClock; import android.util.Log; import androidx.annotation.NonNull; import com.android.launcher3.icons.FastBitmapDrawableDelegate.DelegateFactory; import com.android.launcher3.icons.cache.CacheLookupFlag; import com.android.launcher3.icons.mono.ThemedIconDrawable; import com.android.launcher3.icons.mono.ThemedIconDelegate; import java.util.Calendar; import java.util.concurrent.TimeUnit; Loading Loading @@ -292,7 +297,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap int themedFgColor; ColorFilter bgFilter; if ((creationFlags & FLAG_THEMED) != 0 && themeData != null) { int[] colors = ThemedIconDrawable.getColors(context); int[] colors = ThemedIconDelegate.getColors(context); Drawable tintedDrawable = themeData.baseDrawableState.newDrawable().mutate(); themedFgColor = colors[1]; tintedDrawable.setTint(colors[1]); Loading @@ -308,9 +313,10 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap if (info == null) { return super.newIcon(context, creationFlags); } ClockIconDrawable.ClockConstantState cs = new ClockIconDrawable.ClockConstantState( this, themedFgColor, boundsOffset, info, bg, bgFilter); FastBitmapDrawable d = cs.newDrawable(); ClockDelegateInfo delegateInfo = new ClockDelegateInfo(themedFgColor, boundsOffset, animInfo, bg, bgFilter); FastBitmapDrawable d = new FastBitmapDrawable(this, delegateInfo); applyFlags(context, d, creationFlags, null); return d; } Loading @@ -333,10 +339,23 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap } } private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable { private record ClockDelegateInfo( int themeFgColor, float boundsOffset, AnimationInfo animInfo, Bitmap bg, ColorFilter bgFilter) implements DelegateFactory { @NonNull @Override public FastBitmapDrawableDelegate newDelegate(@NonNull BitmapInfo bitmapInfo, @NonNull Paint paint, @NonNull FastBitmapDrawable host) { return new ClockDrawableDelegate(this, host); } } private static class ClockDrawableDelegate implements FastBitmapDrawableDelegate, Runnable { private final Calendar mTime = Calendar.getInstance(); private final FastBitmapDrawable mHost; private final float mBoundsOffset; private final AnimationInfo mAnimInfo; Loading @@ -349,15 +368,15 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap private final LayerDrawable mFG; private final float mCanvasScale; ClockIconDrawable(ClockConstantState cs) { super(cs.getBitmapInfo()); mBoundsOffset = cs.mBoundsOffset; mAnimInfo = cs.mAnimInfo; ClockDrawableDelegate(ClockDelegateInfo cs, FastBitmapDrawable host) { mHost = host; mBoundsOffset = cs.boundsOffset; mAnimInfo = cs.animInfo; mBG = cs.mBG; mBgFilter = cs.mBgFilter; mBgPaint.setColorFilter(cs.mBgFilter); mThemedFgColor = cs.mThemedFgColor; mBG = cs.bg; mBgFilter = cs.bgFilter; mBgPaint.setColorFilter(cs.bgFilter); mThemedFgColor = cs.themeFgColor; mFullDrawable = (AdaptiveIconDrawable) mAnimInfo.baseDrawableState.newDrawable().mutate(); Loading @@ -371,24 +390,22 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap @Override public void setAlpha(int alpha) { super.setAlpha(alpha); mBgPaint.setAlpha(alpha); mFG.setAlpha(alpha); } @Override protected void onBoundsChange(Rect bounds) { super.onBoundsChange(bounds); public void onBoundsChange(Rect bounds) { // b/211896569 AdaptiveIcon does not work properly when bounds // are not aligned to top/left corner mFullDrawable.setBounds(0, 0, bounds.width(), bounds.height()); } @Override public void drawInternal(Canvas canvas, Rect bounds) { public void drawContent(@NonNull BitmapInfo info, @NonNull Canvas canvas, @NonNull Rect bounds, @NonNull Paint paint) { if (mAnimInfo == null) { super.drawInternal(canvas, bounds); FastBitmapDrawableDelegate.super.drawContent(info, canvas, bounds, paint); return; } canvas.drawBitmap(mBG, null, bounds, mBgPaint); Loading @@ -411,9 +428,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap } @Override protected void updateFilter() { super.updateFilter(); boolean isDisabled = isDisabled(); public void updateFilter(boolean isDisabled, float disabledAlpha) { int alpha = isDisabled ? (int) (disabledAlpha * FULLY_OPAQUE) : FULLY_OPAQUE; setAlpha(alpha); mBgPaint.setColorFilter(isDisabled ? getDisabledColorFilter() : mBgFilter); Loading @@ -421,68 +436,37 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap } @Override public int getIconColor() { return isThemed() ? mThemedFgColor : super.getIconColor(); public int getIconColor(@NonNull BitmapInfo info) { return isThemed() ? mThemedFgColor : FastBitmapDrawableDelegate.super.getIconColor(info); } @Override public void run() { if (mAnimInfo.applyTime(mTime, mFG)) { invalidateSelf(); mHost.invalidateSelf(); } else { reschedule(); } } @Override public boolean setVisible(boolean visible, boolean restart) { boolean result = super.setVisible(visible, restart); if (visible) { public void onVisibilityChanged(boolean isVisible) { if (isVisible) { reschedule(); } else { unscheduleSelf(this); mHost.unscheduleSelf(this); } return result; } private void reschedule() { if (!isVisible()) { if (!mHost.isVisible()) { return; } unscheduleSelf(this); mHost.unscheduleSelf(this); final long upTime = SystemClock.uptimeMillis(); final long step = TICK_MS; /* tick every 200 ms */ scheduleSelf(this, upTime - ((upTime % step)) + step); } @Override public FastBitmapConstantState newConstantState() { return new ClockConstantState(bitmapInfo, mThemedFgColor, mBoundsOffset, mAnimInfo, mBG, mBgPaint.getColorFilter()); } private static class ClockConstantState extends FastBitmapConstantState { private final float mBoundsOffset; private final AnimationInfo mAnimInfo; private final Bitmap mBG; private final ColorFilter mBgFilter; private final int mThemedFgColor; ClockConstantState(BitmapInfo info, int themedFgColor, float boundsOffset, AnimationInfo animInfo, Bitmap bg, ColorFilter bgFilter) { super(info); mBoundsOffset = boundsOffset; mAnimInfo = animInfo; mBG = bg; mBgFilter = bgFilter; mThemedFgColor = themedFgColor; } @Override public FastBitmapDrawable createDrawable() { return new ClockIconDrawable(this); } mHost.scheduleSelf(this, upTime - ((upTime % step)) + step); } } }
iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.kt +47 −42 Original line number Diff line number Diff line Loading @@ -36,11 +36,17 @@ import android.view.animation.DecelerateInterpolator import android.view.animation.Interpolator import android.view.animation.PathInterpolator import androidx.annotation.VisibleForTesting import androidx.core.graphics.ColorUtils import com.android.launcher3.icons.BitmapInfo.DrawableCreationFlags import com.android.launcher3.icons.FastBitmapDrawableDelegate.DelegateFactory import com.android.launcher3.icons.FastBitmapDrawableDelegate.SimpleDelegateFactory import kotlin.math.min open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { class FastBitmapDrawable @JvmOverloads constructor( info: BitmapInfo?, private val delegateFactory: DelegateFactory = SimpleDelegateFactory, ) : Drawable(), Callback { @JvmOverloads constructor(b: Bitmap, iconColor: Int = 0) : this(BitmapInfo.of(b, iconColor)) Loading @@ -49,6 +55,8 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { @JvmField protected val paint: Paint = Paint(FILTER_BITMAP_FLAG or ANTI_ALIAS_FLAG) val delegate = delegateFactory.newDelegate(bitmapInfo, paint, this) @JvmField @VisibleForTesting var isPressed: Boolean = false @JvmField @VisibleForTesting var isHovered: Boolean = false Loading Loading @@ -93,6 +101,7 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { override fun onBoundsChange(bounds: Rect) { super.onBoundsChange(bounds) badge?.setBadgeBounds(bounds) delegate.onBoundsChange(bounds) } override fun draw(canvas: Canvas) { Loading @@ -101,27 +110,27 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { val bounds = bounds canvas.scale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY()) drawInternal(canvas, bounds) badge?.draw(canvas) canvas.restoreToCount(count) } else { drawInternal(canvas, bounds) badge?.draw(canvas) } } protected open fun drawInternal(canvas: Canvas, bounds: Rect) { canvas.drawBitmap(bitmapInfo.icon, null, bounds, paint) private fun drawInternal(canvas: Canvas, bounds: Rect) { delegate.drawContent(bitmapInfo, canvas, bounds, paint) badge?.draw(canvas) } /** Returns the primary icon color, slightly tinted white */ open fun getIconColor(): Int = ColorUtils.compositeColors( GraphicsUtils.setColorAlphaBound(Color.WHITE, WHITE_SCRIM_ALPHA), bitmapInfo.color, ) fun getIconColor(): Int = delegate.getIconColor(bitmapInfo) /** Returns if this represents a themed icon */ open fun isThemed(): Boolean = false fun isThemed(): Boolean = delegate.isThemed() override fun setVisible(visible: Boolean, restart: Boolean): Boolean = super.setVisible(visible, restart).also { delegate.onVisibilityChanged(visible) } override fun onLevelChange(level: Int) = delegate.onLevelChange(level) /** * Returns true if the drawable was created with theme, even if it doesn't support theming Loading @@ -145,6 +154,7 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { paint.alpha = alpha invalidateSelf() badge?.alpha = alpha delegate.setAlpha(alpha) } } Loading Loading @@ -227,23 +237,22 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { } /** Updates the paint to reflect the current brightness and saturation. */ protected open fun updateFilter() { private fun updateFilter() { paint.setColorFilter(if (isDisabled) getDisabledColorFilter(disabledAlpha) else paintFilter) badge?.colorFilter = colorFilter delegate.updateFilter(isDisabled, disabledAlpha) invalidateSelf() } protected open fun newConstantState(): FastBitmapConstantState { return FastBitmapConstantState(bitmapInfo) } override fun getConstantState(): ConstantState { val cs = newConstantState() cs.mIsDisabled = isDisabled cs.mBadgeConstantState = badge?.constantState cs.mCreationFlags = creationFlags return cs } override fun getConstantState() = FastBitmapConstantState( bitmapInfo, isDisabled, badge?.constantState, creationFlags, delegateFactory, level, ) // Returns if the FastBitmapDrawable contains a badge. fun hasBadge(): Boolean = (creationFlags and BitmapInfo.FLAG_NO_BADGE) == 0 Loading @@ -264,27 +273,23 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback { unscheduleSelf(what) } open class FastBitmapConstantState(val bitmapInfo: BitmapInfo) : ConstantState() { // These are initialized later so that subclasses don't need to // pass everything in constructor var mIsDisabled: Boolean = false var mBadgeConstantState: ConstantState? = null @DrawableCreationFlags var mCreationFlags: Int = 0 constructor(bitmap: Bitmap, color: Int) : this(BitmapInfo.of(bitmap, color)) protected open fun createDrawable(): FastBitmapDrawable { return FastBitmapDrawable(bitmapInfo) } data class FastBitmapConstantState( val bitmapInfo: BitmapInfo, val isDisabled: Boolean, val badgeConstantState: ConstantState?, val creationFlags: Int, val delegateFactory: DelegateFactory, val level: Int, ) : ConstantState() { override fun newDrawable(): FastBitmapDrawable { val drawable = createDrawable() drawable.isDisabled = mIsDisabled if (mBadgeConstantState != null) { drawable.badge = mBadgeConstantState!!.newDrawable() val drawable = FastBitmapDrawable(bitmapInfo, delegateFactory) drawable.isDisabled = isDisabled if (badgeConstantState != null) { drawable.badge = badgeConstantState.newDrawable() } drawable.creationFlags = mCreationFlags drawable.creationFlags = creationFlags drawable.level = level return drawable } Loading
iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawableDelegate.kt 0 → 100644 +75 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Rect import androidx.core.graphics.ColorUtils /** A delegate for changing the rendering of [FastBitmapDrawable], to support multi-inheritance */ interface FastBitmapDrawableDelegate { /** [android.graphics.drawable.Drawable.onBoundsChange] */ fun onBoundsChange(bounds: Rect) {} /** [android.graphics.drawable.Drawable.draw] */ fun drawContent(info: BitmapInfo, canvas: Canvas, bounds: Rect, paint: Paint) { canvas.drawBitmap(info.icon, null, bounds, paint) } /** [FastBitmapDrawable.getIconColor] */ fun getIconColor(info: BitmapInfo): Int = ColorUtils.compositeColors( GraphicsUtils.setColorAlphaBound(Color.WHITE, FastBitmapDrawable.WHITE_SCRIM_ALPHA), info.color, ) /** [FastBitmapDrawable.isThemed] */ fun isThemed() = false /** [android.graphics.drawable.Drawable.setAlpha] */ fun setAlpha(alpha: Int) {} /** [android.graphics.drawable.Drawable.setColorFilter] */ fun updateFilter(isDisabled: Boolean, disabledAlpha: Float) {} /** [android.graphics.drawable.Drawable.setVisible] */ fun onVisibilityChanged(isVisible: Boolean) {} /** [android.graphics.drawable.Drawable.onLevelChange] */ fun onLevelChange(level: Int): Boolean = false /** * Interface for creating new delegates. This should not store any state information and can * safely be stored in a [android.graphics.drawable.Drawable.ConstantState] */ fun interface DelegateFactory { fun newDelegate( bitmapInfo: BitmapInfo, paint: Paint, host: FastBitmapDrawable, ): FastBitmapDrawableDelegate } object SimpleDelegateFactory : DelegateFactory { override fun newDelegate(bitmapInfo: BitmapInfo, paint: Paint, host: FastBitmapDrawable) = object : FastBitmapDrawableDelegate {} } }