blob: fdb631397fbfa5c50050ce3282c73c5fce088362 [file] [log] [blame]
/*
* 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 android.annotation.TargetApi;
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.Paint;
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.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.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
import com.android.launcher3.uioverrides.UiFactory;
import com.android.launcher3.util.Provider;
import com.android.launcher3.util.Themes;
/**
* Helper methods for generating various launcher icons
*/
public class LauncherIcons {
private static final Rect sOldBounds = new Rect();
private static final Canvas sCanvas = new Canvas();
static {
sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
Paint.FILTER_BITMAP_FLAG));
}
/**
* Returns a bitmap suitable for the all apps view. If the package or the resource do not
* exist, it returns null.
*/
public static BitmapInfo createIconBitmap(ShortcutIconResource iconRes, Context context) {
PackageManager packageManager = context.getPackageManager();
// the resource
try {
Resources resources = packageManager.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, LauncherAppState.getIDP(context).fillResIconDpi),
Process.myUserHandle() /* only available on primary user */,
context,
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 static BitmapInfo createIconBitmap(Bitmap icon, Context context) {
final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
return BitmapInfo.fromBitmap(icon);
}
return BitmapInfo.fromBitmap(
createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 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 static BitmapInfo createBadgedIconBitmap(
Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
IconNormalizer normalizer;
float scale = 1f;
if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
normalizer = IconNormalizer.getInstance(context);
if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
boolean[] outShape = new boolean[1];
AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
dr.setBounds(0, 0, 1, 1);
scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
if (FeatureFlags.LEGACY_ICON_TREATMENT &&
!outShape[0]){
Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
if (wrappedIcon != icon) {
icon = wrappedIcon;
scale = normalizer.getScale(icon, null, null, null);
}
}
} else {
scale = normalizer.getScale(icon, null, null, null);
}
}
Bitmap bitmap = createIconBitmap(icon, context, scale);
if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
icon instanceof AdaptiveIconDrawable) {
synchronized (sCanvas) {
sCanvas.setBitmap(bitmap);
ShadowGenerator.getInstance(context).recreateIcon(
Bitmap.createBitmap(bitmap), sCanvas);
sCanvas.setBitmap(null);
}
}
final Bitmap result;
if (user != null && !Process.myUserHandle().equals(user)) {
BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
Drawable badged = context.getPackageManager().getUserBadgedIcon(
drawable, user);
if (badged instanceof BitmapDrawable) {
result = ((BitmapDrawable) badged).getBitmap();
} else {
result = createIconBitmap(badged, context, 1f);
}
} 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 static Bitmap createScaledBitmapWithoutShadow(
Drawable icon, Context context, int iconAppTargetSdk) {
RectF iconBounds = new RectF();
IconNormalizer normalizer;
float scale = 1f;
if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
normalizer = IconNormalizer.getInstance(context);
if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
boolean[] outShape = new boolean[1];
AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
dr.setBounds(0, 0, 1, 1);
scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
if (Utilities.ATLEAST_OREO && FeatureFlags.LEGACY_ICON_TREATMENT &&
!outShape[0]) {
Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
if (wrappedIcon != icon) {
icon = wrappedIcon;
scale = normalizer.getScale(icon, iconBounds, null, null);
}
}
} else {
scale = normalizer.getScale(icon, iconBounds, null, null);
}
}
scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
return createIconBitmap(icon, context, scale);
}
/**
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
public static void badgeWithDrawable(Bitmap target, Drawable badge, Context context) {
synchronized (sCanvas) {
sCanvas.setBitmap(target);
badgeWithDrawable(sCanvas, badge, context);
sCanvas.setBitmap(null);
}
}
/**
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
private static void badgeWithDrawable(Canvas target, Drawable badge, Context context) {
int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
int iconSize = LauncherAppState.getIDP(context).iconBitmapSize;
badge.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
badge.draw(target);
}
/**
* @param scale the scale to apply before drawing {@param icon} on the canvas
*/
private static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
synchronized (sCanvas) {
final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
int width = iconBitmapSize;
int height = iconBitmapSize;
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(context.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 = iconBitmapSize;
int textureHeight = iconBitmapSize;
Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
Bitmap.Config.ARGB_8888);
final Canvas canvas = sCanvas;
canvas.setBitmap(bitmap);
final int left = (textureWidth-width) / 2;
final int top = (textureHeight-height) / 2;
sOldBounds.set(icon.getBounds());
if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
int offset = Math.max((int)(ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
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);
}
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
icon.draw(canvas);
canvas.restore();
icon.setBounds(sOldBounds);
canvas.setBitmap(null);
return bitmap;
}
}
/**
* If the platform is running O but the app is not providing AdaptiveIconDrawable, then
* shrink the legacy icon and set it as foreground. Use color drawable as background to
* create AdaptiveIconDrawable.
*/
@TargetApi(Build.VERSION_CODES.O)
private static Drawable wrapToAdaptiveIconDrawable(
Context context, Drawable drawable, float scale) {
if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) {
return drawable;
}
try {
if (!(drawable instanceof AdaptiveIconDrawable)) {
AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
fsd.setDrawable(drawable);
fsd.setScale(scale);
return iconWrapper;
}
} catch (Exception e) {
return drawable;
}
return drawable;
}
public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
return createShortcutIcon(shortcutInfo, context, true /* badged */);
}
public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
boolean badged) {
return createShortcutIcon(shortcutInfo, context, badged, null);
}
public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
LauncherAppState app = LauncherAppState.getInstance(context);
Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
.getShortcutIconDrawable(shortcutInfo,
app.getInvariantDeviceProfile().fillResIconDpi);
IconCache cache = app.getIconCache();
Bitmap unbadgedBitmap = null;
if (unbadgedDrawable != null) {
unbadgedBitmap = LauncherIcons.createScaledBitmapWithoutShadow(
unbadgedDrawable, context, 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(context);
result.icon = unbadgedBitmap;
return result;
}
int size = app.getInvariantDeviceProfile().iconBitmapSize;
final Bitmap unbadgedfinal = unbadgedBitmap;
final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
result.color = badge.iconColor;
result.icon = UiFactory.createFromRenderer(size, size, false, (c) -> {
ShadowGenerator.getInstance(context).recreateIcon(unbadgedfinal, c);
badgeWithDrawable(c, new FastBitmapDrawable(badge), context);
});
return result;
}
public static ItemInfoWithIcon getShortcutInfoBadge(
ShortcutInfoCompat shortcutInfo, IconCache cache) {
ComponentName cn = shortcutInfo.getActivity();
if (cn != null) {
// 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(shortcutInfo.getPackage());
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();
}
}
}