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

Commit 8410508a authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Creating delegate interface for fastbitmap drawable

> This allows delegating the custom redering logic, while avoiding duplication of the overall drawable structure
> Also the interface allows nesting multiple delegates

Bug: 421146818
Bug: 421151325
Bug: 421000549
Flag: EXEMPT refactor
Test: Updated existing drawable tests
Change-Id: Ifb9a8041bf30e2b411ab0b28de4598221e1a8021
parent 35fc858d
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