OSDN Git Service

When creating bitmap, do not apply width/height ratio if AdaptiveIconDrawable
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / graphics / LauncherIcons.java
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 {
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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      */
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 (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);
272             } else {
273                 icon.setBounds(left, top, left+width, top+height);
274             }
275             canvas.save(Canvas.MATRIX_SAVE_FLAG);
276             canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
277             icon.draw(canvas);
278             canvas.restore();
279             icon.setBounds(sOldBounds);
280             canvas.setBitmap(null);
281
282             return bitmap;
283         }
284     }
285
286     /**
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.
290      */
291     static Drawable wrapToAdaptiveIconDrawable(Context context, Drawable drawable, float scale) {
292         if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.isAtLeastO())) {
293             return drawable;
294         }
295
296         try {
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);
302                 fsd.setScale(scale);
303                 return (Drawable) iconWrapper;
304             }
305         } catch (Exception e) {
306             return drawable;
307         }
308         return drawable;
309     }
310
311     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
312         return createShortcutIcon(shortcutInfo, context, true /* badged */);
313     }
314
315     public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
316             boolean badged) {
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);
326
327         if (!badged) {
328             return unbadgedBitmap;
329         }
330         unbadgedBitmap = LauncherIcons.addShadowToIcon(unbadgedBitmap, context);
331
332         final Bitmap badgeBitmap;
333         ComponentName cn = shortcutInfo.getActivity();
334         if (cn != null) {
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)
341                     .setComponent(cn);
342             cache.getTitleAndIcon(appInfo, false);
343             badgeBitmap = appInfo.iconBitmap;
344         } else {
345             PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage());
346             cache.getTitleAndIconForApp(pkgInfo, false);
347             badgeBitmap = pkgInfo.iconBitmap;
348         }
349         return badgeWithBitmap(unbadgedBitmap, badgeBitmap, context);
350     }
351
352     /**
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.
356      */
357     private static class FixedSizeBitmapDrawable extends BitmapDrawable {
358
359         public FixedSizeBitmapDrawable(Bitmap bitmap) {
360             super(null, bitmap);
361         }
362
363         @Override
364         public int getIntrinsicHeight() {
365             return getBitmap().getWidth();
366         }
367
368         @Override
369         public int getIntrinsicWidth() {
370             return getBitmap().getWidth();
371         }
372     }
373 }