2 * Copyright (C) 2016 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.launcher3.graphics;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.Intent.ShortcutIconResource;
23 import android.content.pm.PackageManager;
24 import android.content.res.Resources;
25 import android.graphics.Bitmap;
26 import android.graphics.Canvas;
27 import android.graphics.Paint;
28 import android.graphics.PaintFlagsDrawFilter;
29 import android.graphics.Rect;
30 import android.graphics.RectF;
31 import android.graphics.drawable.AdaptiveIconDrawable;
32 import android.graphics.drawable.BitmapDrawable;
33 import android.graphics.drawable.Drawable;
34 import android.graphics.drawable.PaintDrawable;
35 import android.os.Build;
36 import android.os.Process;
37 import android.os.UserHandle;
39 import com.android.launcher3.AppInfo;
40 import com.android.launcher3.IconCache;
41 import com.android.launcher3.LauncherAppState;
42 import com.android.launcher3.R;
43 import com.android.launcher3.Utilities;
44 import com.android.launcher3.config.FeatureFlags;
45 import com.android.launcher3.model.PackageItemInfo;
46 import com.android.launcher3.shortcuts.DeepShortcutManager;
47 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
50 * Helper methods for generating various launcher icons
52 public class LauncherIcons {
54 private static final Rect sOldBounds = new Rect();
55 private static final Canvas sCanvas = new Canvas();
58 sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
59 Paint.FILTER_BITMAP_FLAG));
63 * Returns a bitmap suitable for the all apps view. If the package or the resource do not
64 * exist, it returns null.
66 public static Bitmap createIconBitmap(ShortcutIconResource iconRes, Context context) {
67 PackageManager packageManager = context.getPackageManager();
70 Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);
71 if (resources != null) {
72 final int id = resources.getIdentifier(iconRes.resourceName, null, null);
73 return createIconBitmap(resources.getDrawableForDensity(
74 id, LauncherAppState.getIDP(context).fillResIconDpi), context);
76 } catch (Exception e) {
83 * Returns a bitmap which is of the appropriate size to be displayed as an icon
85 public static Bitmap createIconBitmap(Bitmap icon, Context context) {
86 final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
87 if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
90 return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
94 * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
95 * The bitmap is also visually normalized with other icons.
97 public static Bitmap createBadgedIconBitmap(
98 Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
100 IconNormalizer normalizer;
102 if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
103 normalizer = IconNormalizer.getInstance(context);
104 if (Utilities.isAtLeastO() && iconAppTargetSdk >= Build.VERSION_CODES.O) {
105 boolean[] outShape = new boolean[1];
106 AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
107 context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
108 dr.setBounds(0, 0, 1, 1);
109 scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
110 if (FeatureFlags.LEGACY_ICON_TREATMENT &&
112 Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
113 if (wrappedIcon != icon) {
115 scale = normalizer.getScale(icon, null, null, null);
119 scale = normalizer.getScale(icon, null, null, null);
122 Bitmap bitmap = createIconBitmap(icon, context, scale);
123 if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
124 icon instanceof AdaptiveIconDrawable) {
125 bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
127 return badgeIconForUser(bitmap, user, context);
131 * Badges the provided icon with the user badge if required.
133 public static Bitmap badgeIconForUser(Bitmap icon, UserHandle user, Context context) {
134 if (user != null && !Process.myUserHandle().equals(user)) {
135 BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
136 Drawable badged = context.getPackageManager().getUserBadgedIcon(
138 if (badged instanceof BitmapDrawable) {
139 return ((BitmapDrawable) badged).getBitmap();
141 return createIconBitmap(badged, context);
149 * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
150 * normalized with other icons and has enough spacing to add shadow.
152 public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk) {
153 RectF iconBounds = new RectF();
154 IconNormalizer normalizer;
156 if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
157 normalizer = IconNormalizer.getInstance(context);
158 if (Utilities.isAtLeastO() && iconAppTargetSdk >= Build.VERSION_CODES.O) {
159 boolean[] outShape = new boolean[1];
160 AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
161 context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
162 dr.setBounds(0, 0, 1, 1);
163 scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
164 if (Utilities.isAtLeastO() && FeatureFlags.LEGACY_ICON_TREATMENT &&
166 Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
167 if (wrappedIcon != icon) {
169 scale = normalizer.getScale(icon, iconBounds, null, null);
173 scale = normalizer.getScale(icon, iconBounds, null, null);
177 scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
178 return createIconBitmap(icon, context, scale);
182 * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
183 * {@link #createScaledBitmapWithoutShadow(Drawable, Context, int)}
185 public static Bitmap addShadowToIcon(Bitmap icon, Context context) {
186 return ShadowGenerator.getInstance(context).recreateIcon(icon);
190 * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
192 public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
193 int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
194 synchronized (sCanvas) {
195 sCanvas.setBitmap(srcTgt);
196 sCanvas.drawBitmap(badge, new Rect(0, 0, badge.getWidth(), badge.getHeight()),
197 new Rect(srcTgt.getWidth() - badgeSize,
198 srcTgt.getHeight() - badgeSize, srcTgt.getWidth(), srcTgt.getHeight()),
199 new Paint(Paint.FILTER_BITMAP_FLAG));
200 sCanvas.setBitmap(null);
206 * Returns a bitmap suitable for the all apps view.
208 public static Bitmap createIconBitmap(Drawable icon, Context context) {
210 if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
211 icon instanceof AdaptiveIconDrawable) {
212 scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0));
214 Bitmap bitmap = createIconBitmap(icon, context, scale);
215 if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
216 icon instanceof AdaptiveIconDrawable) {
217 bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
223 * @param scale the scale to apply before drawing {@param icon} on the canvas
225 public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
226 synchronized (sCanvas) {
227 final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
228 int width = iconBitmapSize;
229 int height = iconBitmapSize;
231 if (icon instanceof PaintDrawable) {
232 PaintDrawable painter = (PaintDrawable) icon;
233 painter.setIntrinsicWidth(width);
234 painter.setIntrinsicHeight(height);
235 } else if (icon instanceof BitmapDrawable) {
236 // Ensure the bitmap has a density.
237 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
238 Bitmap bitmap = bitmapDrawable.getBitmap();
239 if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
240 bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
244 int sourceWidth = icon.getIntrinsicWidth();
245 int sourceHeight = icon.getIntrinsicHeight();
246 if (sourceWidth > 0 && sourceHeight > 0) {
247 // Scale the icon proportionally to the icon dimensions
248 final float ratio = (float) sourceWidth / sourceHeight;
249 if (sourceWidth > sourceHeight) {
250 height = (int) (width / ratio);
251 } else if (sourceHeight > sourceWidth) {
252 width = (int) (height * ratio);
255 // no intrinsic size --> use default size
256 int textureWidth = iconBitmapSize;
257 int textureHeight = iconBitmapSize;
259 Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
260 Bitmap.Config.ARGB_8888);
261 final Canvas canvas = sCanvas;
262 canvas.setBitmap(bitmap);
264 final int left = (textureWidth-width) / 2;
265 final int top = (textureHeight-height) / 2;
267 sOldBounds.set(icon.getBounds());
268 if (icon instanceof AdaptiveIconDrawable) {
269 int offset = Math.min(left, top);
270 int size = Math.max(width, height);
271 icon.setBounds(offset, offset, offset + size, offset + size);
273 icon.setBounds(left, top, left+width, top+height);
275 canvas.save(Canvas.MATRIX_SAVE_FLAG);
276 canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
279 icon.setBounds(sOldBounds);
280 canvas.setBitmap(null);
287 * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
288 * shrink the legacy icon and set it as foreground. Use color drawable as background to
289 * create AdaptiveIconDrawable.
291 static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) {
292 if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) {
297 if (!(drawable instanceof AdaptiveIconDrawable)) {
298 AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
299 context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
300 FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
301 fsd.setDrawable(drawable);
303 return (Drawable) iconWrapper;
305 } catch (Exception e) {
311 public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
312 return createShortcutIcon(shortcutInfo, context, true /* badged */);
315 public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
317 LauncherAppState app = LauncherAppState.getInstance(context);
318 Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
319 .getShortcutIconDrawable(shortcutInfo,
320 app.getInvariantDeviceProfile().fillResIconDpi);
321 IconCache cache = app.getIconCache();
322 Bitmap unbadgedBitmap = unbadgedDrawable == null
323 ? cache.getDefaultIcon(Process.myUserHandle())
324 : LauncherIcons.createScaledBitmapWithoutShadow(unbadgedDrawable, context,
325 Build.VERSION_CODES.O);
328 return unbadgedBitmap;
330 unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context);
332 final Bitmap badgeBitmap;
333 ComponentName cn = shortcutInfo.getActivity();
335 // Get the app info for the source activity.
336 AppInfo appInfo = new AppInfo();
337 appInfo.user = shortcutInfo.getUserHandle();
338 appInfo.componentName = cn;
339 appInfo.intent = new Intent(Intent.ACTION_MAIN)
340 .addCategory(Intent.CATEGORY_LAUNCHER)
342 cache.getTitleAndIcon(appInfo, false);
343 badgeBitmap = appInfo.iconBitmap;
345 PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
346 cache.getTitleAndIconForApp(pkgInfo, false);
347 badgeBitmap = pkgInfo.iconBitmap;
349 return badgeWithBitmap(unbadgedBitmap, badgeBitmap, context);
353 * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
354 * This allows the badging to be done based on the action bitmap size rather than
355 * the scaled bitmap size.
357 private static class FixedSizeBitmapDrawable extends BitmapDrawable {
359 public FixedSizeBitmapDrawable(Bitmap bitmap) {
364 public int getIntrinsicHeight() {
365 return getBitmap().getWidth();
369 public int getIntrinsicWidth() {
370 return getBitmap().getWidth();