793f5c5186
Test: manual, presubmit on the source branch http://x20/teams/android-launcher/merge/ub-launcher3-qt-future-dev_6018744.htmlMerge commit '9353b00616ac688d2df6b9f8513c4895bb4119d7' into merge_ub-launcher3-qt-future-dev_6018744 Change-Id: Ie6235a262b7bba0aa9f2a2962a7aa499a0a2cb53 Merged-In: I8a1e20d0b175f03a1a05c81749f07ab5314e872a
366 lines
14 KiB
Java
366 lines
14 KiB
Java
package com.android.launcher3.icons;
|
|
|
|
import static android.graphics.Paint.DITHER_FLAG;
|
|
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
|
|
|
|
import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
|
|
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.res.Resources;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.PaintFlagsDrawFilter;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.graphics.drawable.AdaptiveIconDrawable;
|
|
import android.graphics.drawable.BitmapDrawable;
|
|
import android.graphics.drawable.ColorDrawable;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Build;
|
|
import android.os.Process;
|
|
import android.os.UserHandle;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
/**
|
|
* This class will be moved to androidx library. There shouldn't be any dependency outside
|
|
* this package.
|
|
*/
|
|
public class BaseIconFactory implements AutoCloseable {
|
|
|
|
private static final String TAG = "BaseIconFactory";
|
|
private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
|
|
static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
|
static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
|
|
|
|
private static final float ICON_BADGE_SCALE = 0.444f;
|
|
|
|
private final Rect mOldBounds = new Rect();
|
|
protected final Context mContext;
|
|
private final Canvas mCanvas;
|
|
private final PackageManager mPm;
|
|
private final ColorExtractor mColorExtractor;
|
|
private boolean mDisableColorExtractor;
|
|
|
|
protected final int mFillResIconDpi;
|
|
protected final int mIconBitmapSize;
|
|
|
|
private IconNormalizer mNormalizer;
|
|
private ShadowGenerator mShadowGenerator;
|
|
private final boolean mShapeDetection;
|
|
|
|
private Drawable mWrapperIcon;
|
|
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
|
|
|
|
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
|
|
boolean shapeDetection) {
|
|
mContext = context.getApplicationContext();
|
|
mShapeDetection = shapeDetection;
|
|
mFillResIconDpi = fillResIconDpi;
|
|
mIconBitmapSize = iconBitmapSize;
|
|
|
|
mPm = mContext.getPackageManager();
|
|
mColorExtractor = new ColorExtractor();
|
|
|
|
mCanvas = new Canvas();
|
|
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
|
|
clear();
|
|
}
|
|
|
|
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
|
|
this(context, fillResIconDpi, iconBitmapSize, false);
|
|
}
|
|
|
|
protected void clear() {
|
|
mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
|
|
mDisableColorExtractor = false;
|
|
}
|
|
|
|
public ShadowGenerator getShadowGenerator() {
|
|
if (mShadowGenerator == null) {
|
|
mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
|
|
}
|
|
return mShadowGenerator;
|
|
}
|
|
|
|
public IconNormalizer getNormalizer() {
|
|
if (mNormalizer == null) {
|
|
mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
|
|
}
|
|
return mNormalizer;
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
|
|
try {
|
|
Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
|
|
if (resources != null) {
|
|
final int id = resources.getIdentifier(iconRes.resourceName, null, null);
|
|
// do not stamp old legacy shortcuts as the app may have already forgotten about it
|
|
return createBadgedIconBitmap(
|
|
resources.getDrawableForDensity(id, mFillResIconDpi),
|
|
Process.myUserHandle() /* only available on primary user */,
|
|
false /* do not apply legacy treatment */);
|
|
}
|
|
} catch (Exception e) {
|
|
// Icon not found.
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public BitmapInfo createIconBitmap(Bitmap icon) {
|
|
if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) {
|
|
icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
|
|
}
|
|
|
|
return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor);
|
|
}
|
|
|
|
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
|
|
boolean shrinkNonAdaptiveIcons) {
|
|
return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
|
|
}
|
|
|
|
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
|
|
int iconAppTargetSdk) {
|
|
return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
|
|
}
|
|
|
|
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
|
|
int iconAppTargetSdk, boolean isInstantApp) {
|
|
return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
|
|
}
|
|
|
|
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
|
|
int iconAppTargetSdk, boolean isInstantApp, float[] scale) {
|
|
boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
|
|
(ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
|
|
return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);
|
|
}
|
|
|
|
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
|
|
boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
|
|
(ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
|
|
return createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);
|
|
}
|
|
|
|
/**
|
|
* Creates bitmap using the source drawable and various parameters.
|
|
* The bitmap is visually normalized with other icons and has enough spacing to add shadow.
|
|
*
|
|
* @param icon source of the icon
|
|
* @param user info can be used for a badge
|
|
* @param shrinkNonAdaptiveIcons {@code true} if non adaptive icons should be treated
|
|
* @param isInstantApp info can be used for a badge
|
|
* @param scale returns the scale result from normalization
|
|
* @return a bitmap suitable for disaplaying as an icon at various system UIs.
|
|
*/
|
|
public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon, UserHandle user,
|
|
boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
|
|
if (scale == null) {
|
|
scale = new float[1];
|
|
}
|
|
icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
|
|
Bitmap bitmap = createIconBitmap(icon, scale[0]);
|
|
if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
|
|
mCanvas.setBitmap(bitmap);
|
|
getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
|
|
mCanvas.setBitmap(null);
|
|
}
|
|
|
|
if (isInstantApp) {
|
|
badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
|
|
}
|
|
if (user != null) {
|
|
BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
|
|
Drawable badged = mPm.getUserBadgedIcon(drawable, user);
|
|
if (badged instanceof BitmapDrawable) {
|
|
bitmap = ((BitmapDrawable) badged).getBitmap();
|
|
} else {
|
|
bitmap = createIconBitmap(badged, 1f);
|
|
}
|
|
}
|
|
return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor);
|
|
}
|
|
|
|
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
|
|
RectF iconBounds = new RectF();
|
|
float[] scale = new float[1];
|
|
icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
|
|
return createIconBitmap(icon,
|
|
Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
|
|
}
|
|
|
|
/**
|
|
* Sets the background color used for wrapped adaptive icon
|
|
*/
|
|
public void setWrapperBackgroundColor(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;
|
|
}
|
|
|
|
private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
|
|
boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
|
|
if (icon == null) {
|
|
return null;
|
|
}
|
|
float scale = 1f;
|
|
|
|
if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
|
|
if (mWrapperIcon == null) {
|
|
mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
|
|
.mutate();
|
|
}
|
|
AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
|
|
dr.setBounds(0, 0, 1, 1);
|
|
boolean[] outShape = new boolean[1];
|
|
scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
|
|
if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
|
|
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
|
|
fsd.setDrawable(icon);
|
|
fsd.setScale(scale);
|
|
icon = dr;
|
|
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
|
|
|
|
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
|
|
}
|
|
} else {
|
|
scale = getNormalizer().getScale(icon, outIconBounds, null, null);
|
|
}
|
|
|
|
outScale[0] = scale;
|
|
return icon;
|
|
}
|
|
|
|
/**
|
|
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
|
|
*/
|
|
public void badgeWithDrawable(Bitmap target, Drawable badge) {
|
|
mCanvas.setBitmap(target);
|
|
badgeWithDrawable(mCanvas, badge);
|
|
mCanvas.setBitmap(null);
|
|
}
|
|
|
|
/**
|
|
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
|
|
*/
|
|
public void badgeWithDrawable(Canvas target, Drawable badge) {
|
|
int badgeSize = getBadgeSizeForIconSize(mIconBitmapSize);
|
|
badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
|
|
mIconBitmapSize, mIconBitmapSize);
|
|
badge.draw(target);
|
|
}
|
|
|
|
private Bitmap createIconBitmap(Drawable icon, float scale) {
|
|
return createIconBitmap(icon, scale, mIconBitmapSize);
|
|
}
|
|
|
|
/**
|
|
* @param icon drawable that should be flattened to a bitmap
|
|
* @param scale the scale to apply before drawing {@param icon} on the canvas
|
|
*/
|
|
public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
|
|
Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
|
|
if (icon == null) {
|
|
return bitmap;
|
|
}
|
|
mCanvas.setBitmap(bitmap);
|
|
mOldBounds.set(icon.getBounds());
|
|
|
|
if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
|
|
int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
|
|
Math.round(size * (1 - scale) / 2 ));
|
|
icon.setBounds(offset, offset, size - offset, size - offset);
|
|
icon.draw(mCanvas);
|
|
} else {
|
|
if (icon instanceof BitmapDrawable) {
|
|
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
|
|
Bitmap b = bitmapDrawable.getBitmap();
|
|
if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
|
|
bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
|
|
}
|
|
}
|
|
int width = size;
|
|
int height = size;
|
|
|
|
int intrinsicWidth = icon.getIntrinsicWidth();
|
|
int intrinsicHeight = icon.getIntrinsicHeight();
|
|
if (intrinsicWidth > 0 && intrinsicHeight > 0) {
|
|
// Scale the icon proportionally to the icon dimensions
|
|
final float ratio = (float) intrinsicWidth / intrinsicHeight;
|
|
if (intrinsicWidth > intrinsicHeight) {
|
|
height = (int) (width / ratio);
|
|
} else if (intrinsicHeight > intrinsicWidth) {
|
|
width = (int) (height * ratio);
|
|
}
|
|
}
|
|
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();
|
|
|
|
}
|
|
icon.setBounds(mOldBounds);
|
|
mCanvas.setBitmap(null);
|
|
return bitmap;
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
clear();
|
|
}
|
|
|
|
public BitmapInfo makeDefaultIcon(UserHandle user) {
|
|
return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi),
|
|
user, Build.VERSION.SDK_INT);
|
|
}
|
|
|
|
public static Drawable getFullResDefaultActivityIcon(int iconDpi) {
|
|
return Resources.getSystem().getDrawableForDensity(
|
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
|
? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
|
|
iconDpi);
|
|
}
|
|
|
|
/**
|
|
* Returns the correct badge size given an icon size
|
|
*/
|
|
public static int getBadgeSizeForIconSize(int iconSize) {
|
|
return (int) (ICON_BADGE_SCALE * iconSize);
|
|
}
|
|
|
|
/**
|
|
* An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
|
|
* This allows the badging to be done based on the action bitmap size rather than
|
|
* the scaled bitmap size.
|
|
*/
|
|
private static class FixedSizeBitmapDrawable extends BitmapDrawable {
|
|
|
|
public FixedSizeBitmapDrawable(Bitmap bitmap) {
|
|
super(null, bitmap);
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicHeight() {
|
|
return getBitmap().getWidth();
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicWidth() {
|
|
return getBitmap().getWidth();
|
|
}
|
|
}
|
|
}
|