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

Commit b0a4aa8c authored by Sunny Goyal's avatar Sunny Goyal
Browse files

Adding Icon theming API support in Launcher

Bug: 200082620
Test: Verified on device
Change-Id: Ie0844312a3361da15ab3601cf6261da597d4d731
parent 41e240cb
Loading
Loading
Loading
Loading
+59 −12
Original line number Diff line number Diff line
@@ -2,15 +2,18 @@ package com.android.launcher3.icons;

import static android.graphics.Paint.DITHER_FLAG;
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;

import static com.android.launcher3.icons.BitmapInfo.FLAG_INSTANT;
import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@@ -50,10 +53,15 @@ public class BaseIconFactory implements AutoCloseable {
    protected final int mFillResIconDpi;
    protected final int mIconBitmapSize;

    protected boolean mMonoIconEnabled;

    private IconNormalizer mNormalizer;
    private ShadowGenerator mShadowGenerator;
    private final boolean mShapeDetection;

    // Shadow bitmap used as background for theme icons
    private Bitmap mWhiteShadowLayer;

    private Drawable mWrapperIcon;
    private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;

@@ -151,7 +159,7 @@ public class BaseIconFactory implements AutoCloseable {
     */
    public BitmapInfo createShapedIconBitmap(Bitmap icon, IconOptions options) {
        Drawable d = new FixedSizeBitmapDrawable(icon);
        float inset = AdaptiveIconDrawable.getExtraInsetFraction();
        float inset = getExtraInsetFraction();
        inset = inset / (1 + 2 * inset);
        d = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK),
                new InsetDrawable(d, inset, inset, inset, inset));
@@ -169,6 +177,7 @@ public class BaseIconFactory implements AutoCloseable {
     * @param icon                      source of the icon
     * @return a bitmap suitable for disaplaying as an icon at various system UIs.
     */
    @TargetApi(Build.VERSION_CODES.TIRAMISU)
    public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon,
            @Nullable IconOptions options) {
        boolean shrinkNonAdaptiveIcons = options == null || options.mShrinkNonAdaptiveIcons;
@@ -182,9 +191,21 @@ public class BaseIconFactory implements AutoCloseable {
        }

        int color = extractColor(bitmap);
        BitmapInfo info = icon instanceof BitmapInfo.Extender
                ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this, scale[0])
                : BitmapInfo.of(bitmap, color);
        BitmapInfo info = BitmapInfo.of(bitmap, color);

        if (icon instanceof BitmapInfo.Extender) {
            info = ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this, scale[0]);
        } else if (mMonoIconEnabled && IconProvider.ATLEAST_T
                && icon instanceof AdaptiveIconDrawable) {
            Drawable mono = ((AdaptiveIconDrawable) icon).getMonochrome();
            if (mono != null) {
                // Convert mono drawable to bitmap
                Drawable paddedMono = new InsetDrawable(mono, -getExtraInsetFraction());
                info.setMonoIcon(
                        createIconBitmap(paddedMono, scale[0], mIconBitmapSize, Config.ALPHA_8),
                        this);
            }
        }
        if (options != null) {
            if (options.mIsInstantApp) {
                info.flags |= FLAG_INSTANT;
@@ -194,6 +215,25 @@ public class BaseIconFactory implements AutoCloseable {
        return info;
    }

    /** package private */
    Bitmap getWhiteShadowLayer() {
        if (mWhiteShadowLayer == null) {
            mWhiteShadowLayer = createScaledBitmapWithShadow(
                    new AdaptiveIconDrawable(new ColorDrawable(Color.WHITE), null));
        }
        return mWhiteShadowLayer;
    }

    /** package private */
    Bitmap createScaledBitmapWithShadow(Drawable d) {
        float scale = getNormalizer().getScale(d, null, null, null);
        Bitmap bitmap = createIconBitmap(d, scale);
        mCanvas.setBitmap(bitmap);
        getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
        mCanvas.setBitmap(null);
        return bitmap;
    }

    public Bitmap createScaledBitmapWithoutShadow(Drawable icon) {
        RectF iconBounds = new RectF();
        float[] scale = new float[1];
@@ -223,7 +263,7 @@ public class BaseIconFactory implements AutoCloseable {
        }
        float scale = 1f;

        if (shrinkNonAdaptiveIcons) {
        if (shrinkNonAdaptiveIcons && !(icon instanceof AdaptiveIconDrawable)) {
            if (mWrapperIcon == null) {
                mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
                        .mutate();
@@ -232,13 +272,12 @@ public class BaseIconFactory implements AutoCloseable {
            dr.setBounds(0, 0, 1, 1);
            boolean[] outShape = new boolean[1];
            scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
            if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
            if (!outShape[0]) {
                FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
                fsd.setDrawable(icon);
                fsd.setScale(scale);
                icon = dr;
                scale = getNormalizer().getScale(icon, outIconBounds, null, null);

                ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
            }
        } else {
@@ -258,7 +297,12 @@ public class BaseIconFactory implements AutoCloseable {
     * @param scale the scale to apply before drawing {@param icon} on the canvas
     */
    public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
        Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
        return createIconBitmap(icon, scale, size, Bitmap.Config.ARGB_8888);
    }

    private Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size,
            Bitmap.Config config) {
        Bitmap bitmap = Bitmap.createBitmap(size, size, config);
        if (icon == null) {
            return bitmap;
        }
@@ -268,12 +312,17 @@ public class BaseIconFactory implements AutoCloseable {
        if (icon instanceof AdaptiveIconDrawable) {
            int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
                    Math.round(size * (1 - scale) / 2 ));
            icon.setBounds(offset, offset, size - offset, size - offset);
            // b/211896569: AdaptiveIconDrawable do not work properly for non top-left bounds
            icon.setBounds(0, 0, size - offset - offset, size - offset - offset);
            int count = mCanvas.save();
            mCanvas.translate(offset, offset);

            if (icon instanceof BitmapInfo.Extender) {
                ((Extender) icon).drawForPersistence(mCanvas);
            } else {
                icon.draw(mCanvas);
            }
            mCanvas.restoreToCount(count);
        } else {
            if (icon instanceof BitmapDrawable) {
                BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
@@ -321,9 +370,7 @@ public class BaseIconFactory implements AutoCloseable {

    public static Drawable getFullResDefaultActivityIcon(int iconDpi) {
        return Resources.getSystem().getDrawableForDensity(
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
                        ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
                iconDpi);
                android.R.drawable.sym_def_app_icon, iconDpi);
    }

    private int extractColor(Bitmap bitmap) {
+33 −69
Original line number Diff line number Diff line
@@ -15,28 +15,18 @@
 */
package com.android.launcher3.icons;

import static com.android.launcher3.icons.GraphicsUtils.getExpectedBitmapSize;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.util.Log;

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

import com.android.launcher3.icons.ThemedIconDrawable.ThemedBitmapInfo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class BitmapInfo {

    static final int FLAG_WORK = 1 << 0;
@@ -60,12 +50,13 @@ public class BitmapInfo {

    public static final String TAG = "BitmapInfo";

    protected static final byte TYPE_DEFAULT = 1;
    protected static final byte TYPE_THEMED = 2;

    public final Bitmap icon;
    public final int color;

    @Nullable
    protected Bitmap mMono;
    protected Bitmap mWhiteShadowLayer;

    public @BitmapInfoFlags int flags;
    private BitmapInfo badgeInfo;

@@ -75,17 +66,26 @@ public class BitmapInfo {
    }

    public BitmapInfo withBadgeInfo(BitmapInfo badgeInfo) {
        BitmapInfo result = new BitmapInfo(icon, color);
        result.flags = flags;
        BitmapInfo result = clone();
        result.badgeInfo = badgeInfo;
        return result;
    }

    protected BitmapInfo copyInternalsTo(BitmapInfo target) {
        target.mMono = mMono;
        target.mWhiteShadowLayer = mWhiteShadowLayer;
        target.flags = flags;
        return target;
    }

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

    public void setMonoIcon(Bitmap mono, BaseIconFactory iconFactory) {
        mMono = mono;
        mWhiteShadowLayer = iconFactory.getWhiteShadowLayer();
    }

    /**
@@ -112,24 +112,14 @@ public class BitmapInfo {
    }

    /**
     * Returns a serialized version of BitmapInfo
     * BitmapInfo can be stored on disk or other persistent storage
     */
    @Nullable
    public byte[] toByteArray() {
        if (isNullOrLowRes()) {
            return null;
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream(getExpectedBitmapSize(icon) + 1);
        try {
            out.write(TYPE_DEFAULT);
            icon.compress(Bitmap.CompressFormat.PNG, 100, out);
            out.flush();
            out.close();
            return out.toByteArray();
        } catch (IOException e) {
            Log.w(TAG, "Could not write bitmap");
            return null;
    public boolean canPersist() {
        return !isNullOrLowRes();
    }

    public Bitmap getMono() {
        return mMono;
    }

    /**
@@ -143,9 +133,14 @@ public class BitmapInfo {
     * Creates a drawable for the provided BitmapInfo
     */
    public FastBitmapDrawable newIcon(Context context, @DrawableCreationFlags int creationFlags) {
        FastBitmapDrawable drawable = isLowRes()
                ? new PlaceHolderIconDrawable(this, context)
                : new FastBitmapDrawable(this);
        FastBitmapDrawable drawable;
        if (isLowRes()) {
            drawable = new PlaceHolderIconDrawable(this, context);
        } else  if ((creationFlags & FLAG_THEMED) != 0 && mMono != null) {
            drawable = ThemedIconDrawable.newDrawable(this, context);
        } else {
            drawable = new FastBitmapDrawable(this);
        }
        applyFlags(context, drawable, creationFlags);
        return drawable;
    }
@@ -164,32 +159,6 @@ public class BitmapInfo {
        }
    }

    /**
     * Returns a BitmapInfo previously serialized using {@link #toByteArray()};
     */
    @NonNull
    public static BitmapInfo fromByteArray(byte[] data, int color, Context context) {
        if (data == null) {
            throw new NullPointerException();
        }
        BitmapFactory.Options decodeOptions;
        if (BitmapRenderer.USE_HARDWARE_BITMAP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            decodeOptions = new BitmapFactory.Options();
            decodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
        } else {
            decodeOptions = null;
        }
        if (data[0] == TYPE_DEFAULT) {
            return BitmapInfo.of(
                    BitmapFactory.decodeByteArray(data, 1, data.length - 1, decodeOptions),
                    color);
        } else if (data[0] == TYPE_THEMED) {
            return ThemedBitmapInfo.decode(data, color, decodeOptions, context);
        } else {
            throw new IllegalArgumentException("Unknown type " + data[0]);
        }
    }

    public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) {
        return of(bitmap, 0);
    }
@@ -213,10 +182,5 @@ public class BitmapInfo {
         * Called to draw the UI independent of any runtime configurations like time or theme
         */
        void drawForPersistence(Canvas canvas);

        /**
         * Returns a new icon with theme applied
         */
        Drawable getThemedDrawable(Context context);
    }
}
+153 −108

File changed.

Preview size limit exceeded, changes collapsed.

+68 −88
Original line number Diff line number Diff line
@@ -20,7 +20,9 @@ import static android.content.Intent.ACTION_DATE_CHANGED;
import static android.content.Intent.ACTION_TIMEZONE_CHANGED;
import static android.content.Intent.ACTION_TIME_CHANGED;
import static android.content.res.Resources.ID_NULL;
import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;

import android.annotation.TargetApi;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -31,8 +33,11 @@ import android.content.pm.LauncherActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.content.res.TypedArray;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.InsetDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.PatternMatcher;
@@ -40,17 +45,14 @@ import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;

import com.android.launcher3.icons.ThemedIconDrawable.ThemeData;
import com.android.launcher3.util.SafeCloseable;
import androidx.annotation.Nullable;
import androidx.core.os.BuildCompat;

import org.xmlpull.v1.XmlPullParser;
import com.android.launcher3.util.SafeCloseable;

import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.function.Supplier;

/**
@@ -62,30 +64,18 @@ public class IconProvider {
    private static final int CONFIG_ICON_MASK_RES_ID = Resources.getSystem().getIdentifier(
            "config_icon_mask", "string", "android");

    private static final String TAG_ICON = "icon";
    private static final String ATTR_PACKAGE = "package";
    private static final String ATTR_DRAWABLE = "drawable";

    private static final String TAG = "IconProvider";
    private static final boolean DEBUG = false;
    public static final boolean ATLEAST_T = BuildCompat.isAtLeastT();

    private static final String ICON_METADATA_KEY_PREFIX = ".dynamic_icons";

    private static final String SYSTEM_STATE_SEPARATOR = " ";
    private static final String THEMED_ICON_MAP_FILE = "grayscale_icon_map";

    private static final Map<String, ThemeData> DISABLED_MAP = Collections.emptyMap();

    private Map<String, ThemeData> mThemedIconMap;

    private final Context mContext;
    protected final Context mContext;
    private final ComponentName mCalendar;
    private final ComponentName mClock;

    static final int ICON_TYPE_DEFAULT = 0;
    static final int ICON_TYPE_CALENDAR = 1;
    static final int ICON_TYPE_CLOCK = 2;

    public IconProvider(Context context) {
        this(context, false);
    }
@@ -94,17 +84,6 @@ public class IconProvider {
        mContext = context;
        mCalendar = parseComponentOrNull(context, R.string.calendar_component_name);
        mClock = parseComponentOrNull(context, R.string.clock_component_name);
        if (!supportsIconTheme) {
            // Initialize an empty map if theming is not supported
            mThemedIconMap = DISABLED_MAP;
        }
    }

    /**
     * Enables or disables icon theme support
     */
    public void setIconThemeSupported(boolean isSupported) {
        mThemedIconMap = isSupported ? null : DISABLED_MAP;
    }

    /**
@@ -142,25 +121,32 @@ public class IconProvider {
                () -> loadActivityInfoIcon(info, iconDpi));
    }

    @TargetApi(Build.VERSION_CODES.TIRAMISU)
    private Drawable getIconWithOverrides(String packageName, int iconDpi,
            Supplier<Drawable> fallback) {
        Drawable icon = null;
        ThemeData td = getThemeDataForPackage(packageName);

        int iconType = ICON_TYPE_DEFAULT;
        Drawable icon = null;
        if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
            icon = loadCalendarDrawable(iconDpi);
            iconType = ICON_TYPE_CALENDAR;
            icon = loadCalendarDrawable(iconDpi, td);
        } else if (mClock != null && mClock.getPackageName().equals(packageName)) {
            icon = loadClockDrawable(iconDpi);
            iconType = ICON_TYPE_CLOCK;
            icon = ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi, td);
        }
        if (icon == null) {
            icon = fallback.get();
            iconType = ICON_TYPE_DEFAULT;
            if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && td != null) {
                AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon;
                if  (aid.getMonochrome() == null) {
                    icon = new AdaptiveIconDrawable(aid.getBackground(),
                            aid.getForeground(), td.loadPaddedDrawable());
                }
            }
        }
        return icon;
    }

        ThemeData td = getThemedIconMap().get(packageName);
        return td != null ? td.wrapDrawable(icon, iconType) : icon;
    protected ThemeData getThemeDataForPackage(String packageName) {
        return null;
    }

    private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
@@ -181,45 +167,8 @@ public class IconProvider {
        return icon;
    }

    private Map<String, ThemeData> getThemedIconMap() {
        if (mThemedIconMap != null) {
            return mThemedIconMap;
        }
        ArrayMap<String, ThemeData> map = new ArrayMap<>();
        try {
            Resources res = mContext.getResources();
            int resID = res.getIdentifier(THEMED_ICON_MAP_FILE, "xml", mContext.getPackageName());
            if (resID != 0) {
                XmlResourceParser parser = res.getXml(resID);
                final int depth = parser.getDepth();

                int type;

                while ((type = parser.next()) != XmlPullParser.START_TAG
                        && type != XmlPullParser.END_DOCUMENT);

                while (((type = parser.next()) != XmlPullParser.END_TAG ||
                        parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
                    if (type != XmlPullParser.START_TAG) {
                        continue;
                    }
                    if (TAG_ICON.equals(parser.getName())) {
                        String pkg = parser.getAttributeValue(null, ATTR_PACKAGE);
                        int iconId = parser.getAttributeResourceValue(null, ATTR_DRAWABLE, 0);
                        if (iconId != 0 && !TextUtils.isEmpty(pkg)) {
                            map.put(pkg, new ThemeData(res, iconId));
                        }
                    }
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "Unable to parse icon map", e);
        }
        mThemedIconMap = map;
        return mThemedIconMap;
    }

    private Drawable loadCalendarDrawable(int iconDpi) {
    @TargetApi(Build.VERSION_CODES.TIRAMISU)
    private Drawable loadCalendarDrawable(int iconDpi, @Nullable ThemeData td) {
        PackageManager pm = mContext.getPackageManager();
        try {
            final Bundle metadata = pm.getActivityInfo(
@@ -230,7 +179,21 @@ public class IconProvider {
            final int id = getDynamicIconId(metadata, resources);
            if (id != ID_NULL) {
                if (DEBUG) Log.d(TAG, "Got icon #" + id);
                return resources.getDrawableForDensity(id, iconDpi, null /* theme */);
                Drawable drawable = resources.getDrawableForDensity(id, iconDpi, null /* theme */);
                if (ATLEAST_T && drawable instanceof AdaptiveIconDrawable && td != null) {
                    AdaptiveIconDrawable aid = (AdaptiveIconDrawable) drawable;
                    if  (aid.getMonochrome() != null) {
                        return drawable;
                    }
                    if ("array".equals(td.mResources.getResourceTypeName(td.mResID))) {
                        TypedArray ta = td.mResources.obtainTypedArray(td.mResID);
                        int monoId = ta.getResourceId(IconProvider.getDay(), ID_NULL);
                        ta.recycle();
                        return monoId == ID_NULL ? drawable
                                : new AdaptiveIconDrawable(aid.getBackground(), aid.getForeground(),
                                        new ThemeData(td.mResources, monoId).loadPaddedDrawable());
                    }
                }
            }
        } catch (PackageManager.NameNotFoundException e) {
            if (DEBUG) {
@@ -241,10 +204,6 @@ public class IconProvider {
        return null;
    }

    private Drawable loadClockDrawable(int iconDpi) {
        return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
    }

    /**
     * @param metadata metadata of the default activity of Calendar
     * @param resources from the Calendar package
@@ -272,7 +231,7 @@ public class IconProvider {
    /**
     * @return Today's day of the month, zero-indexed.
     */
    static int getDay() {
    private static int getDay() {
        return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
    }

@@ -286,8 +245,7 @@ public class IconProvider {
     */
    public String getSystemIconState() {
        return (CONFIG_ICON_MASK_RES_ID == ID_NULL
                ? "" : mContext.getResources().getString(CONFIG_ICON_MASK_RES_ID))
                + (mThemedIconMap == DISABLED_MAP ? ",no-theme" : ",with-theme");
                ? "" : mContext.getResources().getString(CONFIG_ICON_MASK_RES_ID));
    }

    /**
@@ -297,6 +255,28 @@ public class IconProvider {
        return new IconChangeReceiver(listener, handler);
    }

    public static class ThemeData {

        final Resources mResources;
        final int mResID;

        public ThemeData(Resources resources, int resID) {
            mResources = resources;
            mResID = resID;
        }

        Drawable loadPaddedDrawable() {
            if (!"drawable".equals(mResources.getResourceTypeName(mResID))) {
                return null;
            }
            Drawable d = mResources.getDrawable(mResID).mutate();
            d = new InsetDrawable(d, .2f);
            float inset = getExtraInsetFraction() / (1 + 2 * getExtraInsetFraction());
            Drawable fg = new InsetDrawable(d, inset);
            return fg;
        }
    }

    private class IconChangeReceiver extends BroadcastReceiver implements SafeCloseable {

        private final IconChangeListener mCallback;
+20 −182

File changed.

Preview size limit exceeded, changes collapsed.

Loading