Loading iconloaderlib/Android.bp +4 −0 Original line number Diff line number Diff line Loading @@ -22,12 +22,14 @@ android_library { min_sdk_version: "26", static_libs: [ "androidx.core_core", "androidx.core_core-ktx", ], resource_dirs: [ "res", ], srcs: [ "src/**/*.java", "src/**/*.kt", ], } Loading @@ -37,6 +39,7 @@ android_library { min_sdk_version: "26", static_libs: [ "androidx.core_core", "androidx.core_core-ktx", ], resource_dirs: [ "res", Loading @@ -44,5 +47,6 @@ android_library { srcs: [ "src/**/*.java", "src_full_lib/**/*.java", "src/**/*.kt", ], } iconloaderlib/build.gradle +4 −14 Original line number Diff line number Diff line plugins { id 'sysuigradleproject.android-library-conventions' id 'com.android.library' id 'org.jetbrains.kotlin.android' } android { namespace "com.android.launcher3.icons" sourceSets { main { java.srcDirs = ['src', 'src_full_lib'] java.srcDirs = ['src'] manifest.srcFile 'AndroidManifest.xml' res.srcDirs = ['res'] } } lintOptions { abortOnError false } tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" } } dependencies { implementation "androidx.core:core" } iconloaderlib/src/com/android/launcher3/icons/AdaptiveIconGenerator.kt 0 → 100644 +26 −0 Original line number Diff line number Diff line package com.android.launcher3.icons import android.graphics.Bitmap import android.graphics.drawable.Drawable import androidx.core.graphics.drawable.toBitmap object AdaptiveIconGenerator { private const val ICON_SCALE = 1.46f @JvmStatic fun toBitmap(drawable: Drawable) = drawable.toBitmap() @JvmStatic fun getScale(bitmap: Bitmap, scale: Float): Float { val topRightPx = bitmap.getPixel(0, 0) val topLeftPx = bitmap.getPixel(0, bitmap.height - 1) val bottomRightPx = bitmap.getPixel(bitmap.width - 1, 0) val bottomLeftPx = bitmap.getPixel(bitmap.width - 1, bitmap.height - 1) return if (!(topRightPx != 0 && topLeftPx != 0 && bottomRightPx != 0 && bottomLeftPx != 0)) { scale } else { ICON_SCALE } } } iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +13 −2 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; import com.android.launcher3.icons.BitmapInfo.Extender; import com.android.launcher3.util.FlagOp; Loading Loading @@ -323,10 +324,20 @@ public class BaseIconFactory implements AutoCloseable { if (!outShape[0]) { FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground()); fsd.setDrawable(icon); int color; if (icon.getIntrinsicHeight() > 0 && icon.getIntrinsicWidth() > 0) { Bitmap bitmap = AdaptiveIconGenerator.toBitmap(icon); fsd.setScale(AdaptiveIconGenerator.getScale(bitmap, scale)); color = ColorUtils.setAlphaComponent(new ColorExtractor().findDominantColorByHue(bitmap), 200); } else { fsd.setScale(scale); color = mWrapperBackgroundColor; } icon = dr; scale = getNormalizer().getScale(icon, outIconBounds, null, null); ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor); ((ColorDrawable) dr.getBackground()).setColor(color); } } else { scale = getNormalizer().getScale(icon, outIconBounds, null, null); Loading iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java +89 −7 Original line number Diff line number Diff line Loading @@ -27,9 +27,12 @@ import android.graphics.Path; import android.graphics.PathMeasure; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.util.Log; import android.view.ViewDebug; import androidx.core.graphics.ColorUtils; /** * Used to draw a notification dot on top of an icon. */ Loading @@ -38,10 +41,17 @@ public class DotRenderer { private static final String TAG = "DotRenderer"; // The dot size is defined as a percentage of the app icon size. private static final float SIZE_PERCENTAGE = 0.228f; private static final float SIZE_PERCENTAGE = 0.21f; private static final float SIZE_PERCENTAGE_WITH_COUNT = 0.28f; // The max number to draw on dots private static final int MAX_COUNT = 999; private final float mCircleRadius; private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); private final Paint mCircleShadowPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); private final Paint mTextPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); private final Bitmap mBackgroundWithShadow; private final float mBitmapOffset; Loading @@ -51,14 +61,22 @@ public class DotRenderer { private final float[] mLeftDotPosition; private static final int MIN_DOT_SIZE = 1; private final Rect mTextRect = new Rect(); private final boolean mDisplayCount; public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) { int size = Math.round(SIZE_PERCENTAGE * iconSizePx); this(iconSizePx, iconShapePath, pathSize, false, null); } public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize, Boolean displayCount, Typeface typeface) { mDisplayCount = displayCount; int size = Math.round((displayCount ? SIZE_PERCENTAGE_WITH_COUNT : SIZE_PERCENTAGE) * iconSizePx); if (size <= 0) { size = MIN_DOT_SIZE; } ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); builder.ambientShadowAlpha = 88; mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size); mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, displayCount ? (size - 25) : size); mCircleRadius = builder.radius; mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width. Loading @@ -66,6 +84,10 @@ public class DotRenderer { // Find the points on the path that are closest to the top left and right corners. mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1); mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1); mTextPaint.setTextSize(size * 0.65f); mTextPaint.setTextAlign(Paint.Align.LEFT); mTextPaint.setTypeface(typeface); } private static float[] getPathPoint(Path path, float size, float direction) { Loading Loading @@ -101,6 +123,10 @@ public class DotRenderer { * Draw a circle on top of the canvas according to the given params. */ public void draw(Canvas canvas, DrawParams params) { draw(canvas, params, 0); } public void draw(Canvas canvas, DrawParams params, int numNotifications) { if (params == null) { Log.e(TAG, "Invalid null argument(s) passed in call to draw."); return; Loading @@ -120,16 +146,70 @@ public class DotRenderer { float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset)); // We draw the dot relative to its center. canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY); float dx = dotCenterX + offsetX; float dy = dotCenterY + offsetY - 15f; if (numNotifications > 9 && numNotifications < 100) { canvas.translate(dx - 3f, dy); } else if (numNotifications > 99 && numNotifications < 1000) { canvas.translate(dx + 6f, dy); } else { canvas.translate(dx - 12f, dy); } canvas.scale(params.scale, params.scale); mCirclePaint.setColor(Color.BLACK); canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint); mCirclePaint.setColor(params.dotColor); canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); mCircleShadowPaint.setColor(params.shadowDotColor); if (numNotifications >= 10 && numNotifications < 1000) { canvas.drawRoundRect(new RectF(-mCircleRadius + 10, -mCircleRadius, mCircleRadius + 20, mCircleRadius), 50, 50, mCircleShadowPaint); canvas.drawRoundRect(new RectF(-mCircleRadius + 10, -mCircleRadius, mCircleRadius + 20, mCircleRadius), 50, 50, mCirclePaint); } else { canvas.drawCircle(5, 10, mCircleRadius, mCircleShadowPaint); canvas.drawCircle(5, 10, mCircleRadius, mCirclePaint); } if (mDisplayCount && numNotifications > 0) { // Draw the numNotifications text mTextPaint.setColor(getCounterTextColor(Color.WHITE)); mTextPaint.setTypeface(Typeface.DEFAULT_BOLD); mTextPaint.setTextSize(32f); String text = numToNotation(numNotifications); mTextPaint.getTextBounds(text, 0, text.length(), mTextRect); float y = mTextRect.height() / 2f - mTextRect.bottom; if (numNotifications < 10) { canvas.drawText(text, -4f, 22f, mTextPaint); } else if (numNotifications < 100) { canvas.drawText(text, -4f, y, mTextPaint); } else if (numNotifications >= 1000) { canvas.drawText(text, -14f, 20f, mTextPaint); } else { canvas.drawText(text, -14f, y, mTextPaint); } } canvas.restore(); } private String numToNotation(int num) { if (num < 1000) { return String.valueOf(num); } else { return num / 1000 + "k"; } } /** * Returns the color to use for the counter text based on the dot's background color. * * @param dotBackgroundColor The color of the dot background. * @return The color to use on the counter text. */ private int getCounterTextColor(int dotBackgroundColor) { return ColorUtils.setAlphaComponent(dotBackgroundColor, 0xFF); } public static class DrawParams { /** The color (possibly based on the icon) to use for the dot. */ @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) Loading @@ -146,5 +226,7 @@ public class DotRenderer { /** Whether the dot should align to the top left of the icon rather than the top right. */ @ViewDebug.ExportedProperty(category = "notification dot") public boolean leftAlign; @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) public int shadowDotColor; } } Loading
iconloaderlib/Android.bp +4 −0 Original line number Diff line number Diff line Loading @@ -22,12 +22,14 @@ android_library { min_sdk_version: "26", static_libs: [ "androidx.core_core", "androidx.core_core-ktx", ], resource_dirs: [ "res", ], srcs: [ "src/**/*.java", "src/**/*.kt", ], } Loading @@ -37,6 +39,7 @@ android_library { min_sdk_version: "26", static_libs: [ "androidx.core_core", "androidx.core_core-ktx", ], resource_dirs: [ "res", Loading @@ -44,5 +47,6 @@ android_library { srcs: [ "src/**/*.java", "src_full_lib/**/*.java", "src/**/*.kt", ], }
iconloaderlib/build.gradle +4 −14 Original line number Diff line number Diff line plugins { id 'sysuigradleproject.android-library-conventions' id 'com.android.library' id 'org.jetbrains.kotlin.android' } android { namespace "com.android.launcher3.icons" sourceSets { main { java.srcDirs = ['src', 'src_full_lib'] java.srcDirs = ['src'] manifest.srcFile 'AndroidManifest.xml' res.srcDirs = ['res'] } } lintOptions { abortOnError false } tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" } } dependencies { implementation "androidx.core:core" }
iconloaderlib/src/com/android/launcher3/icons/AdaptiveIconGenerator.kt 0 → 100644 +26 −0 Original line number Diff line number Diff line package com.android.launcher3.icons import android.graphics.Bitmap import android.graphics.drawable.Drawable import androidx.core.graphics.drawable.toBitmap object AdaptiveIconGenerator { private const val ICON_SCALE = 1.46f @JvmStatic fun toBitmap(drawable: Drawable) = drawable.toBitmap() @JvmStatic fun getScale(bitmap: Bitmap, scale: Float): Float { val topRightPx = bitmap.getPixel(0, 0) val topLeftPx = bitmap.getPixel(0, bitmap.height - 1) val bottomRightPx = bitmap.getPixel(bitmap.width - 1, 0) val bottomLeftPx = bitmap.getPixel(bitmap.width - 1, bitmap.height - 1) return if (!(topRightPx != 0 && topLeftPx != 0 && bottomRightPx != 0 && bottomLeftPx != 0)) { scale } else { ICON_SCALE } } }
iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +13 −2 Original line number Diff line number Diff line Loading @@ -38,6 +38,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.graphics.ColorUtils; import com.android.launcher3.icons.BitmapInfo.Extender; import com.android.launcher3.util.FlagOp; Loading Loading @@ -323,10 +324,20 @@ public class BaseIconFactory implements AutoCloseable { if (!outShape[0]) { FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground()); fsd.setDrawable(icon); int color; if (icon.getIntrinsicHeight() > 0 && icon.getIntrinsicWidth() > 0) { Bitmap bitmap = AdaptiveIconGenerator.toBitmap(icon); fsd.setScale(AdaptiveIconGenerator.getScale(bitmap, scale)); color = ColorUtils.setAlphaComponent(new ColorExtractor().findDominantColorByHue(bitmap), 200); } else { fsd.setScale(scale); color = mWrapperBackgroundColor; } icon = dr; scale = getNormalizer().getScale(icon, outIconBounds, null, null); ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor); ((ColorDrawable) dr.getBackground()).setColor(color); } } else { scale = getNormalizer().getScale(icon, outIconBounds, null, null); Loading
iconloaderlib/src/com/android/launcher3/icons/DotRenderer.java +89 −7 Original line number Diff line number Diff line Loading @@ -27,9 +27,12 @@ import android.graphics.Path; import android.graphics.PathMeasure; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.util.Log; import android.view.ViewDebug; import androidx.core.graphics.ColorUtils; /** * Used to draw a notification dot on top of an icon. */ Loading @@ -38,10 +41,17 @@ public class DotRenderer { private static final String TAG = "DotRenderer"; // The dot size is defined as a percentage of the app icon size. private static final float SIZE_PERCENTAGE = 0.228f; private static final float SIZE_PERCENTAGE = 0.21f; private static final float SIZE_PERCENTAGE_WITH_COUNT = 0.28f; // The max number to draw on dots private static final int MAX_COUNT = 999; private final float mCircleRadius; private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); private final Paint mCircleShadowPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); private final Paint mTextPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); private final Bitmap mBackgroundWithShadow; private final float mBitmapOffset; Loading @@ -51,14 +61,22 @@ public class DotRenderer { private final float[] mLeftDotPosition; private static final int MIN_DOT_SIZE = 1; private final Rect mTextRect = new Rect(); private final boolean mDisplayCount; public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize) { int size = Math.round(SIZE_PERCENTAGE * iconSizePx); this(iconSizePx, iconShapePath, pathSize, false, null); } public DotRenderer(int iconSizePx, Path iconShapePath, int pathSize, Boolean displayCount, Typeface typeface) { mDisplayCount = displayCount; int size = Math.round((displayCount ? SIZE_PERCENTAGE_WITH_COUNT : SIZE_PERCENTAGE) * iconSizePx); if (size <= 0) { size = MIN_DOT_SIZE; } ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); builder.ambientShadowAlpha = 88; mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, size); mBackgroundWithShadow = builder.setupBlurForSize(size).createPill(size, displayCount ? (size - 25) : size); mCircleRadius = builder.radius; mBitmapOffset = -mBackgroundWithShadow.getHeight() * 0.5f; // Same as width. Loading @@ -66,6 +84,10 @@ public class DotRenderer { // Find the points on the path that are closest to the top left and right corners. mLeftDotPosition = getPathPoint(iconShapePath, pathSize, -1); mRightDotPosition = getPathPoint(iconShapePath, pathSize, 1); mTextPaint.setTextSize(size * 0.65f); mTextPaint.setTextAlign(Paint.Align.LEFT); mTextPaint.setTypeface(typeface); } private static float[] getPathPoint(Path path, float size, float direction) { Loading Loading @@ -101,6 +123,10 @@ public class DotRenderer { * Draw a circle on top of the canvas according to the given params. */ public void draw(Canvas canvas, DrawParams params) { draw(canvas, params, 0); } public void draw(Canvas canvas, DrawParams params, int numNotifications) { if (params == null) { Log.e(TAG, "Invalid null argument(s) passed in call to draw."); return; Loading @@ -120,16 +146,70 @@ public class DotRenderer { float offsetY = Math.max(0, canvasBounds.top - (dotCenterY + mBitmapOffset)); // We draw the dot relative to its center. canvas.translate(dotCenterX + offsetX, dotCenterY + offsetY); float dx = dotCenterX + offsetX; float dy = dotCenterY + offsetY - 15f; if (numNotifications > 9 && numNotifications < 100) { canvas.translate(dx - 3f, dy); } else if (numNotifications > 99 && numNotifications < 1000) { canvas.translate(dx + 6f, dy); } else { canvas.translate(dx - 12f, dy); } canvas.scale(params.scale, params.scale); mCirclePaint.setColor(Color.BLACK); canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint); mCirclePaint.setColor(params.dotColor); canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); mCircleShadowPaint.setColor(params.shadowDotColor); if (numNotifications >= 10 && numNotifications < 1000) { canvas.drawRoundRect(new RectF(-mCircleRadius + 10, -mCircleRadius, mCircleRadius + 20, mCircleRadius), 50, 50, mCircleShadowPaint); canvas.drawRoundRect(new RectF(-mCircleRadius + 10, -mCircleRadius, mCircleRadius + 20, mCircleRadius), 50, 50, mCirclePaint); } else { canvas.drawCircle(5, 10, mCircleRadius, mCircleShadowPaint); canvas.drawCircle(5, 10, mCircleRadius, mCirclePaint); } if (mDisplayCount && numNotifications > 0) { // Draw the numNotifications text mTextPaint.setColor(getCounterTextColor(Color.WHITE)); mTextPaint.setTypeface(Typeface.DEFAULT_BOLD); mTextPaint.setTextSize(32f); String text = numToNotation(numNotifications); mTextPaint.getTextBounds(text, 0, text.length(), mTextRect); float y = mTextRect.height() / 2f - mTextRect.bottom; if (numNotifications < 10) { canvas.drawText(text, -4f, 22f, mTextPaint); } else if (numNotifications < 100) { canvas.drawText(text, -4f, y, mTextPaint); } else if (numNotifications >= 1000) { canvas.drawText(text, -14f, 20f, mTextPaint); } else { canvas.drawText(text, -14f, y, mTextPaint); } } canvas.restore(); } private String numToNotation(int num) { if (num < 1000) { return String.valueOf(num); } else { return num / 1000 + "k"; } } /** * Returns the color to use for the counter text based on the dot's background color. * * @param dotBackgroundColor The color of the dot background. * @return The color to use on the counter text. */ private int getCounterTextColor(int dotBackgroundColor) { return ColorUtils.setAlphaComponent(dotBackgroundColor, 0xFF); } public static class DrawParams { /** The color (possibly based on the icon) to use for the dot. */ @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) Loading @@ -146,5 +226,7 @@ public class DotRenderer { /** Whether the dot should align to the top left of the icon rather than the top right. */ @ViewDebug.ExportedProperty(category = "notification dot") public boolean leftAlign; @ViewDebug.ExportedProperty(category = "notification dot", formatToHexString = true) public int shadowDotColor; } }