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 
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;
48 
49 /**
50  * Helper methods for generating various launcher icons
51  */
52 public class LauncherIcons {
53 
54     private static final Rect sOldBounds = new Rect();
55     private static final Canvas sCanvas = new Canvas();
56 
57     static {
sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG))58         sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
59                 Paint.FILTER_BITMAP_FLAG));
60     }
61 
62     /**
63      * Returns a bitmap suitable for the all apps view. If the package or the resource do not
64      * exist, it returns null.
65      */
createIconBitmap(ShortcutIconResource iconRes, Context context)66     public static Bitmap createIconBitmap(ShortcutIconResource iconRes, Context context) {
67         PackageManager packageManager = context.getPackageManager();
68         // the resource
69         try {
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);
75             }
76         } catch (Exception e) {
77             // Icon not found.
78         }
79         return null;
80     }
81 
82     /**
83      * Returns a bitmap which is of the appropriate size to be displayed as an icon
84      */
createIconBitmap(Bitmap icon, Context context)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()) {
88             return icon;
89         }
90         return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context);
91     }
92 
93     /**
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.
96      */
createBadgedIconBitmap( Drawable icon, UserHandle user, Context context, int iconAppTargetSdk)97     public static Bitmap createBadgedIconBitmap(
98             Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
99 
100         IconNormalizer normalizer;
101         float scale = 1f;
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 &&
111                         !outShape[0]){
112                     Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
113                     if (wrappedIcon != icon) {
114                         icon = wrappedIcon;
115                         scale = normalizer.getScale(icon, null, null, null);
116                     }
117                 }
118             } else {
119                 scale = normalizer.getScale(icon, null, null, null);
120             }
121         }
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);
126         }
127         return badgeIconForUser(bitmap, user, context);
128     }
129 
130     /**
131      * Badges the provided icon with the user badge if required.
132      */
badgeIconForUser(Bitmap icon, UserHandle user, Context context)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(
137                     drawable, user);
138             if (badged instanceof BitmapDrawable) {
139                 return ((BitmapDrawable) badged).getBitmap();
140             } else {
141                 return createIconBitmap(badged, context);
142             }
143         } else {
144             return icon;
145         }
146     }
147 
148     /**
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.
151      */
createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk)152     public static Bitmap createScaledBitmapWithoutShadow(Drawable icon, Context context, int iconAppTargetSdk) {
153         RectF iconBounds = new RectF();
154         IconNormalizer normalizer;
155         float scale = 1f;
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 &&
165                         !outShape[0]) {
166                     Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
167                     if (wrappedIcon != icon) {
168                         icon = wrappedIcon;
169                         scale = normalizer.getScale(icon, iconBounds, null, null);
170                     }
171                 }
172             } else {
173                 scale = normalizer.getScale(icon, iconBounds, null, null);
174             }
175 
176         }
177         scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
178         return createIconBitmap(icon, context, scale);
179     }
180 
181     /**
182      * Adds a shadow to the provided icon. It assumes that the icon has already been scaled using
183      * {@link #createScaledBitmapWithoutShadow(Drawable, Context, int)}
184      */
addShadowToIcon(Bitmap icon, Context context)185     public static Bitmap addShadowToIcon(Bitmap icon, Context context) {
186         return ShadowGenerator.getInstance(context).recreateIcon(icon);
187     }
188 
189     /**
190      * Adds the {@param badge} on top of {@param srcTgt} using the badge dimensions.
191      */
badgeWithBitmap(Bitmap srcTgt, Bitmap badge, Context context)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);
201         }
202         return srcTgt;
203     }
204 
205     /**
206      * Returns a bitmap suitable for the all apps view.
207      */
createIconBitmap(Drawable icon, Context context)208     public static Bitmap createIconBitmap(Drawable icon, Context context) {
209         float scale = 1f;
210         if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.isAtLeastO() &&
211                 icon instanceof AdaptiveIconDrawable) {
212             scale = ShadowGenerator.getScaleForBounds(new RectF(0, 0, 0, 0));
213         }
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);
218         }
219         return bitmap;
220     }
221 
222     /**
223      * @param scale the scale to apply before drawing {@param icon} on the canvas
224      */
createIconBitmap(Drawable icon, Context context, float scale)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;
230 
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());
241                 }
242             }
243 
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);
253                 }
254             }
255             // no intrinsic size --> use default size
256             int textureWidth = iconBitmapSize;
257             int textureHeight = iconBitmapSize;
258 
259             Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
260                     Bitmap.Config.ARGB_8888);
261             final Canvas canvas = sCanvas;
262             canvas.setBitmap(bitmap);
263 
264             final int left = (textureWidth-width) / 2;
265             final int top = (textureHeight-height) / 2;
266 
267             sOldBounds.set(icon.getBounds());
268             if (Utilities.isAtLeastO() && icon instanceof AdaptiveIconDrawable) {
269                 int offset = Math.max((int)(ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
270                         Math.min(left, top));
271                 int size = Math.max(width, height);
272                 icon.setBounds(offset, offset, size, size);
273             } else {
274                 icon.setBounds(left, top, left+width, top+height);
275             }
276             canvas.save(Canvas.MATRIX_SAVE_FLAG);
277             canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
278             icon.draw(canvas);
279             canvas.restore();
280             icon.setBounds(sOldBounds);
281             canvas.setBitmap(null);
282 
283             return bitmap;
284         }
285     }
286 
287     /**
288      * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
289      * shrink the legacy icon and set it as foreground. Use color drawable as background to
290      * create AdaptiveIconDrawable.
291      */
wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale)292     static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) {
293         if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) {
294             return drawable;
295         }
296 
297         try {
298             if (!(drawable instanceof AdaptiveIconDrawable)) {
299                 AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
300                         context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
301                 FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
302                 fsd.setDrawable(drawable);
303                 fsd.setScale(scale);
304                 return (Drawable) iconWrapper;
305             }
306         } catch (Exception e) {
307             return drawable;
308         }
309         return drawable;
310     }
311 
createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context)312     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
313         return createShortcutIcon(shortcutInfo, context, true /* badged */);
314     }
315 
createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, boolean badged)316     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
317             boolean badged) {
318         LauncherAppState app = LauncherAppState.getInstance(context);
319         Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
320                 .getShortcutIconDrawable(shortcutInfo,
321                         app.getInvariantDeviceProfile().fillResIconDpi);
322         IconCache cache = app.getIconCache();
323         Bitmap unbadgedBitmap = unbadgedDrawable == null
324                 ? cache.getDefaultIcon(Process.myUserHandle())
325                 : LauncherIcons.createScaledBitmapWithoutShadow(unbadgedDrawable, context,
326                 Build.VERSION_CODES.O);
327 
328         if (!badged) {
329             return unbadgedBitmap;
330         }
331         unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context);
332 
333         final Bitmap badgeBitmap;
334         ComponentName cn = shortcutInfo.getActivity();
335         if (cn != null) {
336             // Get the app info for the source activity.
337             AppInfo appInfo = new AppInfo();
338             appInfo.user = shortcutInfo.getUserHandle();
339             appInfo.componentName = cn;
340             appInfo.intent = new Intent(Intent.ACTION_MAIN)
341                     .addCategory(Intent.CATEGORY_LAUNCHER)
342                     .setComponent(cn);
343             cache.getTitleAndIcon(appInfo, false);
344             badgeBitmap = appInfo.iconBitmap;
345         } else {
346             PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
347             cache.getTitleAndIconForApp(pkgInfo, false);
348             badgeBitmap = pkgInfo.iconBitmap;
349         }
350         return badgeWithBitmap(unbadgedBitmap, badgeBitmap, context);
351     }
352 
353     /**
354      * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
355      * This allows the badging to be done based on the action bitmap size rather than
356      * the scaled bitmap size.
357      */
358     private static class FixedSizeBitmapDrawable extends BitmapDrawable {
359 
FixedSizeBitmapDrawable(Bitmap bitmap)360         public FixedSizeBitmapDrawable(Bitmap bitmap) {
361             super(null, bitmap);
362         }
363 
364         @Override
getIntrinsicHeight()365         public int getIntrinsicHeight() {
366             return getBitmap().getWidth();
367         }
368 
369         @Override
getIntrinsicWidth()370         public int getIntrinsicWidth() {
371             return getBitmap().getWidth();
372         }
373     }
374 }
375