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

Commit b7258703 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Convert BitmapInfo to Kotlin to help with customization" into main

parents b5084fb6 80a430d4
Loading
Loading
Loading
Loading
+0 −300
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.drawable.Drawable;

import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.launcher3.icons.cache.CacheLookupFlag;
import com.android.launcher3.util.FlagOp;

import java.util.Objects;

public class BitmapInfo {

    public static final int FLAG_WORK = 1 << 0;
    public static final int FLAG_INSTANT = 1 << 1;
    public static final int FLAG_CLONE = 1 << 2;
    public static final int FLAG_PRIVATE = 1 << 3;
    public static final int FLAG_WRAPPED_NON_ADAPTIVE = 1 << 4;
    @IntDef(flag = true, value = {
            FLAG_WORK,
            FLAG_INSTANT,
            FLAG_CLONE,
            FLAG_PRIVATE,
            FLAG_WRAPPED_NON_ADAPTIVE
    })
    @interface BitmapInfoFlags {}

    public static final int FLAG_THEMED = 1 << 0;
    public static final int FLAG_NO_BADGE = 1 << 1;
    public static final int FLAG_SKIP_USER_BADGE = 1 << 2;
    @IntDef(flag = true, value = {
            FLAG_THEMED,
            FLAG_NO_BADGE,
            FLAG_SKIP_USER_BADGE,
    })
    public @interface DrawableCreationFlags {}

    public static final Bitmap LOW_RES_ICON = Bitmap.createBitmap(1, 1, Config.ALPHA_8);
    public static final BitmapInfo LOW_RES_INFO = fromBitmap(LOW_RES_ICON);

    public static final String TAG = "BitmapInfo";

    @NonNull
    public final Bitmap icon;
    public final int color;

    @Nullable
    private ThemedBitmap mThemedBitmap;

    public @BitmapInfoFlags int flags;

    // b/377618519: These are saved to debug why work badges sometimes don't show up on work apps
    public @DrawableCreationFlags int creationFlags;

    private BitmapInfo badgeInfo;

    public BitmapInfo(@NonNull Bitmap icon, int color) {
        this.icon = Objects.requireNonNull(icon);
        this.color = color;
    }

    public BitmapInfo withBadgeInfo(BitmapInfo badgeInfo) {
        BitmapInfo result = clone();
        result.badgeInfo = badgeInfo;
        return result;
    }

    /**
     * Returns a bitmapInfo with the flagOP applied
     */
    public BitmapInfo withFlags(@NonNull FlagOp op) {
        if (op == FlagOp.NO_OP) {
            return this;
        }
        BitmapInfo result = clone();
        result.flags = op.apply(result.flags);
        return result;
    }

    protected BitmapInfo copyInternalsTo(BitmapInfo target) {
        target.mThemedBitmap = mThemedBitmap;
        target.flags = flags;
        target.badgeInfo = badgeInfo;
        return target;
    }

    @Override
    public BitmapInfo clone() {
        return copyInternalsTo(new BitmapInfo(icon, color));
    }

    public void setThemedBitmap(@Nullable ThemedBitmap themedBitmap) {
        mThemedBitmap = themedBitmap;
    }

    @Nullable
    public ThemedBitmap getThemedBitmap() {
        return mThemedBitmap;
    }

    /**
     * Ideally icon should not be null, except in cases when generating hardware bitmap failed
     */
    public final boolean isNullOrLowRes() {
        return icon == null || icon == LOW_RES_ICON;
    }

    public final boolean isLowRes() {
        return getMatchingLookupFlag().useLowRes();
    }

    /**
     * Returns the lookup flag to match this current state of this info
     */
    public CacheLookupFlag getMatchingLookupFlag() {
        return DEFAULT_LOOKUP_FLAG
                .withUseLowRes(LOW_RES_ICON == icon)
                .withThemeIcon(mThemedBitmap != null);
    }

    /**
     * BitmapInfo can be stored on disk or other persistent storage
     */
    public boolean canPersist() {
        return !isNullOrLowRes();
    }

