Adding support for loading system themed icons
Bug: 183641907
Test: Manual
Change-Id: Ic921f46659545316b5a1ead0b3ec5bb758f3fc37
diff --git a/iconloaderlib/Android.bp b/iconloaderlib/Android.bp
index 515aab6..4f4ea0c 100644
--- a/iconloaderlib/Android.bp
+++ b/iconloaderlib/Android.bp
@@ -18,7 +18,7 @@
android_library {
name: "iconloader_base",
- sdk_version: "28",
+ sdk_version: "current",
min_sdk_version: "21",
static_libs: [
"androidx.core_core",
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index 844698c..7265cb9 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -59,6 +59,7 @@
private Drawable mWrapperIcon;
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
+ private Bitmap mUserBadgeBitmap;
private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
private static final float PLACEHOLDER_TEXT_SIZE = 20f;
@@ -256,10 +257,27 @@
}
int color = extractColor(bitmap);
return icon instanceof BitmapInfo.Extender
- ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this)
+ ? ((BitmapInfo.Extender) icon).getExtendedInfo(bitmap, color, this, scale[0], user)
: BitmapInfo.of(bitmap, color);
}
+ public Bitmap getUserBadgeBitmap(UserHandle user) {
+ if (mUserBadgeBitmap == null) {
+ Bitmap bitmap = Bitmap.createBitmap(
+ mIconBitmapSize, mIconBitmapSize, Bitmap.Config.ARGB_8888);
+ Drawable badgedDrawable = mPm.getUserBadgedIcon(
+ new FixedSizeBitmapDrawable(bitmap), user);
+ if (badgedDrawable instanceof BitmapDrawable) {
+ mUserBadgeBitmap = ((BitmapDrawable) badgedDrawable).getBitmap();
+ } else {
+ badgedDrawable.setBounds(0, 0, mIconBitmapSize, mIconBitmapSize);
+ mUserBadgeBitmap = BitmapRenderer.createSoftwareBitmap(
+ mIconBitmapSize, mIconBitmapSize, badgedDrawable::draw);
+ }
+ }
+ return mUserBadgeBitmap;
+ }
+
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
RectF iconBounds = new RectF();
float[] scale = new float[1];
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
index 2e20e8b..845a8ec 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
@@ -15,21 +15,36 @@
*/
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.os.Build;
+import android.os.UserHandle;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.icons.ThemedIconDrawable.ThemedBitmapInfo;
+import com.android.launcher3.icons.cache.BaseIconCache;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
public class BitmapInfo {
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";
+
+ protected static final byte TYPE_DEFAULT = 1;
+ protected static final byte TYPE_THEMED = 2;
+
public final Bitmap icon;
public final int color;
@@ -54,7 +69,27 @@
*/
@Nullable
public byte[] toByteArray() {
- return isNullOrLowRes() ? null : GraphicsUtils.flattenBitmap(icon);
+ 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;
+ }
+ }
+
+ /**
+ * Returns a new icon based on the theme of the context
+ */
+ public FastBitmapDrawable newThemedIcon(Context context) {
+ return newIcon(context);
}
/**
@@ -72,7 +107,11 @@
* Returns a BitmapInfo previously serialized using {@link #toByteArray()};
*/
@NonNull
- public static BitmapInfo fromByteArray(byte[] data, int color) {
+ public static BitmapInfo fromByteArray(byte[] data, int color, UserHandle user,
+ BaseIconCache iconCache, Context context) {
+ if (data == null) {
+ return null;
+ }
BitmapFactory.Options decodeOptions;
if (BitmapRenderer.USE_HARDWARE_BITMAP && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
decodeOptions = new BitmapFactory.Options();
@@ -80,9 +119,15 @@
} else {
decodeOptions = null;
}
- return BitmapInfo.of(
- BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions),
- color);
+ 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, user, iconCache, context);
+ } else {
+ return null;
+ }
}
public static BitmapInfo fromBitmap(@NonNull Bitmap bitmap) {
@@ -101,7 +146,8 @@
/**
* Called for creating a custom BitmapInfo
*/
- default BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) {
+ default BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
+ BaseIconFactory iconFactory, float normalizationScale, UserHandle user) {
return BitmapInfo.of(bitmap, color);
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
index 8581aa6..1a0fcfc 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
@@ -29,8 +29,11 @@
import android.os.Bundle;
import android.os.Process;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
@@ -138,15 +141,16 @@
}
@Override
- public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory) {
+ public BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
+ BaseIconFactory iconFactory, float normalizationScale, UserHandle user) {
iconFactory.disableColorExtraction();
- float [] scale = new float[1];
AdaptiveIconDrawable background = new AdaptiveIconDrawable(
getBackground().getConstantState().newDrawable(), null);
BitmapInfo bitmapInfo = iconFactory.createBadgedIconBitmap(background,
- Process.myUserHandle(), mTargetSdkVersion, false, scale);
+ Process.myUserHandle(), mTargetSdkVersion, false);
- return new ClockBitmapInfo(bitmap, color, scale[0], mAnimationInfo, bitmapInfo.icon);
+ return new ClockBitmapInfo(bitmap, color, normalizationScale,
+ mAnimationInfo, bitmapInfo.icon);
}
@Override
@@ -232,6 +236,12 @@
d.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
return d;
}
+
+ @Nullable
+ @Override
+ public byte[] toByteArray() {
+ return null;
+ }
}
private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable {
diff --git a/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java
index a3f38e0..cf510a1 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java
@@ -55,7 +55,7 @@
@Nullable private ColorFilter mColorFilter;
private boolean mIsPressed;
- private boolean mIsDisabled;
+ protected boolean mIsDisabled;
float mDisabledAlpha = 1f;
// Animator and properties for the fast bitmap drawable's scale
diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
index 5d837fd..17b0016 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
@@ -57,10 +57,7 @@
* Compresses the bitmap to a byte array for serialization.
*/
public static byte[] flattenBitmap(Bitmap bitmap) {
- // Try go guesstimate how much space the icon will take when serialized
- // to avoid unnecessary allocations/copies during the write (4 bytes per pixel).
- int size = bitmap.getWidth() * bitmap.getHeight() * 4;
- ByteArrayOutputStream out = new ByteArrayOutputStream(size);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(getExpectedBitmapSize(bitmap));
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
@@ -72,6 +69,14 @@
}
}
+ /**
+ * Try go guesstimate how much space the icon will take when serialized to avoid unnecessary
+ * allocations/copies during the write (4 bytes per pixel).
+ */
+ static int getExpectedBitmapSize(Bitmap bitmap) {
+ return bitmap.getWidth() * bitmap.getHeight() * 4;
+ }
+
public static int getArea(Region r) {
RegionIterator itr = new RegionIterator(r);
int area = 0;
diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java b/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
index dd6e63d..cc463ba 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
@@ -16,6 +16,8 @@
package com.android.launcher3.icons;
+import static android.content.res.Resources.ID_NULL;
+
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -26,6 +28,7 @@
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
@@ -33,11 +36,14 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.text.TextUtils;
+import android.util.ArrayMap;
import android.util.Log;
-import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.icons.ThemedIconDrawable.ThemeData;
import com.android.launcher3.util.SafeCloseable;
+import org.xmlpull.v1.XmlPullParser;
+
import java.util.Calendar;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -47,24 +53,36 @@
*/
public class IconProvider {
+ 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;
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";
- // Default value returned if there are problems getting resources.
- private static final int NO_ID = 0;
+ private ArrayMap<String, ThemeData> mThemedIconMap;
private final Context mContext;
private final ComponentName mCalendar;
private final ComponentName mClock;
public IconProvider(Context context) {
+ this(context, false);
+ }
+
+ public IconProvider(Context context, boolean supportsIconTheme) {
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 = new ArrayMap<>();
+ }
}
/**
@@ -115,6 +133,7 @@
private Drawable getIconWithOverrides(String packageName, UserHandle user, int iconDpi,
Supplier<Drawable> fallback) {
Drawable icon = null;
+
if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
icon = loadCalendarDrawable(iconDpi);
} else if (mClock != null
@@ -122,9 +141,11 @@
&& Process.myUserHandle().equals(user)) {
icon = loadClockDrawable(iconDpi);
}
- return icon == null ? fallback.get() : icon;
- }
+ icon = icon == null ? fallback.get() : icon;
+ ThemeData td = getThemedIconMap().get(packageName);
+ return td != null ? td.wrapDrawable(icon) : icon;
+ }
private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
final int iconRes = ai.getIconResource();
@@ -144,6 +165,44 @@
return icon;
}
+ private ArrayMap<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) {
PackageManager pm = mContext.getPackageManager();
try {
@@ -153,7 +212,7 @@
.metaData;
final Resources resources = pm.getResourcesForApplication(mCalendar.getPackageName());
final int id = getDynamicIconId(metadata, resources);
- if (id != NO_ID) {
+ if (id != ID_NULL) {
if (DEBUG) Log.d(TAG, "Got icon #" + id);
return resources.getDrawableForDensity(id, iconDpi, null /* theme */);
}
@@ -170,11 +229,6 @@
return ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
}
- protected boolean isClockIcon(ComponentKey key) {
- return mClock != null && mClock.equals(key.componentName)
- && Process.myUserHandle().equals(key.user);
- }
-
/**
* @param metadata metadata of the default activity of Calendar
* @param resources from the Calendar package
@@ -182,20 +236,20 @@
*/
private int getDynamicIconId(Bundle metadata, Resources resources) {
if (metadata == null) {
- return NO_ID;
+ return ID_NULL;
}
String key = mCalendar.getPackageName() + ICON_METADATA_KEY_PREFIX;
- final int arrayId = metadata.getInt(key, NO_ID);
- if (arrayId == NO_ID) {
- return NO_ID;
+ final int arrayId = metadata.getInt(key, ID_NULL);
+ if (arrayId == ID_NULL) {
+ return ID_NULL;
}
try {
- return resources.obtainTypedArray(arrayId).getResourceId(getDay(), NO_ID);
+ return resources.obtainTypedArray(arrayId).getResourceId(getDay(), ID_NULL);
} catch (Resources.NotFoundException e) {
if (DEBUG) {
Log.d(TAG, "package defines '" + key + "' but corresponding array not found");
}
- return NO_ID;
+ return ID_NULL;
}
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java
new file mode 100644
index 0000000..efbabc7
--- /dev/null
+++ b/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2021 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 android.content.res.Configuration.UI_MODE_NIGHT_MASK;
+import static android.content.res.Configuration.UI_MODE_NIGHT_YES;
+import static android.content.res.Resources.ID_NULL;
+
+import static com.android.launcher3.icons.GraphicsUtils.getExpectedBitmapSize;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.icons.BitmapInfo.Extender;
+import com.android.launcher3.icons.cache.BaseIconCache;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * Class to handle monochrome themed app icons
+ */
+@SuppressWarnings("NewApi")
+public class ThemedIconDrawable extends FastBitmapDrawable {
+
+ public static final String TAG = "ThemedIconDrawable";
+
+ final ThemedBitmapInfo bitmapInfo;
+ final int colorFg, colorBg;
+
+ // The foreground/monochrome icon for the app
+ private final Drawable mMonochromeIcon;
+ private final AdaptiveIconDrawable mBgWrapper;
+ private final Rect mBadgeBounds;
+
+ protected ThemedIconDrawable(ThemedConstantState constantState) {
+ super(constantState.mBitmap, constantState.mIconColor, constantState.mIsDisabled);
+ bitmapInfo = constantState.bitmapInfo;
+ colorBg = constantState.colorBg;
+ colorFg = constantState.colorFg;
+
+ mMonochromeIcon = bitmapInfo.mThemeData.loadMonochromeDrawable(colorFg);
+ mBgWrapper = new AdaptiveIconDrawable(new ColorDrawable(colorBg), null);
+ mBadgeBounds = bitmapInfo.mUserBadge == null ? null :
+ new Rect(0, 0, bitmapInfo.mUserBadge.getWidth(), bitmapInfo.mUserBadge.getHeight());
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+ mBgWrapper.setBounds(bounds);
+ mMonochromeIcon.setBounds(bounds);
+ }
+
+ @Override
+ protected void drawInternal(Canvas canvas, Rect bounds) {
+ int count = canvas.save();
+ canvas.scale(bitmapInfo.mNormalizationScale, bitmapInfo.mNormalizationScale,
+ bounds.exactCenterX(), bounds.exactCenterY());
+ mPaint.setColor(colorBg);
+ canvas.drawPath(mBgWrapper.getIconMask(), mPaint);
+ mMonochromeIcon.draw(canvas);
+ canvas.restoreToCount(count);
+ if (mBadgeBounds != null) {
+ canvas.drawBitmap(bitmapInfo.mUserBadge, mBadgeBounds, getBounds(), mPaint);
+ }
+ }
+
+ @Override
+ public ConstantState getConstantState() {
+ return new ThemedConstantState(bitmapInfo, colorBg, colorFg, mIsDisabled);
+ }
+
+ static class ThemedConstantState extends FastBitmapConstantState {
+
+ final ThemedBitmapInfo bitmapInfo;
+ final int colorFg, colorBg;
+
+ public ThemedConstantState(ThemedBitmapInfo bitmapInfo,
+ int colorBg, int colorFg, boolean isDisabled) {
+ super(bitmapInfo.icon, bitmapInfo.color, isDisabled);
+ this.bitmapInfo = bitmapInfo;
+ this.colorBg = colorBg;
+ this.colorFg = colorFg;
+ }
+
+ @Override
+ public FastBitmapDrawable newDrawable() {
+ return new ThemedIconDrawable(this);
+ }
+ }
+
+ public static class ThemedBitmapInfo extends BitmapInfo {
+
+ final ThemeData mThemeData;
+ final float mNormalizationScale;
+ final Bitmap mUserBadge;
+
+ public ThemedBitmapInfo(Bitmap icon, int color, ThemeData themeData,
+ float normalizationScale, Bitmap userBadge) {
+ super(icon, color);
+ mThemeData = themeData;
+ mNormalizationScale = normalizationScale;
+ mUserBadge = userBadge;
+ }
+
+ @Override
+ public FastBitmapDrawable newThemedIcon(Context context) {
+ int[] colors = getColors(context);
+ FastBitmapDrawable drawable = new ThemedConstantState(this, colors[0], colors[1], false)
+ .newDrawable();
+ drawable.mDisabledAlpha = GraphicsUtils.getFloat(context, R.attr.disabledIconAlpha, 1f);
+ return drawable;
+ }
+
+ @Nullable
+ public byte[] toByteArray() {
+ if (isNullOrLowRes()) {
+ return null;
+ }
+ String resName = mThemeData.mResources.getResourceName(mThemeData.mIconID);
+ ByteArrayOutputStream out = new ByteArrayOutputStream(
+ getExpectedBitmapSize(icon) + 3 + resName.length());
+ try {
+ DataOutputStream dos = new DataOutputStream(out);
+ dos.writeByte(TYPE_THEMED);
+ dos.writeFloat(mNormalizationScale);
+ dos.writeUTF(resName);
+ icon.compress(Bitmap.CompressFormat.PNG, 100, dos);
+
+ dos.flush();
+ dos.close();
+ return out.toByteArray();
+ } catch (IOException e) {
+ Log.w(TAG, "Could not write bitmap");
+ return null;
+ }
+ }
+
+ static ThemedBitmapInfo decode(byte[] data, int color,
+ BitmapFactory.Options decodeOptions, UserHandle user, BaseIconCache iconCache,
+ Context context) {
+ try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(data))) {
+ dis.readByte(); // type
+ float normalizationScale = dis.readFloat();
+
+ String resName = dis.readUTF();
+ int resId = context.getResources()
+ .getIdentifier(resName, "drawable", context.getPackageName());
+ if (resId == ID_NULL) {
+ return null;
+ }
+
+ Bitmap userBadgeBitmap = null;
+ if (!Process.myUserHandle().equals(user)) {
+ try (BaseIconFactory iconFactory = iconCache.getIconFactory()) {
+ userBadgeBitmap = iconFactory.getUserBadgeBitmap(user);
+ }
+ }
+
+ ThemeData themeData = new ThemeData(context.getResources(), resId);
+ Bitmap icon = BitmapFactory.decodeStream(dis, null, decodeOptions);
+ return new ThemedBitmapInfo(icon, color, themeData, normalizationScale,
+ userBadgeBitmap);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+ }
+
+ public static class ThemeData {
+
+ final Resources mResources;
+ final int mIconID;
+
+ public ThemeData(Resources resources, int iconID) {
+ mResources = resources;
+ mIconID = iconID;
+ }
+
+ Drawable loadMonochromeDrawable(int accentColor) {
+ Drawable d = mResources.getDrawable(mIconID).mutate();
+ d.setTint(accentColor);
+ d = new InsetDrawable(d, .15f);
+ return d;
+ }
+
+ public Drawable wrapDrawable(Drawable original) {
+ return (original instanceof AdaptiveIconDrawable)
+ ? new ThemedAdaptiveIcon((AdaptiveIconDrawable) original, this)
+ : original;
+ }
+ }
+
+ public static class ThemedAdaptiveIcon extends AdaptiveIconDrawable implements Extender {
+
+ private final ThemeData mThemeData;
+
+ public ThemedAdaptiveIcon(AdaptiveIconDrawable parent, ThemeData themeData) {
+ super(parent.getBackground(), parent.getForeground());
+ mThemeData = themeData;
+ }
+
+ @Override
+ public BitmapInfo getExtendedInfo(Bitmap bitmap, int color, BaseIconFactory iconFactory,
+ float normalizationScale, UserHandle user) {
+ Bitmap userBadge = Process.myUserHandle().equals(user)
+ ? null : iconFactory.getUserBadgeBitmap(user);
+ return new ThemedBitmapInfo(bitmap, color, mThemeData, normalizationScale, userBadge);
+ }
+
+ @Override
+ public void drawForPersistence(Canvas canvas) {
+ draw(canvas);
+ }
+
+ /**
+ * Returns a new icon with theme applied
+ */
+ public Drawable getThemedDrawable(Context context) {
+ int[] colors = getColors(context);
+ Drawable bg = new ColorDrawable(colors[0]);
+ float inset = getExtraInsetFraction() / (1 + 2 * getExtraInsetFraction());
+ Drawable fg = new InsetDrawable(mThemeData.loadMonochromeDrawable(colors[1]), inset);
+ return new AdaptiveIconDrawable(bg, fg);
+ }
+ }
+
+ private static int[] getColors(Context context) {
+ Resources res = context.getResources();
+ int[] colors = new int[2];
+ if ((res.getConfiguration().uiMode & UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES) {
+ colors[0] = res.getColor(android.R.color.system_neutral1_800);
+ colors[1] = res.getColor(android.R.color.system_neutral2_200);
+ } else {
+ colors[0] = res.getColor(android.R.color.system_accent1_100);
+ colors[1] = res.getColor(android.R.color.system_neutral2_700);
+ }
+ return colors;
+ }
+}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index 863e8c8..89dfa18 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -137,7 +137,7 @@
/**
* Opens and returns an icon factory. The factory is recycled by the caller.
*/
- protected abstract BaseIconFactory getIconFactory();
+ public abstract BaseIconFactory getIconFactory();
public void updateIconParams(int iconDpi, int iconPixelSize) {
mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize));
@@ -479,14 +479,14 @@
}
if (!lowRes) {
- byte[] data = c.getBlob(2);
try {
- entry.bitmap = BitmapInfo.fromByteArray(data, entry.bitmap.color);
+ entry.bitmap = BitmapInfo.fromByteArray(
+ c.getBlob(2), entry.bitmap.color, cacheKey.user, this, mContext);
} catch (Exception e) {
return false;
}
}
- return true;
+ return entry.bitmap != null;
}
} catch (SQLiteException e) {
Log.d(TAG, "Error reading icon cache", e);
diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
index 1337975..cc4ad7b 100644
--- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
+++ b/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
@@ -88,7 +88,7 @@
}
@Override
- protected BaseIconFactory getIconFactory() {
+ public BaseIconFactory getIconFactory() {
return IconFactory.obtain(mContext);
}