mirror of
https://github.com/LawnchairLauncher/lawnchair.git
synced 2026-03-03 01:16:49 +00:00
436 lines
16 KiB
Java
436 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2016 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.launcher3.graphics;
|
|
|
|
import static android.graphics.Paint.DITHER_FLAG;
|
|
import static android.graphics.Paint.FILTER_BITMAP_FLAG;
|
|
|
|
import static com.android.launcher3.graphics.ShadowGenerator.BLUR_FACTOR;
|
|
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.Intent.ShortcutIconResource;
|
|
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.graphics.drawable.PaintDrawable;
|
|
import android.os.Build;
|
|
import android.os.Process;
|
|
import android.os.UserHandle;
|
|
import android.support.annotation.Nullable;
|
|
|
|
import com.android.launcher3.AppInfo;
|
|
import com.android.launcher3.FastBitmapDrawable;
|
|
import com.android.launcher3.IconCache;
|
|
import com.android.launcher3.InvariantDeviceProfile;
|
|
import com.android.launcher3.ItemInfoWithIcon;
|
|
import com.android.launcher3.LauncherAppState;
|
|
import com.android.launcher3.R;
|
|
import com.android.launcher3.Utilities;
|
|
import com.android.launcher3.model.PackageItemInfo;
|
|
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
|
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
|
|
import com.android.launcher3.util.Provider;
|
|
import com.android.launcher3.util.Themes;
|
|
|
|
/**
|
|
* Helper methods for generating various launcher icons
|
|
*/
|
|
public class LauncherIcons implements AutoCloseable {
|
|
|
|
private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
|
|
|
|
public static final Object sPoolSync = new Object();
|
|
private static LauncherIcons sPool;
|
|
|
|
/**
|
|
* Return a new Message instance from the global pool. Allows us to
|
|
* avoid allocating new objects in many cases.
|
|
*/
|
|
public static LauncherIcons obtain(Context context) {
|
|
synchronized (sPoolSync) {
|
|
if (sPool != null) {
|
|
LauncherIcons m = sPool;
|
|
sPool = m.next;
|
|
m.next = null;
|
|
return m;
|
|
}
|
|
}
|
|
return new LauncherIcons(context);
|
|
}
|
|
|
|
/**
|
|
* Recycles a LauncherIcons that may be in-use.
|
|
*/
|
|
public void recycle() {
|
|
synchronized (sPoolSync) {
|
|
// Clear any temporary state variables
|
|
mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
|
|
|
|
next = sPool;
|
|
sPool = this;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
recycle();
|
|
}
|
|
|
|
private final Rect mOldBounds = new Rect();
|
|
private final Context mContext;
|
|
private final Canvas mCanvas;
|
|
private final PackageManager mPm;
|
|
|
|
private final int mFillResIconDpi;
|
|
private final int mIconBitmapSize;
|
|
|
|
private IconNormalizer mNormalizer;
|
|
private ShadowGenerator mShadowGenerator;
|
|
|
|
private Drawable mWrapperIcon;
|
|
private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
|
|
|
|
// sometimes we store linked lists of these things
|
|
private LauncherIcons next;
|
|
|
|
private LauncherIcons(Context context) {
|
|
mContext = context.getApplicationContext();
|
|
mPm = mContext.getPackageManager();
|
|
|
|
InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
|
|
mFillResIconDpi = idp.fillResIconDpi;
|
|
mIconBitmapSize = idp.iconBitmapSize;
|
|
|
|
mCanvas = new Canvas();
|
|
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
|
|
}
|
|
|
|
public ShadowGenerator getShadowGenerator() {
|
|
if (mShadowGenerator == null) {
|
|
mShadowGenerator = new ShadowGenerator(mContext);
|
|
}
|
|
return mShadowGenerator;
|
|
}
|
|
|
|
public IconNormalizer getNormalizer() {
|
|
if (mNormalizer == null) {
|
|
mNormalizer = new IconNormalizer(mContext);
|
|
}
|
|
return mNormalizer;
|
|
}
|
|
|
|
/**
|
|
* Returns a bitmap suitable for the all apps view. If the package or the resource do not
|
|
* exist, it returns null.
|
|
*/
|
|
public BitmapInfo createIconBitmap(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 */,
|
|
0 /* do not apply legacy treatment */);
|
|
}
|
|
} catch (Exception e) {
|
|
// Icon not found.
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns a bitmap which is of the appropriate size to be displayed as an icon
|
|
*/
|
|
public BitmapInfo createIconBitmap(Bitmap icon) {
|
|
if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
|
|
return BitmapInfo.fromBitmap(icon);
|
|
}
|
|
return BitmapInfo.fromBitmap(
|
|
createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
|
|
}
|
|
|
|
/**
|
|
* Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
|
|
* view or workspace. The icon is badged for {@param user}.
|
|
* The bitmap is also visually normalized with other icons.
|
|
*/
|
|
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
|
|
return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
|
|
}
|
|
|
|
/**
|
|
* Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
|
|
* view or workspace. The icon is badged for {@param user}.
|
|
* The bitmap is also visually normalized with other icons.
|
|
*/
|
|
public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
|
|
boolean isInstantApp) {
|
|
float[] scale = new float[1];
|
|
icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
|
|
Bitmap bitmap = createIconBitmap(icon, scale[0]);
|
|
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
|
|
mCanvas.setBitmap(bitmap);
|
|
getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
|
|
mCanvas.setBitmap(null);
|
|
}
|
|
|
|
final Bitmap result;
|
|
if (user != null && !Process.myUserHandle().equals(user)) {
|
|
BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
|
|
Drawable badged = mPm.getUserBadgedIcon(drawable, user);
|
|
if (badged instanceof BitmapDrawable) {
|
|
result = ((BitmapDrawable) badged).getBitmap();
|
|
} else {
|
|
result = createIconBitmap(badged, 1f);
|
|
}
|
|
} else if (isInstantApp) {
|
|
badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
|
|
result = bitmap;
|
|
} else {
|
|
result = bitmap;
|
|
}
|
|
return BitmapInfo.fromBitmap(result);
|
|
}
|
|
|
|
/**
|
|
* Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
|
|
* normalized with other icons and has enough spacing to add shadow.
|
|
*/
|
|
public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
|
|
RectF iconBounds = new RectF();
|
|
float[] scale = new float[1];
|
|
icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, 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;
|
|
}
|
|
|
|
private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
|
|
RectF outIconBounds, float[] outScale) {
|
|
float scale = 1f;
|
|
if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
|
|
boolean[] outShape = new boolean[1];
|
|
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, dr.getIconMask(), outShape);
|
|
if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
|
|
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.
|
|
*/
|
|
private 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);
|
|
}
|
|
|
|
/**
|
|
* @param scale the scale to apply before drawing {@param icon} on the canvas
|
|
*/
|
|
private Bitmap createIconBitmap(Drawable icon, float scale) {
|
|
int width = mIconBitmapSize;
|
|
int height = mIconBitmapSize;
|
|
|
|
if (icon instanceof PaintDrawable) {
|
|
PaintDrawable painter = (PaintDrawable) icon;
|
|
painter.setIntrinsicWidth(width);
|
|
painter.setIntrinsicHeight(height);
|
|
} else if (icon instanceof BitmapDrawable) {
|
|
// Ensure the bitmap has a density.
|
|
BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
|
|
Bitmap bitmap = bitmapDrawable.getBitmap();
|
|
if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
|
|
bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
|
|
}
|
|
}
|
|
|
|
int sourceWidth = icon.getIntrinsicWidth();
|
|
int sourceHeight = icon.getIntrinsicHeight();
|
|
if (sourceWidth > 0 && sourceHeight > 0) {
|
|
// Scale the icon proportionally to the icon dimensions
|
|
final float ratio = (float) sourceWidth / sourceHeight;
|
|
if (sourceWidth > sourceHeight) {
|
|
height = (int) (width / ratio);
|
|
} else if (sourceHeight > sourceWidth) {
|
|
width = (int) (height * ratio);
|
|
}
|
|
}
|
|
// no intrinsic size --> use default size
|
|
int textureWidth = mIconBitmapSize;
|
|
int textureHeight = mIconBitmapSize;
|
|
|
|
Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
|
|
Bitmap.Config.ARGB_8888);
|
|
mCanvas.setBitmap(bitmap);
|
|
|
|
final int left = (textureWidth-width) / 2;
|
|
final int top = (textureHeight-height) / 2;
|
|
|
|
mOldBounds.set(icon.getBounds());
|
|
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
|
|
int offset = Math.max((int)(BLUR_FACTOR * textureWidth), Math.min(left, top));
|
|
int size = Math.max(width, height);
|
|
icon.setBounds(offset, offset, size, size);
|
|
} else {
|
|
icon.setBounds(left, top, left+width, top+height);
|
|
}
|
|
mCanvas.save();
|
|
mCanvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
|
|
icon.draw(mCanvas);
|
|
mCanvas.restore();
|
|
icon.setBounds(mOldBounds);
|
|
mCanvas.setBitmap(null);
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo) {
|
|
return createShortcutIcon(shortcutInfo, true /* badged */);
|
|
}
|
|
|
|
public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, boolean badged) {
|
|
return createShortcutIcon(shortcutInfo, badged, null);
|
|
}
|
|
|
|
public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo,
|
|
boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
|
|
Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
|
|
.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
|
|
IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
|
|
|
|
Bitmap unbadgedBitmap = null;
|
|
if (unbadgedDrawable != null) {
|
|
unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
|
|
} else {
|
|
if (fallbackIconProvider != null) {
|
|
unbadgedBitmap = fallbackIconProvider.get();
|
|
}
|
|
if (unbadgedBitmap == null) {
|
|
unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
|
|
}
|
|
}
|
|
|
|
BitmapInfo result = new BitmapInfo();
|
|
if (!badged) {
|
|
result.color = Themes.getColorAccent(mContext);
|
|
result.icon = unbadgedBitmap;
|
|
return result;
|
|
}
|
|
|
|
final Bitmap unbadgedfinal = unbadgedBitmap;
|
|
final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
|
|
|
|
result.color = badge.iconColor;
|
|
result.icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
|
|
getShadowGenerator().recreateIcon(unbadgedfinal, c);
|
|
badgeWithDrawable(c, new FastBitmapDrawable(badge));
|
|
});
|
|
return result;
|
|
}
|
|
|
|
public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) {
|
|
ComponentName cn = shortcutInfo.getActivity();
|
|
String badgePkg = shortcutInfo.getBadgePackage(mContext);
|
|
boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage());
|
|
if (cn != null && !hasBadgePkgSet) {
|
|
// Get the app info for the source activity.
|
|
AppInfo appInfo = new AppInfo();
|
|
appInfo.user = shortcutInfo.getUserHandle();
|
|
appInfo.componentName = cn;
|
|
appInfo.intent = new Intent(Intent.ACTION_MAIN)
|
|
.addCategory(Intent.CATEGORY_LAUNCHER)
|
|
.setComponent(cn);
|
|
cache.getTitleAndIcon(appInfo, false);
|
|
return appInfo;
|
|
} else {
|
|
PackageItemInfo pkgInfo = new PackageItemInfo(badgePkg);
|
|
cache.getTitleAndIconForApp(pkgInfo, false);
|
|
return pkgInfo;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
}
|