diff --git a/app/src/main/java/fr/neamar/kiss/IconsHandler.java b/app/src/main/java/fr/neamar/kiss/IconsHandler.java index 007482adb..176293fa2 100644 --- a/app/src/main/java/fr/neamar/kiss/IconsHandler.java +++ b/app/src/main/java/fr/neamar/kiss/IconsHandler.java @@ -27,7 +27,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Random; import fr.neamar.kiss.db.AppRecord; import fr.neamar.kiss.db.DBHelper; @@ -38,6 +37,7 @@ import fr.neamar.kiss.result.AppResult; import fr.neamar.kiss.result.TagDummyResult; import fr.neamar.kiss.utils.DrawableUtils; +import fr.neamar.kiss.utils.IconShape; import fr.neamar.kiss.utils.UserHandle; import fr.neamar.kiss.utils.Utilities; @@ -57,7 +57,7 @@ public class IconsHandler { private final SystemIconPack mSystemPack = new SystemIconPack(); private boolean mForceAdaptive = false; private boolean mContactPackMask = false; - private int mContactsShape = DrawableUtils.SHAPE_SYSTEM; + private IconShape mContactsShape = IconShape.SHAPE_SYSTEM; private boolean mForceShape = false; private Utilities.AsyncRun mLoadIconsPackTask = null; private volatile Map customIconIds = null; @@ -101,11 +101,13 @@ public void onPrefChanged(SharedPreferences pref, String key) { } } - private static int getAdaptiveShape(SharedPreferences pref, String key) { + @NonNull + private static IconShape getAdaptiveShape(SharedPreferences pref, String key) { try { - return Integer.parseInt(pref.getString(key, String.valueOf(DrawableUtils.SHAPE_SYSTEM))); + int shapeId = Integer.parseInt(pref.getString(key, String.valueOf(IconShape.SHAPE_SYSTEM.getId()))); + return IconShape.valueById(shapeId); } catch (Exception e) { - return DrawableUtils.SHAPE_SYSTEM; + return IconShape.SHAPE_SYSTEM; } } @@ -222,7 +224,7 @@ public Drawable getBackgroundDrawable(@ColorInt int backgroundColor) { return null; } - final int shape = getShapeForGeneratingDrawable(); + final IconShape shape = getShapeForGeneratingDrawable(); Drawable drawable = DrawableUtils.generateBackgroundDrawable(ctx, backgroundColor, shape); return forceIconMask(drawable, shape); } @@ -232,7 +234,7 @@ public Drawable getDrawableIconForCodepoint(int codePoint, @ColorInt int textCol if (mIconPack != null && !mIconPack.isLoaded()) { return null; } - final int shape = getShapeForGeneratingDrawable(); + final IconShape shape = getShapeForGeneratingDrawable(); Drawable drawable = DrawableUtils.generateCodepointDrawable(ctx, codePoint, textColor, backgroundColor, shape); return forceIconMask(drawable, shape); } @@ -272,7 +274,7 @@ public Drawable applyBadge(@NonNull Drawable drawable, @NonNull UserHandle userH } public Drawable applyContactMask(@NonNull Context ctx, @NonNull Drawable drawable) { - final int shape = getContactsShape(); + final IconShape shape = getContactsShape(); if (mContactPackMask && mIconPack != null && mIconPack.hasMask()) { // if the icon pack has a mask, use that instead of the adaptive shape @@ -292,7 +294,7 @@ public Drawable applyContactMask(@NonNull Context ctx, @NonNull Drawable drawabl * @param drawable drawable to mask * @return masked drawable */ - private Drawable forceIconMask(@NonNull Drawable drawable, int shape) { + private Drawable forceIconMask(@NonNull Drawable drawable, @NonNull IconShape shape) { // apply mask if (mIconPack != null && mIconPack.hasMask()) { // if the icon pack has a mask, use that instead of the adaptive shape @@ -305,42 +307,37 @@ private Drawable forceIconMask(@NonNull Drawable drawable, int shape) { /** * Get shape used for contact icons with fallbacks. - * If contacts shape is {@link DrawableUtils#SHAPE_SYSTEM} app shape is used. - * If app shape is {@link DrawableUtils#SHAPE_SYSTEM} too and no icon mask can be configured for device, used shape is a circle. + * If contacts shape is {@link IconShape#SHAPE_SYSTEM} app shape is used. + * If app shape is {@link IconShape#SHAPE_SYSTEM} too and no icon mask can be configured for device, used shape is a circle. * * @return shape */ - private int getContactsShape() { - int shape = mContactsShape; - if (shape == DrawableUtils.SHAPE_SYSTEM) { + @NonNull + private IconShape getContactsShape() { + IconShape shape = mContactsShape; + if (shape == IconShape.SHAPE_SYSTEM) { shape = mSystemPack.getAdaptiveShape(); } - if (shape == DrawableUtils.SHAPE_SYSTEM && !DrawableUtils.hasDeviceConfiguredMask()) { - shape = DrawableUtils.SHAPE_CIRCLE; + // contacts have square images, so fallback to circle explicitly + if (shape == IconShape.SHAPE_SYSTEM && !DrawableUtils.hasDeviceConfiguredMask()) { + shape = IconShape.SHAPE_CIRCLE; } return shape; } /** * Get shape used for generating drawables with fallbacks. - * If icon pack has mask then {@link DrawableUtils#SHAPE_SYSTEM} is used. - * If shape is {@link DrawableUtils#SHAPE_SYSTEM} too and no icon mask can be configured for device, used shape is a circle. + * If icon pack has mask then {@link IconShape#SHAPE_SYSTEM} is used. + * If shape is {@link IconShape#SHAPE_SYSTEM} too and no icon mask can be configured for device, used shape is a circle. * * @return shape */ - private int getShapeForGeneratingDrawable() { - int shape = mSystemPack.getAdaptiveShape(); + @NonNull + private IconShape getShapeForGeneratingDrawable() { + IconShape shape = mSystemPack.getAdaptiveShape(); if (mIconPack != null && mIconPack.hasMask()) { - shape = DrawableUtils.SHAPE_SYSTEM; + shape = IconShape.SHAPE_SYSTEM; } - if (shape == DrawableUtils.SHAPE_SYSTEM && !DrawableUtils.hasDeviceConfiguredMask()) { - shape = DrawableUtils.SHAPE_CIRCLE; - } - if (shape == DrawableUtils.SHAPE_TEARDROP_RND) { - Random r = new Random(); - shape = DrawableUtils.SHAPE_TEARDROP_BR + r.nextInt(4); - } - return shape; } diff --git a/app/src/main/java/fr/neamar/kiss/icons/SystemIconPack.java b/app/src/main/java/fr/neamar/kiss/icons/SystemIconPack.java index dbf599ea3..a9c200290 100644 --- a/app/src/main/java/fr/neamar/kiss/icons/SystemIconPack.java +++ b/app/src/main/java/fr/neamar/kiss/icons/SystemIconPack.java @@ -13,6 +13,7 @@ import fr.neamar.kiss.ui.GoogleCalendarIcon; import fr.neamar.kiss.utils.DrawableUtils; +import fr.neamar.kiss.utils.IconShape; import fr.neamar.kiss.utils.PackageManagerUtils; import fr.neamar.kiss.utils.UserHandle; @@ -20,7 +21,7 @@ public class SystemIconPack implements IconPack { private static final String TAG = SystemIconPack.class.getSimpleName(); private final String packageName; - private int mAdaptiveShape = DrawableUtils.SHAPE_SYSTEM; + private IconShape mAdaptiveShape = IconShape.SHAPE_SYSTEM; public SystemIconPack(String packageName) { this.packageName = packageName; @@ -40,11 +41,12 @@ public String getPackPackageName() { public void load(PackageManager packageManager) { } - public int getAdaptiveShape() { + @NonNull + public IconShape getAdaptiveShape() { return mAdaptiveShape; } - public void setAdaptiveShape(int shape) { + public void setAdaptiveShape(@NonNull IconShape shape) { mAdaptiveShape = shape; } diff --git a/app/src/main/java/fr/neamar/kiss/utils/DrawableUtils.java b/app/src/main/java/fr/neamar/kiss/utils/DrawableUtils.java index c8cb59424..cf86c56f4 100644 --- a/app/src/main/java/fr/neamar/kiss/utils/DrawableUtils.java +++ b/app/src/main/java/fr/neamar/kiss/utils/DrawableUtils.java @@ -29,24 +29,12 @@ public class DrawableUtils { - public static final int SHAPE_SYSTEM = 0; - public static final int SHAPE_CIRCLE = 1; - public static final int SHAPE_SQUARE = 2; - public static final int SHAPE_SQUIRCLE = 3; - public static final int SHAPE_ROUND_RECT = 4; - public static final int SHAPE_TEARDROP_BR = 5; - private static final int SHAPE_TEARDROP_BL = 6; - private static final int SHAPE_TEARDROP_TL = 7; - private static final int SHAPE_TEARDROP_TR = 8; - public static final int SHAPE_TEARDROP_RND = 9; - public static final int SHAPE_HEXAGON = 10; - public static final int SHAPE_OCTAGON = 11; - private static final Paint PAINT = new Paint(); private static final Path SHAPE_PATH = new Path(); private static final RectF RECT_F = new RectF(); public static final String KEY_THEMED_ICONS = "themed-icons"; private static final String TAG = DrawableUtils.class.getSimpleName(); + private static final IconShape[] TEARDROP_SHAPES = {IconShape.SHAPE_TEARDROP_BR, IconShape.SHAPE_TEARDROP_BL, IconShape.SHAPE_TEARDROP_TL, IconShape.SHAPE_TEARDROP_TR}; // https://stackoverflow.com/questions/3035692/how-to-convert-a-drawable-to-a-bitmap public static Bitmap drawableToBitmap(@NonNull Drawable drawable) { @@ -78,7 +66,7 @@ public static Bitmap drawableToBitmap(@NonNull Drawable drawable) { * @param shape from SHAPE_* * @return margin size */ - private static float getScaleToFit(int shape) { + private static float getScaleToFit(IconShape shape) { switch (shape) { case SHAPE_SYSTEM: case SHAPE_CIRCLE: @@ -95,8 +83,9 @@ private static float getScaleToFit(int shape) { return 0.26f; case SHAPE_OCTAGON: return 0.25f; + default: + return 0f; } - return 0.f; } /** @@ -110,12 +99,11 @@ private static float getScaleToFit(int shape) { * @return shaped icon */ @NonNull - public static Drawable applyIconMaskShape(@NonNull Context ctx, @NonNull Drawable icon, int shape, boolean fitInside, @ColorInt int backgroundColor) { - if (shape == SHAPE_SYSTEM && !hasDeviceConfiguredMask()) + public static Drawable applyIconMaskShape(@NonNull Context ctx, @NonNull Drawable icon, @NonNull IconShape shape, boolean fitInside, @ColorInt int backgroundColor) { + if (shape == IconShape.SHAPE_SYSTEM && !hasDeviceConfiguredMask()) { // if no icon mask can be configured for device, then use icon as is return icon; - if (shape == SHAPE_TEARDROP_RND) - shape = SHAPE_TEARDROP_BR + (icon.hashCode() % 4); + } Bitmap outputBitmap; Canvas outputCanvas; @@ -134,7 +122,7 @@ public static Drawable applyIconMaskShape(@NonNull Context ctx, @NonNull Drawabl outputBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888); outputCanvas = new Canvas(outputBitmap); - setIconShapeAndDrawBackground(outputCanvas, backgroundColor, shape, false); + setIconShapeAndDrawBackground(outputCanvas, backgroundColor, shape, false, icon.hashCode()); // Stretch adaptive layers because they are 108dp and the icon size is 48dp if (bgDrawable != null) { @@ -148,7 +136,7 @@ public static Drawable applyIconMaskShape(@NonNull Context ctx, @NonNull Drawabl } } // If icon is not adaptive, put it in a colored canvas to make it have a unified shape - else if (icon != null) { + else { // Shrink icon fit inside the shape int iconSize; int iconOffset = 0; @@ -167,19 +155,12 @@ else if (icon != null) { outputBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888); outputCanvas = new Canvas(outputBitmap); - setIconShapeAndDrawBackground(outputCanvas, backgroundColor, shape, true); + setIconShapeAndDrawBackground(outputCanvas, backgroundColor, shape, true, icon.hashCode()); // Shrink icon so that it fits the shape int bottomRightCorner = iconSize - iconOffset; icon.setBounds(iconOffset, iconOffset, bottomRightCorner, bottomRightCorner); icon.draw(outputCanvas); - } else { - int iconSize = ctx.getResources().getDimensionPixelSize(R.dimen.result_icon_size); - - outputBitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888); - outputCanvas = new Canvas(outputBitmap); - - setIconShapeAndDrawBackground(outputCanvas, Color.BLACK, shape, true); } return new BitmapDrawable(ctx.getResources(), outputBitmap); } @@ -189,8 +170,11 @@ else if (icon != null) { * Synchronized because class fields like {@link DrawableUtils#SHAPE_PATH}, {@link DrawableUtils#RECT_F} and {@link DrawableUtils#PAINT} are reused for every call, which may result in unexpected behaviour if method is called from different threads running in parallel. * * @param shape type of shape: DrawableUtils.SHAPE_* + * @param hash, for pseudo random shape if applicable */ - private synchronized static void setIconShapeAndDrawBackground(Canvas canvas, @ColorInt int backgroundColor, int shape, boolean drawBackground) { + private synchronized static void setIconShapeAndDrawBackground(Canvas canvas, @ColorInt int backgroundColor, @NonNull IconShape shape, boolean drawBackground, int hash) { + shape = getFinalShape(shape, hash); + int iconSize = canvas.getHeight(); final Path path = SHAPE_PATH; path.rewind(); @@ -348,16 +332,16 @@ public static boolean isThemedIconEnabled(Context ctx) { } @NonNull - public synchronized static Drawable generateBackgroundDrawable(@NonNull Context ctx, @ColorInt int backgroundColor, int shape) { - Bitmap bitmap = generateBackgroundBitmap(ctx, backgroundColor, shape); + public synchronized static Drawable generateBackgroundDrawable(@NonNull Context ctx, @ColorInt int backgroundColor, @NonNull IconShape shape) { + Bitmap bitmap = generateBackgroundBitmap(ctx, backgroundColor, shape, backgroundColor); return new BitmapDrawable(ctx.getResources(), bitmap); } @NonNull - public static Drawable generateCodepointDrawable(@NonNull Context ctx, int codepoint, @ColorInt int textColor, @ColorInt int backgroundColor, int shape) { + public static Drawable generateCodepointDrawable(@NonNull Context ctx, int codepoint, @ColorInt int textColor, @ColorInt int backgroundColor, @NonNull IconShape shape) { int iconSize = ctx.getResources().getDimensionPixelSize(R.dimen.result_icon_size); - Bitmap bitmap = generateBackgroundBitmap(ctx, backgroundColor, shape); + Bitmap bitmap = generateBackgroundBitmap(ctx, backgroundColor, shape, codepoint); // create a canvas from a bitmap Canvas canvas = new Canvas(bitmap); @@ -422,19 +406,30 @@ else if (!Character.UnicodeBlock.BASIC_LATIN.toString().equals(blockString)) { } @NonNull - private synchronized static Bitmap generateBackgroundBitmap(@NonNull Context ctx, @ColorInt int backgroundColor, int shape) { + private synchronized static Bitmap generateBackgroundBitmap(@NonNull Context ctx, @ColorInt int backgroundColor, @NonNull IconShape shape, int hash) { int iconSize = ctx.getResources().getDimensionPixelSize(R.dimen.result_icon_size); // create a canvas from a bitmap Bitmap bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); - if (shape == SHAPE_SYSTEM && !hasDeviceConfiguredMask()) { - shape = SHAPE_CIRCLE; - } - - setIconShapeAndDrawBackground(canvas, backgroundColor, shape, true); + setIconShapeAndDrawBackground(canvas, backgroundColor, shape, true, hash); return bitmap; } + public static IconShape getFinalShape(IconShape shape, int hash) { + switch (shape) { + case SHAPE_SYSTEM: + if (!hasDeviceConfiguredMask()) { + return IconShape.SHAPE_CIRCLE; + } + return shape; + case SHAPE_TEARDROP_RND: + return TEARDROP_SHAPES[Math.abs(hash % 4)]; + default: + return shape; + } + } + + } diff --git a/app/src/main/java/fr/neamar/kiss/utils/IconShape.java b/app/src/main/java/fr/neamar/kiss/utils/IconShape.java new file mode 100644 index 000000000..309f7d9d5 --- /dev/null +++ b/app/src/main/java/fr/neamar/kiss/utils/IconShape.java @@ -0,0 +1,40 @@ +package fr.neamar.kiss.utils; + +import androidx.annotation.NonNull; + +public enum IconShape { + + SHAPE_SYSTEM(0), + SHAPE_CIRCLE(1), + SHAPE_SQUARE(2), + SHAPE_SQUIRCLE(3), + SHAPE_ROUND_RECT(4), + SHAPE_TEARDROP_BR(5), + SHAPE_TEARDROP_BL(6), + SHAPE_TEARDROP_TL(7), + SHAPE_TEARDROP_TR(8), + SHAPE_TEARDROP_RND(9), + SHAPE_HEXAGON(10), + SHAPE_OCTAGON(11); + + private final int id; + + IconShape(int id) { + this.id = id; + } + + @NonNull + public static IconShape valueById(int shapeId) { + for (IconShape shape : values()) { + if (shape.getId() == shapeId) { + return shape; + } + } + return SHAPE_SYSTEM; + } + + public int getId() { + return id; + } + +}