b0be237b3f
The current logic in BaseIconFactory is only trying to add the user badge if the user passed in isn't null and isn't the current user, then if no such attempt is made, try to add an instant app badge. This is causing user badge to disappear if the current user is a work profile. Meanwhile, the badging logic in IconDrawableFactory is to try to add the instant app badge first, and then always try to add the user badge. Bug: 132625654 Test: manually test as in b/128625591 Change-Id: Ia5b456f1f27095d546504266a1a90034105c9ae9
345 lines
13 KiB
Java
345 lines
13 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;
|
|
|
|
/**
|
|
* 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 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 Drawable mWrapperIcon;
|
|
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
|
|
|
|
protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
|
|
mContext = context.getApplicationContext();
|
|
|
|
mFillResIconDpi = fillResIconDpi;
|
|
mIconBitmapSize = iconBitmapSize;
|
|
|
|
mPm = mContext.getPackageManager();
|
|
mColorExtractor = new ColorExtractor();
|
|
|
|
mCanvas = new Canvas();
|
|
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
|
|
clear();
|
|
}
|
|
|
|
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(mIconBitmapSize);
|
|
}
|
|
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(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(Drawable icon, boolean shrinkNonAdaptiveIcons,
|
|
RectF outIconBounds, float[] outScale) {
|
|
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);
|
|
scale = getNormalizer().getScale(icon, outIconBounds);
|
|
if (!(icon instanceof AdaptiveIconDrawable)) {
|
|
FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
|
|
fsd.setDrawable(icon);
|
|
fsd.setScale(scale);
|
|
icon = dr;
|
|
scale = getNormalizer().getScale(icon, outIconBounds);
|
|
|
|
((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
|
|
}
|
|
} else {
|
|
scale = getNormalizer().getScale(icon, outIconBounds);
|
|
}
|
|
|
|
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 = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
|
|
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(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);
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
}
|