Adding support for themed calendar and clock icons
Bug: 183641907
Test: Manual
Change-Id: I6452a4b4096ef45e03768afbef3e1a3a4b58299b
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
index 845a8ec..06b39b8 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
@@ -22,6 +22,7 @@
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.UserHandle;
import android.util.Log;
@@ -146,14 +147,17 @@
/**
* Called for creating a custom BitmapInfo
*/
- default BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
- BaseIconFactory iconFactory, float normalizationScale, UserHandle user) {
- return BitmapInfo.of(bitmap, color);
- }
+ BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
+ BaseIconFactory iconFactory, float normalizationScale, UserHandle user);
/**
* Called to draw the UI independent of any runtime configurations like time or theme
*/
- default void drawForPersistence(Canvas canvas) { }
+ void drawForPersistence(Canvas canvas);
+
+ /**
+ * Returns a new icon with theme applied
+ */
+ Drawable getThemedDrawable(Context context);
}
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
index fe00428..ad9267a 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
@@ -15,14 +15,25 @@
*/
package com.android.launcher3.icons;
+import static com.android.launcher3.icons.ThemedIconDrawable.getColors;
+
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
@@ -31,11 +42,15 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
+import android.util.TypedValue;
import androidx.annotation.Nullable;
+import com.android.launcher3.icons.ThemedIconDrawable.ThemeData;
+
import java.util.Calendar;
import java.util.concurrent.TimeUnit;
+import java.util.function.IntFunction;
/**
* Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic
@@ -74,6 +89,7 @@
private final AnimationInfo mAnimationInfo = new AnimationInfo();
private int mTargetSdkVersion;
+ protected ThemeData mThemeData;
public ClockDrawableWrapper(AdaptiveIconDrawable base) {
super(base.getBackground(), base.getForeground());
@@ -88,60 +104,94 @@
PackageManager pm = context.getPackageManager();
ApplicationInfo appInfo = pm.getApplicationInfo(pkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
- final Bundle metadata = appInfo.metaData;
- if (metadata == null) {
- return null;
- }
- int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0);
- if (drawableId == 0) {
- return null;
- }
-
- Drawable drawable = pm.getResourcesForApplication(appInfo).getDrawableForDensity(
- drawableId, iconDpi).mutate();
- if (!(drawable instanceof AdaptiveIconDrawable)) {
- return null;
- }
-
- ClockDrawableWrapper wrapper =
- new ClockDrawableWrapper((AdaptiveIconDrawable) drawable);
- wrapper.mTargetSdkVersion = appInfo.targetSdkVersion;
- AnimationInfo info = wrapper.mAnimationInfo;
-
- info.baseDrawableState = drawable.getConstantState();
-
- info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE);
- info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE);
- info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE);
-
- info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0);
- info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0);
- info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0);
-
- LayerDrawable foreground = (LayerDrawable) wrapper.getForeground();
- int layerCount = foreground.getNumberOfLayers();
- if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) {
- info.hourLayerIndex = INVALID_VALUE;
- }
- if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) {
- info.minuteLayerIndex = INVALID_VALUE;
- }
- if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) {
- info.secondLayerIndex = INVALID_VALUE;
- } else if (DISABLE_SECONDS) {
- foreground.setDrawable(info.secondLayerIndex, null);
- info.secondLayerIndex = INVALID_VALUE;
- }
- info.applyTime(Calendar.getInstance(), foreground);
- return wrapper;
+ Resources res = pm.getResourcesForApplication(appInfo);
+ return forExtras(appInfo, appInfo.metaData,
+ resId -> res.getDrawableForDensity(resId, iconDpi));
} catch (Exception e) {
Log.d(TAG, "Unable to load clock drawable info", e);
}
return null;
}
+ private static ClockDrawableWrapper fromThemeData(Context context, ThemeData themeData) {
+ try {
+ TypedArray ta = themeData.mResources.obtainTypedArray(themeData.mResID);
+ int count = ta.length();
+ Bundle extras = new Bundle();
+ for (int i = 0; i < count; i += 2) {
+ TypedValue v = ta.peekValue(i + 1);
+ extras.putInt(ta.getString(i), v.type >= TypedValue.TYPE_FIRST_INT
+ && v.type <= TypedValue.TYPE_LAST_INT
+ ? v.data : v.resourceId);
+ }
+ ta.recycle();
+ ClockDrawableWrapper drawable = ClockDrawableWrapper.forExtras(
+ context.getApplicationInfo(), extras, resId -> {
+ int[] colors = getColors(context);
+ Drawable bg = new ColorDrawable(colors[0]);
+ Drawable fg = themeData.mResources.getDrawable(resId).mutate();
+ fg.setTint(colors[1]);
+ return new AdaptiveIconDrawable(bg, fg);
+ });
+ if (drawable != null) {
+ return drawable;
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error loading themed clock", e);
+ }
+ return null;
+ }
+
+ private static ClockDrawableWrapper forExtras(ApplicationInfo appInfo, Bundle metadata,
+ IntFunction<Drawable> drawableProvider) {
+ if (metadata == null) {
+ return null;
+ }
+ int drawableId = metadata.getInt(ROUND_ICON_METADATA_KEY, 0);
+ if (drawableId == 0) {
+ return null;
+ }
+
+ Drawable drawable = drawableProvider.apply(drawableId).mutate();
+ if (!(drawable instanceof AdaptiveIconDrawable)) {
+ return null;
+ }
+
+ ClockDrawableWrapper wrapper =
+ new ClockDrawableWrapper((AdaptiveIconDrawable) drawable);
+ wrapper.mTargetSdkVersion = appInfo.targetSdkVersion;
+ AnimationInfo info = wrapper.mAnimationInfo;
+
+ info.baseDrawableState = drawable.getConstantState();
+
+ info.hourLayerIndex = metadata.getInt(HOUR_INDEX_METADATA_KEY, INVALID_VALUE);
+ info.minuteLayerIndex = metadata.getInt(MINUTE_INDEX_METADATA_KEY, INVALID_VALUE);
+ info.secondLayerIndex = metadata.getInt(SECOND_INDEX_METADATA_KEY, INVALID_VALUE);
+
+ info.defaultHour = metadata.getInt(DEFAULT_HOUR_METADATA_KEY, 0);
+ info.defaultMinute = metadata.getInt(DEFAULT_MINUTE_METADATA_KEY, 0);
+ info.defaultSecond = metadata.getInt(DEFAULT_SECOND_METADATA_KEY, 0);
+
+ LayerDrawable foreground = (LayerDrawable) wrapper.getForeground();
+ int layerCount = foreground.getNumberOfLayers();
+ if (info.hourLayerIndex < 0 || info.hourLayerIndex >= layerCount) {
+ info.hourLayerIndex = INVALID_VALUE;
+ }
+ if (info.minuteLayerIndex < 0 || info.minuteLayerIndex >= layerCount) {
+ info.minuteLayerIndex = INVALID_VALUE;
+ }
+ if (info.secondLayerIndex < 0 || info.secondLayerIndex >= layerCount) {
+ info.secondLayerIndex = INVALID_VALUE;
+ } else if (DISABLE_SECONDS) {
+ foreground.setDrawable(info.secondLayerIndex, null);
+ info.secondLayerIndex = INVALID_VALUE;
+ }
+ info.applyTime(Calendar.getInstance(), foreground);
+ return wrapper;
+ }
+
@Override
- public BitmapInfo getExtendedInfo(Bitmap bitmap, int color,
+ public ClockBitmapInfo getExtendedInfo(Bitmap bitmap, int color,
BaseIconFactory iconFactory, float normalizationScale, UserHandle user) {
iconFactory.disableColorExtraction();
AdaptiveIconDrawable background = new AdaptiveIconDrawable(
@@ -150,7 +200,7 @@
Process.myUserHandle(), mTargetSdkVersion, false);
return new ClockBitmapInfo(bitmap, color, normalizationScale,
- mAnimationInfo, bitmapInfo.icon);
+ mAnimationInfo, bitmapInfo.icon, mThemeData);
}
@Override
@@ -163,6 +213,15 @@
mAnimationInfo.applyTime(Calendar.getInstance(), (LayerDrawable) getForeground());
}
+ @Override
+ public Drawable getThemedDrawable(Context context) {
+ if (mThemeData != null) {
+ ClockDrawableWrapper drawable = fromThemeData(context, mThemeData);
+ return drawable == null ? this : drawable;
+ }
+ return this;
+ }
+
private void resetLevel(LayerDrawable drawable, int index) {
if (index != INVALID_VALUE) {
drawable.getDrawable(index).setLevel(0);
@@ -214,20 +273,47 @@
}
}
- private static class ClockBitmapInfo extends BitmapInfo {
+ static class ClockBitmapInfo extends BitmapInfo {
public final float scale;
public final int offset;
public final AnimationInfo animInfo;
public final Bitmap mFlattenedBackground;
+ public final ThemeData themeData;
+
ClockBitmapInfo(Bitmap icon, int color, float scale, AnimationInfo animInfo,
- Bitmap background) {
+ Bitmap background, ThemeData themeData) {
super(icon, color);
this.scale = scale;
this.animInfo = animInfo;
this.offset = (int) Math.ceil(ShadowGenerator.BLUR_FACTOR * icon.getWidth());
this.mFlattenedBackground = background;
+ this.themeData = themeData;
+ }
+
+ @Override
+ public FastBitmapDrawable newThemedIcon(Context context) {
+ if (themeData != null) {
+ ClockDrawableWrapper wrapper = fromThemeData(context, themeData);
+ if (wrapper != null) {
+ ColorFilter bgFilter = new PorterDuffColorFilter(
+ getColors(context)[0], Mode.SRC_ATOP);
+ ClockBitmapInfo bitmapInfo = new ClockBitmapInfo(icon, color, scale,
+ wrapper.mAnimationInfo, mFlattenedBackground, themeData) {
+
+ @Override
+ void drawBackground(Canvas canvas, Rect bounds, Paint paint) {
+ ColorFilter oldFilter = paint.getColorFilter();
+ paint.setColorFilter(bgFilter);
+ super.drawBackground(canvas, bounds, paint);
+ paint.setColorFilter(oldFilter);
+ }
+ };
+ return bitmapInfo.newIcon(context);
+ }
+ }
+ return super.newThemedIcon(context);
}
@Override
@@ -242,6 +328,11 @@
public byte[] toByteArray() {
return null;
}
+
+ void drawBackground(Canvas canvas, Rect bounds, Paint paint) {
+ // draw the background that is already flattened to a bitmap
+ canvas.drawBitmap(mFlattenedBackground, null, bounds, paint);
+ }
}
private static class ClockIconDrawable extends FastBitmapDrawable implements Runnable {
@@ -274,8 +365,7 @@
super.drawInternal(canvas, bounds);
return;
}
- // draw the background that is already flattened to a bitmap
- canvas.drawBitmap(mInfo.mFlattenedBackground, null, bounds, mPaint);
+ mInfo.drawBackground(canvas, bounds, mPaint);
// prepare and draw the foreground
mInfo.animInfo.applyTime(mTime, mForeground);
diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java b/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
index 6c96967..449c0da 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
@@ -82,6 +82,10 @@
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);
}
@@ -143,17 +147,23 @@
Supplier<Drawable> fallback) {
Drawable icon = null;
+ int iconType = ICON_TYPE_DEFAULT;
if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
icon = loadCalendarDrawable(iconDpi);
+ iconType = ICON_TYPE_CALENDAR;
} else if (mClock != null
&& mClock.getPackageName().equals(packageName)
&& Process.myUserHandle().equals(user)) {
icon = loadClockDrawable(iconDpi);
+ iconType = ICON_TYPE_CLOCK;
}
- icon = icon == null ? fallback.get() : icon;
+ if (icon == null) {
+ icon = fallback.get();
+ iconType = ICON_TYPE_DEFAULT;
+ }
ThemeData td = getThemedIconMap().get(packageName);
- return td != null ? td.wrapDrawable(icon) : icon;
+ return td != null ? td.wrapDrawable(icon, iconType) : icon;
}
private Drawable loadActivityInfoIcon(ActivityInfo ai, int density) {
@@ -265,7 +275,7 @@
/**
* @return Today's day of the month, zero-indexed.
*/
- private int getDay() {
+ static int getDay() {
return Calendar.getInstance().get(Calendar.DAY_OF_MONTH) - 1;
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java
index 7425c68..da62712 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.java
@@ -20,9 +20,12 @@
import static android.content.res.Resources.ID_NULL;
import static com.android.launcher3.icons.GraphicsUtils.getExpectedBitmapSize;
+import static com.android.launcher3.icons.IconProvider.ICON_TYPE_CALENDAR;
+import static com.android.launcher3.icons.IconProvider.ICON_TYPE_CLOCK;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
@@ -38,6 +41,7 @@
import androidx.annotation.Nullable;
import com.android.launcher3.icons.BitmapInfo.Extender;
+import com.android.launcher3.icons.ClockDrawableWrapper.ClockBitmapInfo;
import com.android.launcher3.icons.cache.BaseIconCache;
import java.io.ByteArrayInputStream;
@@ -72,6 +76,7 @@
mBgWrapper = new AdaptiveIconDrawable(new ColorDrawable(colorBg), null);
mBadgeBounds = bitmapInfo.mUserBadge == null ? null :
new Rect(0, 0, bitmapInfo.mUserBadge.getWidth(), bitmapInfo.mUserBadge.getHeight());
+
}
@Override
@@ -147,7 +152,7 @@
if (isNullOrLowRes()) {
return null;
}
- String resName = mThemeData.mResources.getResourceName(mThemeData.mIconID);
+ String resName = mThemeData.mResources.getResourceName(mThemeData.mResID);
ByteArrayOutputStream out = new ByteArrayOutputStream(
getExpectedBitmapSize(icon) + 3 + resName.length());
try {
@@ -200,30 +205,46 @@
public static class ThemeData {
final Resources mResources;
- final int mIconID;
+ final int mResID;
- public ThemeData(Resources resources, int iconID) {
+ public ThemeData(Resources resources, int resID) {
mResources = resources;
- mIconID = iconID;
+ mResID = resID;
}
Drawable loadMonochromeDrawable(int accentColor) {
- Drawable d = mResources.getDrawable(mIconID).mutate();
+ Drawable d = mResources.getDrawable(mResID).mutate();
d.setTint(accentColor);
- d = new InsetDrawable(d, .15f);
+ d = new InsetDrawable(d, .2f);
return d;
}
- public Drawable wrapDrawable(Drawable original) {
- return (original instanceof AdaptiveIconDrawable)
- ? new ThemedAdaptiveIcon((AdaptiveIconDrawable) original, this)
- : original;
+ public Drawable wrapDrawable(Drawable original, int iconType) {
+ if (!(original instanceof AdaptiveIconDrawable)) {
+ return original;
+ }
+ AdaptiveIconDrawable aid = (AdaptiveIconDrawable) original;
+ String resourceType = mResources.getResourceTypeName(mResID);
+ if (iconType == ICON_TYPE_CALENDAR && "array".equals(resourceType)) {
+ TypedArray ta = mResources.obtainTypedArray(mResID);
+ int id = ta.getResourceId(IconProvider.getDay(), ID_NULL);
+ ta.recycle();
+ return id == ID_NULL ? original
+ : new ThemedAdaptiveIcon(aid, new ThemeData(mResources, id));
+ } else if (iconType == ICON_TYPE_CLOCK && "array".equals(resourceType)) {
+ ((ClockDrawableWrapper) original).mThemeData = this;
+ return original;
+ } else if ("drawable".equals(resourceType)) {
+ return new ThemedAdaptiveIcon(aid, this);
+ } else {
+ return original;
+ }
}
}
- public static class ThemedAdaptiveIcon extends AdaptiveIconDrawable implements Extender {
+ static class ThemedAdaptiveIcon extends AdaptiveIconDrawable implements Extender {
- private final ThemeData mThemeData;
+ protected final ThemeData mThemeData;
public ThemedAdaptiveIcon(AdaptiveIconDrawable parent, ThemeData themeData) {
super(parent.getBackground(), parent.getForeground());
@@ -243,9 +264,7 @@
draw(canvas);
}
- /**
- * Returns a new icon with theme applied
- */
+ @Override
public Drawable getThemedDrawable(Context context) {
int[] colors = getColors(context);
Drawable bg = new ColorDrawable(colors[0]);