    /**
     * Creates a drawable for the provided BitmapInfo
     */
    public FastBitmapDrawable newIcon(Context context) {
        return newIcon(context, 0);
    }

    /**
     * Creates a drawable for the provided BitmapInfo
     */
    public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
        return newIcon(context, creationFlags, null);
    }

    /**
     * Creates a drawable for the provided 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.
     * @return FastBitmapDrawable
     */
    public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags,
            @Nullable Path badgeShape) {
        FastBitmapDrawable drawable;
        if (isLowRes()) {
            drawable = new PlaceHolderIconDrawable(this, context);
        } else  if ((creationFlags & FLAG_THEMED) != 0 && mThemedBitmap != null
                && mThemedBitmap != ThemedBitmap.NOT_SUPPORTED) {
            drawable = mThemedBitmap.newDrawable(this, context);
        } else {
            drawable = new FastBitmapDrawable(this);
        }
        applyFlags(context, drawable, creationFlags, badgeShape);
        return drawable;
    }

    protected void applyFlags(Context context, FastBitmapDrawable drawable,
            @DrawableCreationFlags int creationFlags, @Nullable Path badgeShape) {
        this.creationFlags = creationFlags;
        drawable.disabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
        drawable.creationFlags = creationFlags;
        if ((creationFlags & FLAG_NO_BADGE) == 0) {
            Drawable badge = getBadgeDrawable(context, (creationFlags & FLAG_THEMED) != 0,
                    (creationFlags & FLAG_SKIP_USER_BADGE) != 0, badgeShape);
            if (badge != null) {
                drawable.setBadge(badge);
            }
        }
    }

    /**
     * Gets Badge drawable based on current flags
     * @param context Context
     * @param isThemed If Drawable is themed.
     * @param badgeShape Optional Path to mask badges to a shape. Should be 100x100.
     * @return Drawable for the badge.
     */
    public Drawable getBadgeDrawable(Context context, boolean isThemed, @Nullable Path badgeShape) {
        return getBadgeDrawable(context, isThemed, false, badgeShape);
    }

    /**
     * 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.
     * @param badgeShape Optional Path to mask badge Drawable to a shape. Should be 100x100.
     * @return Drawable for an icon Badge.
     */
    @Nullable
    private Drawable getBadgeDrawable(Context context, boolean isThemed, boolean skipUserBadge,
            @Nullable Path badgeShape) {
        if (badgeInfo != null) {
            int creationFlag = isThemed ? FLAG_THEMED : 0;
            if (skipUserBadge) {
                creationFlag |= FLAG_SKIP_USER_BADGE;
            }
            return badgeInfo.newIcon(context, creationFlag, badgeShape);
        }
        if (skipUserBadge) {
            return null;
        } else {
            BadgeDrawableInfo drawableInfo = getBadgeDrawableInfo();
            if (drawableInfo != null) {
                return new UserBadgeDrawable(context, drawableInfo.drawableRes,
                        drawableInfo.colorRes, isThemed, badgeShape);
            }
        }
        return null;
    }

    public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) {
        return of(bitmap, 0);
    }

    public static BitmapInfo of(@NonNull Bitmap bitmap, int color) {
        return new BitmapInfo(bitmap, color);
    }

    /**
     * Returns information about the badge to apply based on current flags.
     */
    @Nullable
    public BadgeDrawableInfo getBadgeDrawableInfo() {
        if ((flags & FLAG_INSTANT) != 0) {
            return new BadgeDrawableInfo(R.drawable.ic_instant_app_badge,
                    R.color.badge_tint_instant);
        } else if ((flags & FLAG_WORK) != 0) {
            return new BadgeDrawableInfo(R.drawable.ic_work_app_badge, R.color.badge_tint_work);
        } else if ((flags & FLAG_CLONE) != 0) {
            return new BadgeDrawableInfo(R.drawable.ic_clone_app_badge, R.color.badge_tint_clone);
        } else if ((flags & FLAG_PRIVATE) != 0) {
            return new BadgeDrawableInfo(R.drawable.ic_private_profile_app_badge,
                    R.color.badge_tint_private);
        } else {
            return null;
        }
    }

    /**
     * Interface to be implemented by drawables to provide a custom BitmapInfo
     */
    public interface Extender {

        /**
         * Called for creating a custom BitmapInfo
         */
        BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
                BaseIconFactory iconFactory, float normalizationScale);

        /**
         * Called to draw the UI independent of any runtime configurations like time or theme
         */
        void drawForPersistence(Canvas canvas);
    }

    /**
     * Drawables backing a specific badge shown on app icons.
     * @param drawableRes Drawable resource for the badge.
     * @param colorRes Color resource to tint the badge.
     */
    public record BadgeDrawableInfo(
            @DrawableRes int drawableRes,
            @ColorRes int colorRes
    ) {}
}
+272 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.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
import androidx.annotation.IntDef
import com.android.launcher3.icons.cache.CacheLookupFlag
import com.android.launcher3.util.FlagOp

