Loading iconloaderlib/build.gradle +4 −15 Original line number Diff line number Diff line apply plugin: 'com.android.library' android { compileSdkVersion COMPILE_SDK buildToolsVersion BUILD_TOOLS_VERSION defaultConfig { minSdkVersion 26 targetSdkVersion 28 plugins { id 'sysuigradleproject.android-library-conventions' } android { sourceSets { main { java.srcDirs = ['src', 'src_full_lib'] Loading @@ -24,13 +18,8 @@ android { tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation "androidx.core:core:${ANDROID_X_VERSION}" implementation "androidx.core:core" } iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +154 −75 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ import static com.android.launcher3.icons.BitmapInfo.FLAG_INSTANT; import static com.android.launcher3.icons.BitmapInfo.FLAG_WORK; import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; Loading @@ -31,12 +33,17 @@ import android.os.Build; import android.os.UserHandle; import android.util.SparseBooleanArray; import androidx.annotation.ColorInt; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.icons.BitmapInfo.Extender; import com.android.launcher3.util.FlagOp; import java.lang.annotation.Retention; import java.util.Objects; /** * This class will be moved to androidx library. There shouldn't be any dependency outside * this package. Loading @@ -45,27 +52,47 @@ public class BaseIconFactory implements AutoCloseable { private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE; protected static final int BITMAP_GENERATION_MODE_DEFAULT = 0; protected static final int BITMAP_GENERATION_MODE_ALPHA = 1; protected static final int BITMAP_GENERATION_MODE_WITH_SHADOW = 2; public static final int MODE_DEFAULT = 0; public static final int MODE_ALPHA = 1; public static final int MODE_WITH_SHADOW = 2; public static final int MODE_HARDWARE = 3; public static final int MODE_HARDWARE_WITH_SHADOW = 4; @Retention(SOURCE) @IntDef({MODE_DEFAULT, MODE_ALPHA, MODE_WITH_SHADOW, MODE_HARDWARE_WITH_SHADOW, MODE_HARDWARE}) @interface BitmapGenerationMode {} private static final float ICON_BADGE_SCALE = 0.444f; @NonNull private final Rect mOldBounds = new Rect(); @NonNull private final SparseBooleanArray mIsUserBadged = new SparseBooleanArray(); @NonNull protected final Context mContext; @NonNull private final Canvas mCanvas; @NonNull private final PackageManager mPm; @NonNull private final ColorExtractor mColorExtractor; private boolean mDisableColorExtractor; protected final int mFillResIconDpi; protected final int mIconBitmapSize; protected boolean mMonoIconEnabled; @Nullable private IconNormalizer mNormalizer; @Nullable private ShadowGenerator mShadowGenerator; private final boolean mShapeDetection; // Shadow bitmap used as background for theme icons Loading Loading @@ -97,9 +124,9 @@ public class BaseIconFactory implements AutoCloseable { protected void clear() { mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; mDisableColorExtractor = false; } @NonNull public ShadowGenerator getShadowGenerator() { if (mShadowGenerator == null) { mShadowGenerator = new ShadowGenerator(mIconBitmapSize); Loading @@ -107,6 +134,7 @@ public class BaseIconFactory implements AutoCloseable { return mShadowGenerator; } @NonNull public IconNormalizer getNormalizer() { if (mNormalizer == null) { mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection); Loading Loading @@ -149,12 +177,13 @@ public class BaseIconFactory implements AutoCloseable { icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f); } return BitmapInfo.of(icon, extractColor(icon)); return BitmapInfo.of(icon, mColorExtractor.findDominantColorByHue(icon)); } /** * Creates an icon from the bitmap cropped to the current device icon shape */ @NonNull public BitmapInfo createShapedIconBitmap(Bitmap icon, IconOptions options) { Drawable d = new FixedSizeBitmapDrawable(icon); float inset = getExtraInsetFraction(); Loading @@ -164,6 +193,7 @@ public class BaseIconFactory implements AutoCloseable { return createBadgedIconBitmap(d, options); } @NonNull public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon) { return createBadgedIconBitmap(icon, null); } Loading @@ -176,32 +206,46 @@ public class BaseIconFactory implements AutoCloseable { * @return a bitmap suitable for disaplaying as an icon at various system UIs. */ @TargetApi(Build.VERSION_CODES.TIRAMISU) @NonNull public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, @Nullable IconOptions options) { boolean shrinkNonAdaptiveIcons = options == null || options.mShrinkNonAdaptiveIcons; float[] scale = new float[1]; icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale); Bitmap bitmap = createIconBitmap(icon, scale[0], BITMAP_GENERATION_MODE_WITH_SHADOW); Bitmap bitmap = createIconBitmap(icon, scale[0], MODE_WITH_SHADOW); int color = extractColor(bitmap); int color = (options != null && options.mExtractedColor != null) ? options.mExtractedColor : mColorExtractor.findDominantColorByHue(bitmap); 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(); } else if (IconProvider.ATLEAST_T && mMonoIconEnabled) { Drawable mono = getMonochromeDrawable(icon); if (mono != null) { // Convert mono drawable to bitmap Drawable paddedMono = new ClippedMonoDrawable(mono); info.setMonoIcon( createIconBitmap(paddedMono, scale[0], BITMAP_GENERATION_MODE_ALPHA), this); info.setMonoIcon(createIconBitmap(mono, scale[0], MODE_ALPHA), this); } } info = info.withFlags(getBitmapFlagOp(options)); return info; } /** * Returns a monochromatic version of the given drawable or null, if it is not supported * @param base the original icon */ @TargetApi(Build.VERSION_CODES.TIRAMISU) protected Drawable getMonochromeDrawable(Drawable base) { if (base instanceof AdaptiveIconDrawable) { Drawable mono = ((AdaptiveIconDrawable) base).getMonochrome(); if (mono != null) { return new ClippedMonoDrawable(mono); } } return null; } @NonNull public FlagOp getBitmapFlagOp(@Nullable IconOptions options) { FlagOp op = FlagOp.NO_OP; if (options != null) { Loading @@ -227,47 +271,36 @@ public class BaseIconFactory implements AutoCloseable { return op; } /** package private */ Bitmap getWhiteShadowLayer() { @NonNull public Bitmap getWhiteShadowLayer() { if (mWhiteShadowLayer == null) { mWhiteShadowLayer = createScaledBitmapWithShadow( new AdaptiveIconDrawable(new ColorDrawable(Color.WHITE), null)); mWhiteShadowLayer = createScaledBitmap( new AdaptiveIconDrawable(new ColorDrawable(Color.WHITE), null), MODE_HARDWARE_WITH_SHADOW); } return mWhiteShadowLayer; } /** package private */ public Bitmap createScaledBitmapWithShadow(Drawable d) { float scale = getNormalizer().getScale(d, null, null, null); Bitmap bitmap = createIconBitmap(d, scale); return BitmapRenderer.createHardwareBitmap(bitmap.getWidth(), bitmap.getHeight(), canvas -> getShadowGenerator().recreateIcon(bitmap, canvas)); } public Bitmap createScaledBitmapWithoutShadow(Drawable icon) { @NonNull public Bitmap createScaledBitmap(@NonNull Drawable icon, @BitmapGenerationMode int mode) { RectF iconBounds = new RectF(); float[] scale = new float[1]; icon = normalizeAndWrapToAdaptiveIcon(icon, true, iconBounds, scale); return createIconBitmap(icon, Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds))); Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)), mode); } /** * Sets the background color used for wrapped adaptive icon */ public void setWrapperBackgroundColor(int color) { public void setWrapperBackgroundColor(final int color) { mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color; } /** * Disables the dominant color extraction for all icons loaded. */ public void disableColorExtraction() { mDisableColorExtractor = true; } protected Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon, boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) { @Nullable protected Drawable normalizeAndWrapToAdaptiveIcon(@Nullable Drawable icon, final boolean shrinkNonAdaptiveIcons, @Nullable final RectF outIconBounds, @NonNull final float[] outScale) { if (icon == null) { return null; } Loading Loading @@ -298,20 +331,26 @@ public class BaseIconFactory implements AutoCloseable { return icon; } protected Bitmap createIconBitmap(Drawable icon, float scale) { return createIconBitmap(icon, scale, BITMAP_GENERATION_MODE_DEFAULT); @NonNull protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale) { return createIconBitmap(icon, scale, MODE_DEFAULT); } protected Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int bitmapGenerationMode) { @NonNull protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale, @BitmapGenerationMode int bitmapGenerationMode) { final int size = mIconBitmapSize; final Bitmap bitmap; switch (bitmapGenerationMode) { case BITMAP_GENERATION_MODE_ALPHA: case MODE_ALPHA: bitmap = Bitmap.createBitmap(size, size, Config.ALPHA_8); break; case BITMAP_GENERATION_MODE_WITH_SHADOW: case MODE_HARDWARE: case MODE_HARDWARE_WITH_SHADOW: { return BitmapRenderer.createHardwareBitmap(size, size, canvas -> drawIconBitmap(canvas, icon, scale, bitmapGenerationMode, null)); } case MODE_WITH_SHADOW: default: bitmap = Bitmap.createBitmap(size, size, Config.ARGB_8888); break; Loading @@ -320,6 +359,15 @@ public class BaseIconFactory implements AutoCloseable { return bitmap; } mCanvas.setBitmap(bitmap); drawIconBitmap(mCanvas, icon, scale, bitmapGenerationMode, bitmap); mCanvas.setBitmap(null); return bitmap; } private void drawIconBitmap(@NonNull Canvas canvas, @Nullable final Drawable icon, final float scale, @BitmapGenerationMode int bitmapGenerationMode, @Nullable Bitmap targetBitmap) { final int size = mIconBitmapSize; mOldBounds.set(icon.getBounds()); if (icon instanceof AdaptiveIconDrawable) { Loading @@ -327,19 +375,20 @@ public class BaseIconFactory implements AutoCloseable { Math.round(size * (1 - scale) / 2)); // 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 (bitmapGenerationMode == BITMAP_GENERATION_MODE_WITH_SHADOW) { int count = canvas.save(); canvas.translate(offset, offset); if (bitmapGenerationMode == MODE_WITH_SHADOW || bitmapGenerationMode == MODE_HARDWARE_WITH_SHADOW) { getShadowGenerator().addPathShadow( ((AdaptiveIconDrawable) icon).getIconMask(), mCanvas); ((AdaptiveIconDrawable) icon).getIconMask(), canvas); } if (icon instanceof BitmapInfo.Extender) { ((Extender) icon).drawForPersistence(mCanvas); ((Extender) icon).drawForPersistence(canvas); } else { icon.draw(mCanvas); icon.draw(canvas); } mCanvas.restoreToCount(count); canvas.restoreToCount(count); } else { if (icon instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; Loading @@ -365,15 +414,24 @@ public class BaseIconFactory implements AutoCloseable { final int left = (size - width) / 2; final int top = (size - height) / 2; icon.setBounds(left, top, left + width, top + height); mCanvas.save(); mCanvas.scale(scale, scale, size / 2, size / 2); icon.draw(mCanvas); mCanvas.restore(); canvas.save(); canvas.scale(scale, scale, size / 2, size / 2); icon.draw(canvas); canvas.restore(); if (bitmapGenerationMode == MODE_WITH_SHADOW && targetBitmap != null) { // Shadow extraction only works in software mode getShadowGenerator().drawShadow(targetBitmap, canvas); // Draw the icon again on top: canvas.save(); canvas.scale(scale, scale, size / 2, size / 2); icon.draw(canvas); canvas.restore(); } } icon.setBounds(mOldBounds); mCanvas.setBitmap(null); return bitmap; } @Override Loading @@ -381,36 +439,40 @@ public class BaseIconFactory implements AutoCloseable { clear(); } @NonNull public BitmapInfo makeDefaultIcon() { return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi)); } public static Drawable getFullResDefaultActivityIcon(int iconDpi) { return Resources.getSystem().getDrawableForDensity( android.R.drawable.sym_def_app_icon, iconDpi); } private int extractColor(Bitmap bitmap) { return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap); @NonNull public static Drawable getFullResDefaultActivityIcon(final int iconDpi) { return Objects.requireNonNull(Resources.getSystem().getDrawableForDensity( android.R.drawable.sym_def_app_icon, iconDpi)); } /** * Returns the correct badge size given an icon size */ public static int getBadgeSizeForIconSize(int iconSize) { public static int getBadgeSizeForIconSize(final int iconSize) { return (int) (ICON_BADGE_SCALE * iconSize); } public static class IconOptions { boolean mShrinkNonAdaptiveIcons = true; boolean mIsInstantApp; UserHandle mUserHandle; @Nullable UserHandle mUserHandle; @ColorInt @Nullable Integer mExtractedColor; /** * Set to false if non-adaptive icons should not be treated */ public IconOptions setShrinkNonAdaptiveIcons(boolean shrink) { @NonNull public IconOptions setShrinkNonAdaptiveIcons(final boolean shrink) { mShrinkNonAdaptiveIcons = shrink; return this; } Loading @@ -418,7 +480,8 @@ public class BaseIconFactory implements AutoCloseable { /** * User for this icon, in case of badging */ public IconOptions setUser(UserHandle user) { @NonNull public IconOptions setUser(@Nullable final UserHandle user) { mUserHandle = user; return this; } Loading @@ -426,10 +489,20 @@ public class BaseIconFactory implements AutoCloseable { /** * If this icon represents an instant app */ public IconOptions setInstantApp(boolean instantApp) { @NonNull public IconOptions setInstantApp(final boolean instantApp) { mIsInstantApp = instantApp; return this; } /** * Disables auto color extraction and overrides the color to the provided value */ @NonNull public IconOptions setExtractedColor(@ColorInt int color) { mExtractedColor = color; return this; } } /** Loading @@ -439,7 +512,7 @@ public class BaseIconFactory implements AutoCloseable { */ private static class FixedSizeBitmapDrawable extends BitmapDrawable { public FixedSizeBitmapDrawable(Bitmap bitmap) { public FixedSizeBitmapDrawable(@Nullable final Bitmap bitmap) { super(null, bitmap); } Loading @@ -466,11 +539,12 @@ public class BaseIconFactory implements AutoCloseable { } } private static class ClippedMonoDrawable extends InsetDrawable { protected static class ClippedMonoDrawable extends InsetDrawable { @NonNull private final AdaptiveIconDrawable mCrop; public ClippedMonoDrawable(Drawable base) { public ClippedMonoDrawable(@Nullable final Drawable base) { super(base, -getExtraInsetFraction()); mCrop = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null); } Loading @@ -487,11 +561,16 @@ public class BaseIconFactory implements AutoCloseable { private static class CenterTextDrawable extends ColorDrawable { @NonNull private final Rect mTextBounds = new Rect(); @NonNull private final Paint mTextPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); @NonNull private final String mText; CenterTextDrawable(String text, int color) { CenterTextDrawable(@NonNull final String text, final int color) { mText = text; mTextPaint.setColor(color); } Loading iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java +2 −1 Original line number Diff line number Diff line Loading @@ -214,7 +214,8 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap BaseIconFactory iconFactory, float normalizationScale) { AdaptiveIconDrawable background = new AdaptiveIconDrawable( getBackground().getConstantState().newDrawable(), null); Bitmap flattenBG = iconFactory.createScaledBitmapWithShadow(background); Bitmap flattenBG = iconFactory.createScaledBitmap(background, BaseIconFactory.MODE_HARDWARE_WITH_SHADOW); // Only pass theme info if mono-icon is enabled AnimationInfo themeInfo = iconFactory.mMonoIconEnabled ? mThemeInfo : null; Loading iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java +13 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Color; import android.util.SparseArray; import androidx.annotation.NonNull; import java.util.Arrays; /** Loading @@ -26,16 +29,24 @@ import java.util.Arrays; public class ColorExtractor { private final int NUM_SAMPLES = 20; @NonNull private final float[] mTmpHsv = new float[3]; @NonNull private final float[] mTmpHueScoreHistogram = new float[360]; @NonNull private final int[] mTmpPixels = new int[NUM_SAMPLES]; @NonNull private final SparseArray<Float> mTmpRgbScores = new SparseArray<>(); /** * This picks a dominant color, looking for high-saturation, high-value, repeated hues. * @param bitmap The bitmap to scan */ public int findDominantColorByHue(Bitmap bitmap) { public int findDominantColorByHue(@NonNull final Bitmap bitmap) { return findDominantColorByHue(bitmap, NUM_SAMPLES); } Loading @@ -43,7 +54,7 @@ public class ColorExtractor { * This picks a dominant color, looking for high-saturation, high-value, repeated hues. * @param bitmap The bitmap to scan */ public int findDominantColorByHue(Bitmap bitmap, int samples) { protected int findDominantColorByHue(@NonNull final Bitmap bitmap, final int samples) { final int height = bitmap.getHeight(); final int width = bitmap.getWidth(); int sampleStride = (int) Math.sqrt((height * width) / samples); Loading iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java +1 −5 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ public class ShadowGenerator { mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); } public synchronized void recreateIcon(Bitmap icon, Canvas out) { public synchronized void drawShadow(Bitmap icon, Canvas out) { if (ENABLE_SHADOWS) { int[] offset = new int[2]; mBlurPaint.setMaskFilter(mDefaultBlurMaskFilter); Loading @@ -73,10 +73,6 @@ public class ShadowGenerator { out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint); } // Draw the icon mDrawPaint.setAlpha(255); out.drawBitmap(icon, 0, 0, mDrawPaint); } /** package private **/ Loading Loading
iconloaderlib/build.gradle +4 −15 Original line number Diff line number Diff line apply plugin: 'com.android.library' android { compileSdkVersion COMPILE_SDK buildToolsVersion BUILD_TOOLS_VERSION defaultConfig { minSdkVersion 26 targetSdkVersion 28 plugins { id 'sysuigradleproject.android-library-conventions' } android { sourceSets { main { java.srcDirs = ['src', 'src_full_lib'] Loading @@ -24,13 +18,8 @@ android { tasks.withType(JavaCompile) { options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation "androidx.core:core:${ANDROID_X_VERSION}" implementation "androidx.core:core" }
iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java +154 −75 Original line number Diff line number Diff line Loading @@ -9,6 +9,8 @@ import static com.android.launcher3.icons.BitmapInfo.FLAG_INSTANT; import static com.android.launcher3.icons.BitmapInfo.FLAG_WORK; import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; Loading @@ -31,12 +33,17 @@ import android.os.Build; import android.os.UserHandle; import android.util.SparseBooleanArray; import androidx.annotation.ColorInt; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.launcher3.icons.BitmapInfo.Extender; import com.android.launcher3.util.FlagOp; import java.lang.annotation.Retention; import java.util.Objects; /** * This class will be moved to androidx library. There shouldn't be any dependency outside * this package. Loading @@ -45,27 +52,47 @@ public class BaseIconFactory implements AutoCloseable { private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE; protected static final int BITMAP_GENERATION_MODE_DEFAULT = 0; protected static final int BITMAP_GENERATION_MODE_ALPHA = 1; protected static final int BITMAP_GENERATION_MODE_WITH_SHADOW = 2; public static final int MODE_DEFAULT = 0; public static final int MODE_ALPHA = 1; public static final int MODE_WITH_SHADOW = 2; public static final int MODE_HARDWARE = 3; public static final int MODE_HARDWARE_WITH_SHADOW = 4; @Retention(SOURCE) @IntDef({MODE_DEFAULT, MODE_ALPHA, MODE_WITH_SHADOW, MODE_HARDWARE_WITH_SHADOW, MODE_HARDWARE}) @interface BitmapGenerationMode {} private static final float ICON_BADGE_SCALE = 0.444f; @NonNull private final Rect mOldBounds = new Rect(); @NonNull private final SparseBooleanArray mIsUserBadged = new SparseBooleanArray(); @NonNull protected final Context mContext; @NonNull private final Canvas mCanvas; @NonNull private final PackageManager mPm; @NonNull private final ColorExtractor mColorExtractor; private boolean mDisableColorExtractor; protected final int mFillResIconDpi; protected final int mIconBitmapSize; protected boolean mMonoIconEnabled; @Nullable private IconNormalizer mNormalizer; @Nullable private ShadowGenerator mShadowGenerator; private final boolean mShapeDetection; // Shadow bitmap used as background for theme icons Loading Loading @@ -97,9 +124,9 @@ public class BaseIconFactory implements AutoCloseable { protected void clear() { mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND; mDisableColorExtractor = false; } @NonNull public ShadowGenerator getShadowGenerator() { if (mShadowGenerator == null) { mShadowGenerator = new ShadowGenerator(mIconBitmapSize); Loading @@ -107,6 +134,7 @@ public class BaseIconFactory implements AutoCloseable { return mShadowGenerator; } @NonNull public IconNormalizer getNormalizer() { if (mNormalizer == null) { mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection); Loading Loading @@ -149,12 +177,13 @@ public class BaseIconFactory implements AutoCloseable { icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f); } return BitmapInfo.of(icon, extractColor(icon)); return BitmapInfo.of(icon, mColorExtractor.findDominantColorByHue(icon)); } /** * Creates an icon from the bitmap cropped to the current device icon shape */ @NonNull public BitmapInfo createShapedIconBitmap(Bitmap icon, IconOptions options) { Drawable d = new FixedSizeBitmapDrawable(icon); float inset = getExtraInsetFraction(); Loading @@ -164,6 +193,7 @@ public class BaseIconFactory implements AutoCloseable { return createBadgedIconBitmap(d, options); } @NonNull public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon) { return createBadgedIconBitmap(icon, null); } Loading @@ -176,32 +206,46 @@ public class BaseIconFactory implements AutoCloseable { * @return a bitmap suitable for disaplaying as an icon at various system UIs. */ @TargetApi(Build.VERSION_CODES.TIRAMISU) @NonNull public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, @Nullable IconOptions options) { boolean shrinkNonAdaptiveIcons = options == null || options.mShrinkNonAdaptiveIcons; float[] scale = new float[1]; icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale); Bitmap bitmap = createIconBitmap(icon, scale[0], BITMAP_GENERATION_MODE_WITH_SHADOW); Bitmap bitmap = createIconBitmap(icon, scale[0], MODE_WITH_SHADOW); int color = extractColor(bitmap); int color = (options != null && options.mExtractedColor != null) ? options.mExtractedColor : mColorExtractor.findDominantColorByHue(bitmap); 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(); } else if (IconProvider.ATLEAST_T && mMonoIconEnabled) { Drawable mono = getMonochromeDrawable(icon); if (mono != null) { // Convert mono drawable to bitmap Drawable paddedMono = new ClippedMonoDrawable(mono); info.setMonoIcon( createIconBitmap(paddedMono, scale[0], BITMAP_GENERATION_MODE_ALPHA), this); info.setMonoIcon(createIconBitmap(mono, scale[0], MODE_ALPHA), this); } } info = info.withFlags(getBitmapFlagOp(options)); return info; } /** * Returns a monochromatic version of the given drawable or null, if it is not supported * @param base the original icon */ @TargetApi(Build.VERSION_CODES.TIRAMISU) protected Drawable getMonochromeDrawable(Drawable base) { if (base instanceof AdaptiveIconDrawable) { Drawable mono = ((AdaptiveIconDrawable) base).getMonochrome(); if (mono != null) { return new ClippedMonoDrawable(mono); } } return null; } @NonNull public FlagOp getBitmapFlagOp(@Nullable IconOptions options) { FlagOp op = FlagOp.NO_OP; if (options != null) { Loading @@ -227,47 +271,36 @@ public class BaseIconFactory implements AutoCloseable { return op; } /** package private */ Bitmap getWhiteShadowLayer() { @NonNull public Bitmap getWhiteShadowLayer() { if (mWhiteShadowLayer == null) { mWhiteShadowLayer = createScaledBitmapWithShadow( new AdaptiveIconDrawable(new ColorDrawable(Color.WHITE), null)); mWhiteShadowLayer = createScaledBitmap( new AdaptiveIconDrawable(new ColorDrawable(Color.WHITE), null), MODE_HARDWARE_WITH_SHADOW); } return mWhiteShadowLayer; } /** package private */ public Bitmap createScaledBitmapWithShadow(Drawable d) { float scale = getNormalizer().getScale(d, null, null, null); Bitmap bitmap = createIconBitmap(d, scale); return BitmapRenderer.createHardwareBitmap(bitmap.getWidth(), bitmap.getHeight(), canvas -> getShadowGenerator().recreateIcon(bitmap, canvas)); } public Bitmap createScaledBitmapWithoutShadow(Drawable icon) { @NonNull public Bitmap createScaledBitmap(@NonNull Drawable icon, @BitmapGenerationMode int mode) { RectF iconBounds = new RectF(); float[] scale = new float[1]; icon = normalizeAndWrapToAdaptiveIcon(icon, true, iconBounds, scale); return createIconBitmap(icon, Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds))); Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)), mode); } /** * Sets the background color used for wrapped adaptive icon */ public void setWrapperBackgroundColor(int color) { public void setWrapperBackgroundColor(final int color) { mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color; } /** * Disables the dominant color extraction for all icons loaded. */ public void disableColorExtraction() { mDisableColorExtractor = true; } protected Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon, boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) { @Nullable protected Drawable normalizeAndWrapToAdaptiveIcon(@Nullable Drawable icon, final boolean shrinkNonAdaptiveIcons, @Nullable final RectF outIconBounds, @NonNull final float[] outScale) { if (icon == null) { return null; } Loading Loading @@ -298,20 +331,26 @@ public class BaseIconFactory implements AutoCloseable { return icon; } protected Bitmap createIconBitmap(Drawable icon, float scale) { return createIconBitmap(icon, scale, BITMAP_GENERATION_MODE_DEFAULT); @NonNull protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale) { return createIconBitmap(icon, scale, MODE_DEFAULT); } protected Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int bitmapGenerationMode) { @NonNull protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale, @BitmapGenerationMode int bitmapGenerationMode) { final int size = mIconBitmapSize; final Bitmap bitmap; switch (bitmapGenerationMode) { case BITMAP_GENERATION_MODE_ALPHA: case MODE_ALPHA: bitmap = Bitmap.createBitmap(size, size, Config.ALPHA_8); break; case BITMAP_GENERATION_MODE_WITH_SHADOW: case MODE_HARDWARE: case MODE_HARDWARE_WITH_SHADOW: { return BitmapRenderer.createHardwareBitmap(size, size, canvas -> drawIconBitmap(canvas, icon, scale, bitmapGenerationMode, null)); } case MODE_WITH_SHADOW: default: bitmap = Bitmap.createBitmap(size, size, Config.ARGB_8888); break; Loading @@ -320,6 +359,15 @@ public class BaseIconFactory implements AutoCloseable { return bitmap; } mCanvas.setBitmap(bitmap); drawIconBitmap(mCanvas, icon, scale, bitmapGenerationMode, bitmap); mCanvas.setBitmap(null); return bitmap; } private void drawIconBitmap(@NonNull Canvas canvas, @Nullable final Drawable icon, final float scale, @BitmapGenerationMode int bitmapGenerationMode, @Nullable Bitmap targetBitmap) { final int size = mIconBitmapSize; mOldBounds.set(icon.getBounds()); if (icon instanceof AdaptiveIconDrawable) { Loading @@ -327,19 +375,20 @@ public class BaseIconFactory implements AutoCloseable { Math.round(size * (1 - scale) / 2)); // 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 (bitmapGenerationMode == BITMAP_GENERATION_MODE_WITH_SHADOW) { int count = canvas.save(); canvas.translate(offset, offset); if (bitmapGenerationMode == MODE_WITH_SHADOW || bitmapGenerationMode == MODE_HARDWARE_WITH_SHADOW) { getShadowGenerator().addPathShadow( ((AdaptiveIconDrawable) icon).getIconMask(), mCanvas); ((AdaptiveIconDrawable) icon).getIconMask(), canvas); } if (icon instanceof BitmapInfo.Extender) { ((Extender) icon).drawForPersistence(mCanvas); ((Extender) icon).drawForPersistence(canvas); } else { icon.draw(mCanvas); icon.draw(canvas); } mCanvas.restoreToCount(count); canvas.restoreToCount(count); } else { if (icon instanceof BitmapDrawable) { BitmapDrawable bitmapDrawable = (BitmapDrawable) icon; Loading @@ -365,15 +414,24 @@ public class BaseIconFactory implements AutoCloseable { final int left = (size - width) / 2; final int top = (size - height) / 2; icon.setBounds(left, top, left + width, top + height); mCanvas.save(); mCanvas.scale(scale, scale, size / 2, size / 2); icon.draw(mCanvas); mCanvas.restore(); canvas.save(); canvas.scale(scale, scale, size / 2, size / 2); icon.draw(canvas); canvas.restore(); if (bitmapGenerationMode == MODE_WITH_SHADOW && targetBitmap != null) { // Shadow extraction only works in software mode getShadowGenerator().drawShadow(targetBitmap, canvas); // Draw the icon again on top: canvas.save(); canvas.scale(scale, scale, size / 2, size / 2); icon.draw(canvas); canvas.restore(); } } icon.setBounds(mOldBounds); mCanvas.setBitmap(null); return bitmap; } @Override Loading @@ -381,36 +439,40 @@ public class BaseIconFactory implements AutoCloseable { clear(); } @NonNull public BitmapInfo makeDefaultIcon() { return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi)); } public static Drawable getFullResDefaultActivityIcon(int iconDpi) { return Resources.getSystem().getDrawableForDensity( android.R.drawable.sym_def_app_icon, iconDpi); } private int extractColor(Bitmap bitmap) { return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap); @NonNull public static Drawable getFullResDefaultActivityIcon(final int iconDpi) { return Objects.requireNonNull(Resources.getSystem().getDrawableForDensity( android.R.drawable.sym_def_app_icon, iconDpi)); } /** * Returns the correct badge size given an icon size */ public static int getBadgeSizeForIconSize(int iconSize) { public static int getBadgeSizeForIconSize(final int iconSize) { return (int) (ICON_BADGE_SCALE * iconSize); } public static class IconOptions { boolean mShrinkNonAdaptiveIcons = true; boolean mIsInstantApp; UserHandle mUserHandle; @Nullable UserHandle mUserHandle; @ColorInt @Nullable Integer mExtractedColor; /** * Set to false if non-adaptive icons should not be treated */ public IconOptions setShrinkNonAdaptiveIcons(boolean shrink) { @NonNull public IconOptions setShrinkNonAdaptiveIcons(final boolean shrink) { mShrinkNonAdaptiveIcons = shrink; return this; } Loading @@ -418,7 +480,8 @@ public class BaseIconFactory implements AutoCloseable { /** * User for this icon, in case of badging */ public IconOptions setUser(UserHandle user) { @NonNull public IconOptions setUser(@Nullable final UserHandle user) { mUserHandle = user; return this; } Loading @@ -426,10 +489,20 @@ public class BaseIconFactory implements AutoCloseable { /** * If this icon represents an instant app */ public IconOptions setInstantApp(boolean instantApp) { @NonNull public IconOptions setInstantApp(final boolean instantApp) { mIsInstantApp = instantApp; return this; } /** * Disables auto color extraction and overrides the color to the provided value */ @NonNull public IconOptions setExtractedColor(@ColorInt int color) { mExtractedColor = color; return this; } } /** Loading @@ -439,7 +512,7 @@ public class BaseIconFactory implements AutoCloseable { */ private static class FixedSizeBitmapDrawable extends BitmapDrawable { public FixedSizeBitmapDrawable(Bitmap bitmap) { public FixedSizeBitmapDrawable(@Nullable final Bitmap bitmap) { super(null, bitmap); } Loading @@ -466,11 +539,12 @@ public class BaseIconFactory implements AutoCloseable { } } private static class ClippedMonoDrawable extends InsetDrawable { protected static class ClippedMonoDrawable extends InsetDrawable { @NonNull private final AdaptiveIconDrawable mCrop; public ClippedMonoDrawable(Drawable base) { public ClippedMonoDrawable(@Nullable final Drawable base) { super(base, -getExtraInsetFraction()); mCrop = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null); } Loading @@ -487,11 +561,16 @@ public class BaseIconFactory implements AutoCloseable { private static class CenterTextDrawable extends ColorDrawable { @NonNull private final Rect mTextBounds = new Rect(); @NonNull private final Paint mTextPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); @NonNull private final String mText; CenterTextDrawable(String text, int color) { CenterTextDrawable(@NonNull final String text, final int color) { mText = text; mTextPaint.setColor(color); } Loading
iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java +2 −1 Original line number Diff line number Diff line Loading @@ -214,7 +214,8 @@ public class ClockDrawableWrapper extends AdaptiveIconDrawable implements Bitmap BaseIconFactory iconFactory, float normalizationScale) { AdaptiveIconDrawable background = new AdaptiveIconDrawable( getBackground().getConstantState().newDrawable(), null); Bitmap flattenBG = iconFactory.createScaledBitmapWithShadow(background); Bitmap flattenBG = iconFactory.createScaledBitmap(background, BaseIconFactory.MODE_HARDWARE_WITH_SHADOW); // Only pass theme info if mono-icon is enabled AnimationInfo themeInfo = iconFactory.mMonoIconEnabled ? mThemeInfo : null; Loading
iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java +13 −2 Original line number Diff line number Diff line Loading @@ -18,6 +18,9 @@ package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Color; import android.util.SparseArray; import androidx.annotation.NonNull; import java.util.Arrays; /** Loading @@ -26,16 +29,24 @@ import java.util.Arrays; public class ColorExtractor { private final int NUM_SAMPLES = 20; @NonNull private final float[] mTmpHsv = new float[3]; @NonNull private final float[] mTmpHueScoreHistogram = new float[360]; @NonNull private final int[] mTmpPixels = new int[NUM_SAMPLES]; @NonNull private final SparseArray<Float> mTmpRgbScores = new SparseArray<>(); /** * This picks a dominant color, looking for high-saturation, high-value, repeated hues. * @param bitmap The bitmap to scan */ public int findDominantColorByHue(Bitmap bitmap) { public int findDominantColorByHue(@NonNull final Bitmap bitmap) { return findDominantColorByHue(bitmap, NUM_SAMPLES); } Loading @@ -43,7 +54,7 @@ public class ColorExtractor { * This picks a dominant color, looking for high-saturation, high-value, repeated hues. * @param bitmap The bitmap to scan */ public int findDominantColorByHue(Bitmap bitmap, int samples) { protected int findDominantColorByHue(@NonNull final Bitmap bitmap, final int samples) { final int height = bitmap.getHeight(); final int width = bitmap.getWidth(); int sampleStride = (int) Math.sqrt((height * width) / samples); Loading
iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java +1 −5 Original line number Diff line number Diff line Loading @@ -58,7 +58,7 @@ public class ShadowGenerator { mDefaultBlurMaskFilter = new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL); } public synchronized void recreateIcon(Bitmap icon, Canvas out) { public synchronized void drawShadow(Bitmap icon, Canvas out) { if (ENABLE_SHADOWS) { int[] offset = new int[2]; mBlurPaint.setMaskFilter(mDefaultBlurMaskFilter); Loading @@ -73,10 +73,6 @@ public class ShadowGenerator { out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint); } // Draw the icon mDrawPaint.setAlpha(255); out.drawBitmap(icon, 0, 0, mDrawPaint); } /** package private **/ Loading