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

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

Merge "Creating delegate interface for fastbitmap drawable" into main

parents cddf537b 8410508a
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -32,6 +32,9 @@ android_library {
        "src/**/*.java",
        "src/**/*.kt",
    ],
    kotlincflags: [
        "-Xjvm-default=all",
    ],
}

android_library {
@@ -56,4 +59,7 @@ android_library {
        "//apex_available:platform",
        "com.android.permission",
    ],
    kotlincflags: [
        "-Xjvm-default=all",
    ],
}
+35 −40
Original line number Diff line number Diff line
@@ -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

@@ -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 &&
@@ -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
@@ -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.
@@ -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
@@ -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 */
@@ -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 {
@@ -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 {
+48 −64
Original line number Diff line number Diff line
@@ -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;

@@ -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;
@@ -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]);
@@ -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;
        }
@@ -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;

@@ -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();
@@ -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);
@@ -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);
@@ -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);
        }
    }
}
+47 −42
Original line number Diff line number Diff line
@@ -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))

@@ -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

@@ -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) {
@@ -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
@@ -145,6 +154,7 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback {
            paint.alpha = alpha
            invalidateSelf()
            badge?.alpha = alpha
            delegate.setAlpha(alpha)
        }
    }

@@ -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
@@ -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
        }

+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