open class BitmapInfo(
    @JvmField val icon: Bitmap,
    @JvmField val color: Int,
    @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],
    )
    internal annotation class BitmapInfoFlags

    @IntDef(flag = true, value = [FLAG_THEMED, FLAG_NO_BADGE, FLAG_SKIP_USER_BADGE])
    annotation class DrawableCreationFlags

    // b/377618519: These are saved to debug why work badges sometimes don't show up on work apps
    @DrawableCreationFlags @JvmField var creationFlags: Int = 0

    private var badgeInfo: BitmapInfo? = null

    fun withBadgeInfo(badgeInfo: BitmapInfo?) = clone().also { it.badgeInfo = badgeInfo }

    /** Returns a bitmapInfo with the flagOP applied */
    fun withFlags(op: FlagOp): BitmapInfo {
        if (op === FlagOp.NO_OP) {
            return this
        }
        return clone().also { it.flags = op.apply(it.flags) }
    }

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

    protected fun copyInternalsTo(target: BitmapInfo): BitmapInfo {
        target.themedBitmap = themedBitmap
        target.flags = flags
        target.badgeInfo = badgeInfo
        return target
    }

    // TODO: rename or remove because icon can no longer be null?
    val isNullOrLowRes: Boolean
        get() = icon == LOW_RES_ICON

    val isLowRes: Boolean
        get() = matchingLookupFlag.useLowRes()

    open val matchingLookupFlag: CacheLookupFlag
        /** Returns the lookup flag to match this current state of this info */
        get() =
            CacheLookupFlag.DEFAULT_LOOKUP_FLAG.withUseLowRes(LOW_RES_ICON == icon)
                .withThemeIcon(themedBitmap != null)

    /** BitmapInfo can be stored on disk or other persistent storage */
    open fun canPersist(): Boolean {
        return !isNullOrLowRes
    }

    /** Creates a drawable for the provided BitmapInfo */
    @JvmOverloads
    fun newIcon(
        context: Context,
        @DrawableCreationFlags creationFlags: Int = 0,
    ): FastBitmapDrawable {
        return newIcon(context, creationFlags, null)
    }

    /**
     * Creates a drawable for the provided 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.
     * @return FastBitmapDrawable
     */
    open fun newIcon(
        context: Context,
        @DrawableCreationFlags creationFlags: Int,
        badgeShape: Path?,
    ): FastBitmapDrawable {
        val drawable: FastBitmapDrawable =
            if (isLowRes) {
                PlaceHolderIconDrawable(this, context)
            } else if (
                (creationFlags and FLAG_THEMED) != 0 &&
                    themedBitmap != null &&
                    themedBitmap !== ThemedBitmap.NOT_SUPPORTED
            ) {
                themedBitmap!!.newDrawable(this, context)
            } else {
                FastBitmapDrawable(this)
            }
        applyFlags(context, drawable, creationFlags, badgeShape)
        return drawable
    }

    protected fun applyFlags(
        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
            )
            if (badge != null) {
                drawable.badge = badge
            }
        }
    }

    /**
     * Gets Badge drawable based on current flags
     *
     * @param context Context
     * @param isThemed If Drawable is themed.
     * @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)
    }

    /**
     * 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.
     * @param badgeShape Optional Path to mask badge Drawable to a shape. Should be 100x100.
     * @return Drawable for an icon Badge.
     */
    private fun getBadgeDrawable(
        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)
        }
        if (skipUserBadge) {
            return null
        } else {
            getBadgeDrawableInfo()?.let {
                return UserBadgeDrawable(
                    context,
                    it.drawableRes,
                    it.colorRes,
                    isThemed,
                    badgeShape
                )
            }
        }
        return null
    }

    /**
     * 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(
                R.drawable.ic_private_profile_app_badge,
                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 */
        fun getExtendedInfo(
            bitmap: Bitmap?,
            color: Int,
            iconFactory: BaseIconFactory?,
            normalizationScale: Float,
        ): BitmapInfo?

        /** Called to draw the UI independent of any runtime configurations like time or theme */
        fun drawForPersistence(canvas: Canvas?)
    }

    /**
     * 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
    )

    companion object {
        const val TAG: String = "BitmapInfo"

        // BitmapInfo flags
        const val FLAG_WORK: Int = 1 shl 0
        const val FLAG_INSTANT: Int = 1 shl 1
        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

        // Drawable creation flags
        const val FLAG_THEMED: Int = 1 shl 0
        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)

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

        @JvmStatic
        fun of(bitmap: Bitmap, color: Int): BitmapInfo {
            return BitmapInfo(bitmap, color)
        }
    }
}
+5 −4
Original line number Diff line number Diff line
@@ -275,7 +275,7 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap
        ClockBitmapInfo(Bitmap icon, int color, float scale,
                AnimationInfo animInfo, Bitmap background,
                AnimationInfo themeInfo, Bitmap themeBackground) {
            super(icon, color);
            super(icon, color, /* flags */ 0, /* themedBitmap */ null);
            this.boundsOffset = Math.max(ShadowGenerator.BLUR_FACTOR, (1 - scale) / 2);
            this.animInfo = animInfo;
            this.mFlattenedBackground = background;
