• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.launcher3.icons;
2 
3 import static android.graphics.Paint.DITHER_FLAG;
4 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
5 
6 import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR;
7 
8 import android.content.Context;
9 import android.content.Intent;
10 import android.content.pm.PackageManager;
11 import android.content.res.Resources;
12 import android.graphics.Bitmap;
13 import android.graphics.Canvas;
14 import android.graphics.Color;
15 import android.graphics.PaintFlagsDrawFilter;
16 import android.graphics.Rect;
17 import android.graphics.RectF;
18 import android.graphics.drawable.AdaptiveIconDrawable;
19 import android.graphics.drawable.BitmapDrawable;
20 import android.graphics.drawable.ColorDrawable;
21 import android.graphics.drawable.Drawable;
22 import android.os.Build;
23 import android.os.Process;
24 import android.os.UserHandle;
25 
26 /**
27  * This class will be moved to androidx library. There shouldn't be any dependency outside
28  * this package.
29  */
30 public class BaseIconFactory implements AutoCloseable {
31 
32     private static final String TAG = "BaseIconFactory";
33     private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
34     static final boolean ATLEAST_OREO = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
35     static final boolean ATLEAST_P = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
36 
37     private final Rect mOldBounds = new Rect();
38     protected final Context mContext;
39     private final Canvas mCanvas;
40     private final PackageManager mPm;
41     private final ColorExtractor mColorExtractor;
42     private boolean mDisableColorExtractor;
43 
44     protected final int mFillResIconDpi;
45     protected final int mIconBitmapSize;
46 
47     private IconNormalizer mNormalizer;
48     private ShadowGenerator mShadowGenerator;
49     private final boolean mShapeDetection;
50 
51     private Drawable mWrapperIcon;
52     private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
53 
BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize, boolean shapeDetection)54     protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
55             boolean shapeDetection) {
56         mContext = context.getApplicationContext();
57         mShapeDetection = shapeDetection;
58         mFillResIconDpi = fillResIconDpi;
59         mIconBitmapSize = iconBitmapSize;
60 
61         mPm = mContext.getPackageManager();
62         mColorExtractor = new ColorExtractor();
63 
64         mCanvas = new Canvas();
65         mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
66         clear();
67     }
68 
BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize)69     protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
70         this(context, fillResIconDpi, iconBitmapSize, false);
71     }
72 
clear()73     protected void clear() {
74         mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
75         mDisableColorExtractor = false;
76     }
77 
getShadowGenerator()78     public ShadowGenerator getShadowGenerator() {
79         if (mShadowGenerator == null) {
80             mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
81         }
82         return mShadowGenerator;
83     }
84 
getNormalizer()85     public IconNormalizer getNormalizer() {
86         if (mNormalizer == null) {
87             mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
88         }
89         return mNormalizer;
90     }
91 
92     @SuppressWarnings("deprecation")
createIconBitmap(Intent.ShortcutIconResource iconRes)93     public BitmapInfo createIconBitmap(Intent.ShortcutIconResource iconRes) {
94         try {
95             Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
96             if (resources != null) {
97                 final int id = resources.getIdentifier(iconRes.resourceName, null, null);
98                 // do not stamp old legacy shortcuts as the app may have already forgotten about it
99                 return createBadgedIconBitmap(
100                         resources.getDrawableForDensity(id, mFillResIconDpi),
101                         Process.myUserHandle() /* only available on primary user */,
102                         false /* do not apply legacy treatment */);
103             }
104         } catch (Exception e) {
105             // Icon not found.
106         }
107         return null;
108     }
109 
createIconBitmap(Bitmap icon)110     public BitmapInfo createIconBitmap(Bitmap icon) {
111         if (mIconBitmapSize != icon.getWidth() || mIconBitmapSize != icon.getHeight()) {
112             icon = createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f);
113         }
114 
115         return BitmapInfo.fromBitmap(icon, mDisableColorExtractor ? null : mColorExtractor);
116     }
117 
createBadgedIconBitmap(Drawable icon, UserHandle user, boolean shrinkNonAdaptiveIcons)118     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
119             boolean shrinkNonAdaptiveIcons) {
120         return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, false, null);
121     }
122 
createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk)123     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
124             int iconAppTargetSdk) {
125         return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
126     }
127 
createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk, boolean isInstantApp)128     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
129             int iconAppTargetSdk, boolean isInstantApp) {
130         return createBadgedIconBitmap(icon, user, iconAppTargetSdk, isInstantApp, null);
131     }
132 
createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk, boolean isInstantApp, float[] scale)133     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
134             int iconAppTargetSdk, boolean isInstantApp, float[] scale) {
135         boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
136                 (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
137         return createBadgedIconBitmap(icon, user, shrinkNonAdaptiveIcons, isInstantApp, scale);
138     }
139 
createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk)140     public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
141         boolean shrinkNonAdaptiveIcons = ATLEAST_P ||
142                 (ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O);
143         return  createScaledBitmapWithoutShadow(icon, shrinkNonAdaptiveIcons);
144     }
145 
146     /**
147      * Creates bitmap using the source drawable and various parameters.
148      * The bitmap is visually normalized with other icons and has enough spacing to add shadow.
149      *
150      * @param icon                      source of the icon
151      * @param user                      info can be used for a badge
152      * @param shrinkNonAdaptiveIcons    {@code true} if non adaptive icons should be treated
153      * @param isInstantApp              info can be used for a badge
154      * @param scale                     returns the scale result from normalization
155      * @return a bitmap suitable for disaplaying as an icon at various system UIs.
156      */
createBadgedIconBitmap(Drawable icon, UserHandle user, boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale)157     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user,
158             boolean shrinkNonAdaptiveIcons, boolean isInstantApp, float[] scale) {
159         if (scale == null) {
160             scale = new float[1];
161         }
162         icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
163         Bitmap bitmap = createIconBitmap(icon, scale[0]);
164         if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
165             mCanvas.setBitmap(bitmap);
166             getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
167             mCanvas.setBitmap(null);
168         }
169 
170         if (isInstantApp) {
171             badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
172         }
173         if (user != null) {
174             BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
175             Drawable badged = mPm.getUserBadgedIcon(drawable, user);
176             if (badged instanceof BitmapDrawable) {
177                 bitmap = ((BitmapDrawable) badged).getBitmap();
178             } else {
179                 bitmap = createIconBitmap(badged, 1f);
180             }
181         }
182         return BitmapInfo.fromBitmap(bitmap, mDisableColorExtractor ? null : mColorExtractor);
183     }
184 
createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons)185     public Bitmap createScaledBitmapWithoutShadow(Drawable icon, boolean shrinkNonAdaptiveIcons) {
186         RectF iconBounds = new RectF();
187         float[] scale = new float[1];
188         icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, iconBounds, scale);
189         return createIconBitmap(icon,
190                 Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
191     }
192 
193     /**
194      * Sets the background color used for wrapped adaptive icon
195      */
setWrapperBackgroundColor(int color)196     public void setWrapperBackgroundColor(int color) {
197         mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
198     }
199 
200     /**
201      * Disables the dominant color extraction for all icons loaded.
202      */
disableColorExtraction()203     public void disableColorExtraction() {
204         mDisableColorExtractor = true;
205     }
206 
normalizeAndWrapToAdaptiveIcon(Drawable icon, boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale)207     private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, boolean shrinkNonAdaptiveIcons,
208             RectF outIconBounds, float[] outScale) {
209         float scale = 1f;
210 
211         if (shrinkNonAdaptiveIcons && ATLEAST_OREO) {
212             if (mWrapperIcon == null) {
213                 mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
214                         .mutate();
215             }
216             AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
217             dr.setBounds(0, 0, 1, 1);
218             boolean[] outShape = new boolean[1];
219             scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
220             if (!(icon instanceof AdaptiveIconDrawable) && !outShape[0]) {
221                 FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
222                 fsd.setDrawable(icon);
223                 fsd.setScale(scale);
224                 icon = dr;
225                 scale = getNormalizer().getScale(icon, outIconBounds, null, null);
226 
227                 ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
228             }
229         } else {
230             scale = getNormalizer().getScale(icon, outIconBounds, null, null);
231         }
232 
233         outScale[0] = scale;
234         return icon;
235     }
236 
237     /**
238      * Adds the {@param badge} on top of {@param target} using the badge dimensions.
239      */
badgeWithDrawable(Bitmap target, Drawable badge)240     public void badgeWithDrawable(Bitmap target, Drawable badge) {
241         mCanvas.setBitmap(target);
242         badgeWithDrawable(mCanvas, badge);
243         mCanvas.setBitmap(null);
244     }
245 
246     /**
247      * Adds the {@param badge} on top of {@param target} using the badge dimensions.
248      */
badgeWithDrawable(Canvas target, Drawable badge)249     public void badgeWithDrawable(Canvas target, Drawable badge) {
250         int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
251         badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
252                 mIconBitmapSize, mIconBitmapSize);
253         badge.draw(target);
254     }
255 
createIconBitmap(Drawable icon, float scale)256     private Bitmap createIconBitmap(Drawable icon, float scale) {
257         return createIconBitmap(icon, scale, mIconBitmapSize);
258     }
259 
260     /**
261      * @param icon drawable that should be flattened to a bitmap
262      * @param scale the scale to apply before drawing {@param icon} on the canvas
263      */
createIconBitmap(Drawable icon, float scale, int size)264     public Bitmap createIconBitmap(Drawable icon, float scale, int size) {
265         Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
266         if (icon == null) {
267             return bitmap;
268         }
269         mCanvas.setBitmap(bitmap);
270         mOldBounds.set(icon.getBounds());
271 
272         if (ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
273             int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
274                     Math.round(size * (1 - scale) / 2 ));
275             icon.setBounds(offset, offset, size - offset, size - offset);
276             icon.draw(mCanvas);
277         } else {
278             if (icon instanceof BitmapDrawable) {
279                 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
280                 Bitmap b = bitmapDrawable.getBitmap();
281                 if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
282                     bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
283                 }
284             }
285             int width = size;
286             int height = size;
287 
288             int intrinsicWidth = icon.getIntrinsicWidth();
289             int intrinsicHeight = icon.getIntrinsicHeight();
290             if (intrinsicWidth > 0 && intrinsicHeight > 0) {
291                 // Scale the icon proportionally to the icon dimensions
292                 final float ratio = (float) intrinsicWidth / intrinsicHeight;
293                 if (intrinsicWidth > intrinsicHeight) {
294                     height = (int) (width / ratio);
295                 } else if (intrinsicHeight > intrinsicWidth) {
296                     width = (int) (height * ratio);
297                 }
298             }
299             final int left = (size - width) / 2;
300             final int top = (size - height) / 2;
301             icon.setBounds(left, top, left + width, top + height);
302             mCanvas.save();
303             mCanvas.scale(scale, scale, size / 2, size / 2);
304             icon.draw(mCanvas);
305             mCanvas.restore();
306 
307         }
308         icon.setBounds(mOldBounds);
309         mCanvas.setBitmap(null);
310         return bitmap;
311     }
312 
313     @Override
close()314     public void close() {
315         clear();
316     }
317 
makeDefaultIcon(UserHandle user)318     public BitmapInfo makeDefaultIcon(UserHandle user) {
319         return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi),
320                 user, Build.VERSION.SDK_INT);
321     }
322 
getFullResDefaultActivityIcon(int iconDpi)323     public static Drawable getFullResDefaultActivityIcon(int iconDpi) {
324         return Resources.getSystem().getDrawableForDensity(
325                 Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
326                         ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon,
327                 iconDpi);
328     }
329 
330     /**
331      * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
332      * This allows the badging to be done based on the action bitmap size rather than
333      * the scaled bitmap size.
334      */
335     private static class FixedSizeBitmapDrawable extends BitmapDrawable {
336 
FixedSizeBitmapDrawable(Bitmap bitmap)337         public FixedSizeBitmapDrawable(Bitmap bitmap) {
338             super(null, bitmap);
339         }
340 
341         @Override
getIntrinsicHeight()342         public int getIntrinsicHeight() {
343             return getBitmap().getWidth();
344         }
345 
346         @Override
getIntrinsicWidth()347         public int getIntrinsicWidth() {
348             return getBitmap().getWidth();
349         }
350     }
351 }
352