• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package com.android.launcher3.graphics;
18 
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;
38 import android.support.annotation.Nullable;
39 
40 import com.android.launcher3.AppInfo;
41 import com.android.launcher3.FastBitmapDrawable;
42 import com.android.launcher3.IconCache;
43 import com.android.launcher3.LauncherAppState;
44 import com.android.launcher3.R;
45 import com.android.launcher3.Utilities;
46 import com.android.launcher3.config.FeatureFlags;
47 import com.android.launcher3.model.PackageItemInfo;
48 import com.android.launcher3.shortcuts.DeepShortcutManager;
49 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
50 import com.android.launcher3.util.Provider;
51 
52 /**
53  * Helper methods for generating various launcher icons
54  */
55 public class LauncherIcons {
56 
57     private static final Rect sOldBounds = new Rect();
58     private static final Canvas sCanvas = new Canvas();
59 
60     static {
sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG))61         sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
62                 Paint.FILTER_BITMAP_FLAG));
63     }
64 
65     /**
66      * Returns a bitmap suitable for the all apps view. If the package or the resource do not
67      * exist, it returns null.
68      */
createIconBitmap(ShortcutIconResource iconRes, Context context)69     public static Bitmap createIconBitmap(ShortcutIconResource iconRes, Context context) {
70         PackageManager packageManager = context.getPackageManager();
71         // the resource
72         try {
73             Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);
74             if (resources != null) {
75                 final int id = resources.getIdentifier(iconRes.resourceName, null, null);
76                 return createIconBitmap(resources.getDrawableForDensity(
77                         id, LauncherAppState.getIDP(context).fillResIconDpi), context);
78             }
79         } catch (Exception e) {
80             // Icon not found.
81         }
82         return null;
83     }
84 
85     /**
86      * Returns a bitmap which is of the appropriate size to be displayed as an icon
87      */
createIconBitmap(Bitmap icon, Context context)88     public static Bitmap createIconBitmap(Bitmap icon, Context context) {
89         final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
90         if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
91             return icon;
92         }
93         return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
94     }
95 
96     /**
97      * Returns a bitmap suitable for the all apps view. The icon is badged for {@param user}.
98      * The bitmap is also visually normalized with other icons.
99      */
createBadgedIconBitmap( Drawable icon, UserHandle user, Context context, int iconAppTargetSdk)100     public static Bitmap createBadgedIconBitmap(
101             Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
102 
103         IconNormalizer normalizer;
104         float scale = 1f;
105         if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
106             normalizer = IconNormalizer.getInstance(context);
107             if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
108                 boolean[] outShape = new boolean[1];
109                 AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
110                         context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
111                 dr.setBounds(0, 0, 1, 1);
112                 scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
113                 if (FeatureFlags.LEGACY_ICON_TREATMENT &&
114                         !outShape[0]){
115                     Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
116                     if (wrappedIcon != icon) {
117                         icon = wrappedIcon;
118                         scale = normalizer.getScale(icon, null, null, null);
119                     }
120                 }
121             } else {
122                 scale = normalizer.getScale(icon, null, null, null);
123             }
124         }
125         Bitmap bitmap = createIconBitmap(icon, context, scale);
126         if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
127                 icon instanceof AdaptiveIconDrawable) {
128             bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
129         }
130         return badgeIconForUser(bitmap, user, context);
131     }
132 
133     /**
134      * Badges the provided icon with the user badge if required.
135      */
badgeIconForUser(Bitmap icon, UserHandle user, Context context)136     public static Bitmap badgeIconForUser(Bitmap icon, UserHandle user, Context context) {
137         if (user != null && !Process.myUserHandle().equals(user)) {
138             BitmapDrawable drawable = new FixedSizeBitmapDrawable(icon);
139             Drawable badged = context.getPackageManager().getUserBadgedIcon(
140                     drawable, user);
141             if (badged instanceof BitmapDrawable) {
142                 return ((BitmapDrawable) badged).getBitmap();
143             } else {
144                 return createIconBitmap(badged, context);
145             }
146         } else {
147             return icon;
148         }
149     }
150 
151     /**
152      * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
153      * normalized with other icons and has enough spacing to add shadow.
154      */
createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk)155     public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk) {
156         RectF iconBounds = new RectF();
157         IconNormalizer normalizer;
158         float scale = 1f;
159         if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
160             normalizer = IconNormalizer.getInstance(context);
161             if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
162                 boolean[] outShape = new boolean[1];
163                 AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
164                         context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
165                 dr.setBounds(0, 0, 1, 1);
166                 scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
167                 if (Utilities.ATLEAST_OREO && FeatureFlags.LEGACY_ICON_TREATMENT &&
168                         !outShape[0]) {
169                     Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
170                     if (wrappedIcon != icon) {
171                         icon = wrappedIcon;
172                         scale = normalizer.getScale(icon, iconBounds, null, null);
173                     }
174                 }
175             } else {
176                 scale = normalizer.getScale(icon, iconBounds, null, null);
177             }
178 
179         }
180         scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
181         return createIconBitmap(icon, context, scale);
182     }
183 
184     /**
185      * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
186      * {@link #createScaledBitmapWithoutShadow(Drawable, Context, int)}
187      */
addShadowToIcon(Bitmap icon, Context context)188     public static Bitmap addShadowToIcon(Bitmap icon, Context context) {
189         return ShadowGenerator.getInstance(context).recreateIcon(icon);
190     }
191 
192     /**
193      * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
194      */
badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context)195     public static Bitmap badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context) {
196         return badgeWithDrawable(srcTgt, new FastBitmapDrawable(badge), context);
197     }
198 
badgeWithDrawable(Bitmap srcTgt, Drawable badge, Context context)199     public static Bitmap badgeWithDrawable(Bitmap srcTgt, Drawable badge, Context context) {
200         int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
201         synchronized (sCanvas) {
202             sCanvas.setBitmap(srcTgt);
203             int iconSize = srcTgt.getWidth();
204             badge.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
205             badge.draw(sCanvas);
206             sCanvas.setBitmap(null);
207         }
208         return srcTgt;
209     }
210 
211     /**
212      * Returns a bitmap suitable for the all apps view.
213      */
createIconBitmap(Drawable icon, Context context)214     public static Bitmap createIconBitmap(Drawable icon, Context context) {
215         float scale = 1f;
216         if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
217                 icon instanceof AdaptiveIconDrawable) {
218             scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0));
219         }
220         Bitmap bitmap =  createIconBitmap(icon, context, scale);
221         if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
222                 icon instanceof AdaptiveIconDrawable) {
223             bitmap = ShadowGenerator.getInstance(context).recreateIcon(bitmap);
224         }
225         return bitmap;
226     }
227 
228     /**
229      * @param scale the scale to apply before drawing {@param icon} on the canvas
230      */
createIconBitmap(Drawable icon, Context context, float scale)231     public static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
232         synchronized (sCanvas) {
233             final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
234             int width = iconBitmapSize;
235             int height = iconBitmapSize;
236 
237             if (icon instanceof PaintDrawable) {
238                 PaintDrawable painter = (PaintDrawable) icon;
239                 painter.setIntrinsicWidth(width);
240                 painter.setIntrinsicHeight(height);
241             } else if (icon instanceof BitmapDrawable) {
242                 // Ensure the bitmap has a density.
243                 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
244                 Bitmap bitmap = bitmapDrawable.getBitmap();
245                 if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
246                     bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
247                 }
248             }
249 
250             int sourceWidth = icon.getIntrinsicWidth();
251             int sourceHeight = icon.getIntrinsicHeight();
252             if (sourceWidth > 0 && sourceHeight > 0) {
253                 // Scale the icon proportionally to the icon dimensions
254                 final float ratio = (float) sourceWidth / sourceHeight;
255                 if (sourceWidth > sourceHeight) {
256                     height = (int) (width / ratio);
257                 } else if (sourceHeight > sourceWidth) {
258                     width = (int) (height * ratio);
259                 }
260             }
261             // no intrinsic size --> use default size
262             int textureWidth = iconBitmapSize;
263             int textureHeight = iconBitmapSize;
264 
265             Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
266                     Bitmap.Config.ARGB_8888);
267             final Canvas canvas = sCanvas;
268             canvas.setBitmap(bitmap);
269 
270             final int left = (textureWidth-width) / 2;
271             final int top = (textureHeight-height) / 2;
272 
273             sOldBounds.set(icon.getBounds());
274             if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
275                 int offset = Math.max((int)(ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
276                         Math.min(left, top));
277                 int size = Math.max(width, height);
278                 icon.setBounds(offset, offset, size, size);
279             } else {
280                 icon.setBounds(left, top, left+width, top+height);
281             }
282             canvas.save(Canvas.MATRIX_SAVE_FLAG);
283             canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
284             icon.draw(canvas);
285             canvas.restore();
286             icon.setBounds(sOldBounds);
287             canvas.setBitmap(null);
288 
289             return bitmap;
290         }
291     }
292 
293     /**
294      * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
295      * shrink the legacy icon and set it as foreground. Use color drawable as background to
296      * create AdaptiveIconDrawable.
297      */
wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale)298     static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) {
299         if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) {
300             return drawable;
301         }
302 
303         try {
304             if (!(drawable instanceof AdaptiveIconDrawable)) {
305                 AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
306                         context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
307                 FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
308                 fsd.setDrawable(drawable);
309                 fsd.setScale(scale);
310                 return (Drawable) iconWrapper;
311             }
312         } catch (Exception e) {
313             return drawable;
314         }
315         return drawable;
316     }
317 
createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context)318     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
319         return createShortcutIcon(shortcutInfo, context, true /* badged */);
320     }
321 
createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, boolean badged)322     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
323             boolean badged) {
324         return createShortcutIcon(shortcutInfo, context, badged, null);
325     }
326 
createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, final Bitmap fallbackIcon)327     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
328             final Bitmap fallbackIcon) {
329         Provider<Bitmap> fallbackIconProvider = new Provider<Bitmap>() {
330             @Override
331             public Bitmap get() {
332                 // If the shortcut is pinned but no longer has an icon in the system,
333                 // keep the current icon instead of reverting to the default icon.
334                 return fallbackIcon;
335             }
336         };
337         return createShortcutIcon(shortcutInfo, context, true, fallbackIconProvider);
338     }
339 
createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider)340     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
341             boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
342         LauncherAppState app = LauncherAppState.getInstance(context);
343         Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
344                 .getShortcutIconDrawable(shortcutInfo,
345                         app.getInvariantDeviceProfile().fillResIconDpi);
346         IconCache cache = app.getIconCache();
347         Bitmap unbadgedBitmap = null;
348         if (unbadgedDrawable != null) {
349             unbadgedBitmap = LauncherIcons.createScaledBitmapWithoutShadow(
350                     unbadgedDrawable, context, 0);
351         } else {
352             if (fallbackIconProvider != null) {
353                 unbadgedBitmap = fallbackIconProvider.get();
354             }
355             if (unbadgedBitmap == null) {
356                 unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle());
357             }
358         }
359 
360         if (!badged) {
361             return unbadgedBitmap;
362         }
363         unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context);
364         return badgeWithBitmap(unbadgedBitmap, getShortcutInfoBadge(shortcutInfo, cache), context);
365     }
366 
getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache)367     public static Bitmap getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) {
368         final Bitmap badgeBitmap;
369         ComponentName cn = shortcutInfo.getActivity();
370         if (cn != null) {
371             // Get the app info for the source activity.
372             AppInfo appInfo = new AppInfo();
373             appInfo.user = shortcutInfo.getUserHandle();
374             appInfo.componentName = cn;
375             appInfo.intent = new Intent(Intent.ACTION_MAIN)
376                     .addCategory(Intent.CATEGORY_LAUNCHER)
377                     .setComponent(cn);
378             cache.getTitleAndIcon(appInfo, false);
379             badgeBitmap = appInfo.iconBitmap;
380         } else {
381             PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
382             cache.getTitleAndIconForApp(pkgInfo, false);
383             badgeBitmap = pkgInfo.iconBitmap;
384         }
385         return badgeBitmap;
386     }
387 
388     /**
389      * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
390      * This allows the badging to be done based on the action bitmap size rather than
391      * the scaled bitmap size.
392      */
393     private static class FixedSizeBitmapDrawable extends BitmapDrawable {
394 
FixedSizeBitmapDrawable(Bitmap bitmap)395         public FixedSizeBitmapDrawable(Bitmap bitmap) {
396             super(null, bitmap);
397         }
398 
399         @Override
getIntrinsicHeight()400         public int getIntrinsicHeight() {
401             return getBitmap().getWidth();
402         }
403 
404         @Override
getIntrinsicWidth()405         public int getIntrinsicWidth() {
406             return getBitmap().getWidth();
407         }
408     }
409 }
410