@@ -322,8 +322,9 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap

        @Override
        public BitmapInfo clone() {
            return copyInternalsTo(new ClockBitmapInfo(icon, color, 1 - 2 * boundsOffset, animInfo,
                    mFlattenedBackground, themeData, themeBackground));
            return copyInternalsTo(new ClockBitmapInfo(icon, color,
                    1 - 2 * boundsOffset, animInfo, mFlattenedBackground,
                    themeData, themeBackground));
        }

        @Override
+1 −1
Original line number Diff line number Diff line
@@ -52,7 +52,7 @@ open class FastBitmapDrawable(info: BitmapInfo?) : Drawable(), Callback {
    @JvmField @VisibleForTesting var isPressed: Boolean = false
    @JvmField @VisibleForTesting var isHovered: Boolean = false

    @JvmField protected var disabledAlpha: Float = 1f
    @JvmField var disabledAlpha: Float = 1f

    var isDisabled: Boolean = false
        set(value) {
+2 −2
Original line number Diff line number Diff line
@@ -42,10 +42,11 @@ import android.util.SparseArray
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import com.android.launcher3.Flags
import com.android.systemui.shared.Flags.extendibleThemeManager
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON
import com.android.launcher3.icons.BitmapInfo.Companion.LOW_RES_ICON
import com.android.launcher3.icons.GraphicsUtils
import com.android.launcher3.icons.IconProvider
import com.android.launcher3.icons.SourceHint
@@ -54,7 +55,6 @@ import com.android.launcher3.icons.cache.CacheLookupFlag.Companion.DEFAULT_LOOKU
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.FlagOp
import com.android.launcher3.util.SQLiteCacheHelper
import com.android.systemui.shared.Flags.extendibleThemeManager
import java.util.function.Supplier
import kotlin.collections.MutableMap.MutableEntry