• 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 static android.graphics.Paint.DITHER_FLAG;
20 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
21 
22 import static com.android.launcher3.graphics.ShadowGenerator.BLUR_FACTOR;
23 
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.Intent.ShortcutIconResource;
28 import android.content.pm.PackageManager;
29 import android.content.res.Resources;
30 import android.graphics.Bitmap;
31 import android.graphics.Canvas;
32 import android.graphics.Color;
33 import android.graphics.PaintFlagsDrawFilter;
34 import android.graphics.Rect;
35 import android.graphics.RectF;
36 import android.graphics.drawable.AdaptiveIconDrawable;
37 import android.graphics.drawable.BitmapDrawable;
38 import android.graphics.drawable.ColorDrawable;
39 import android.graphics.drawable.Drawable;
40 import android.graphics.drawable.PaintDrawable;
41 import android.os.Build;
42 import android.os.Process;
43 import android.os.UserHandle;
44 import android.support.annotation.Nullable;
45 
46 import com.android.launcher3.AppInfo;
47 import com.android.launcher3.FastBitmapDrawable;
48 import com.android.launcher3.IconCache;
49 import com.android.launcher3.InvariantDeviceProfile;
50 import com.android.launcher3.ItemInfoWithIcon;
51 import com.android.launcher3.LauncherAppState;
52 import com.android.launcher3.R;
53 import com.android.launcher3.Utilities;
54 import com.android.launcher3.model.PackageItemInfo;
55 import com.android.launcher3.shortcuts.DeepShortcutManager;
56 import com.android.launcher3.shortcuts.ShortcutInfoCompat;
57 import com.android.launcher3.util.Provider;
58 import com.android.launcher3.util.Themes;
59 
60 /**
61  * Helper methods for generating various launcher icons
62  */
63 public class LauncherIcons implements AutoCloseable {
64 
65     private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
66 
67     public static final Object sPoolSync = new Object();
68     private static LauncherIcons sPool;
69 
70     /**
71      * Return a new Message instance from the global pool. Allows us to
72      * avoid allocating new objects in many cases.
73      */
obtain(Context context)74     public static LauncherIcons obtain(Context context) {
75         synchronized (sPoolSync) {
76             if (sPool != null) {
77                 LauncherIcons m = sPool;
78                 sPool = m.next;
79                 m.next = null;
80                 return m;
81             }
82         }
83         return new LauncherIcons(context);
84     }
85 
86     /**
87      * Recycles a LauncherIcons that may be in-use.
88      */
recycle()89     public void recycle() {
90         synchronized (sPoolSync) {
91             // Clear any temporary state variables
92             mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
93 
94             next = sPool;
95             sPool = this;
96         }
97     }
98 
99     @Override
close()100     public void close() {
101         recycle();
102     }
103 
104     private final Rect mOldBounds = new Rect();
105     private final Context mContext;
106     private final Canvas mCanvas;
107     private final PackageManager mPm;
108 
109     private final int mFillResIconDpi;
110     private final int mIconBitmapSize;
111 
112     private IconNormalizer mNormalizer;
113     private ShadowGenerator mShadowGenerator;
114 
115     private Drawable mWrapperIcon;
116     private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
117 
118     // sometimes we store linked lists of these things
119     private LauncherIcons next;
120 
LauncherIcons(Context context)121     private LauncherIcons(Context context) {
122         mContext = context.getApplicationContext();
123         mPm = mContext.getPackageManager();
124 
125         InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
126         mFillResIconDpi = idp.fillResIconDpi;
127         mIconBitmapSize = idp.iconBitmapSize;
128 
129         mCanvas = new Canvas();
130         mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
131     }
132 
getShadowGenerator()133     public ShadowGenerator getShadowGenerator() {
134         if (mShadowGenerator == null) {
135             mShadowGenerator = new ShadowGenerator(mContext);
136         }
137         return mShadowGenerator;
138     }
139 
getNormalizer()140     public IconNormalizer getNormalizer() {
141         if (mNormalizer == null) {
142             mNormalizer = new IconNormalizer(mContext);
143         }
144         return mNormalizer;
145     }
146 
147     /**
148      * Returns a bitmap suitable for the all apps view. If the package or the resource do not
149      * exist, it returns null.
150      */
createIconBitmap(ShortcutIconResource iconRes)151     public BitmapInfo createIconBitmap(ShortcutIconResource iconRes) {
152         try {
153             Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
154             if (resources != null) {
155                 final int id = resources.getIdentifier(iconRes.resourceName, null, null);
156                 // do not stamp old legacy shortcuts as the app may have already forgotten about it
157                 return createBadgedIconBitmap(
158                         resources.getDrawableForDensity(id, mFillResIconDpi),
159                         Process.myUserHandle() /* only available on primary user */,
160                         0 /* do not apply legacy treatment */);
161             }
162         } catch (Exception e) {
163             // Icon not found.
164         }
165         return null;
166     }
167 
168     /**
169      * Returns a bitmap which is of the appropriate size to be displayed as an icon
170      */
createIconBitmap(Bitmap icon)171     public BitmapInfo createIconBitmap(Bitmap icon) {
172         if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
173             return BitmapInfo.fromBitmap(icon);
174         }
175         return BitmapInfo.fromBitmap(
176                 createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
177     }
178 
179     /**
180      * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
181      * view or workspace. The icon is badged for {@param user}.
182      * The bitmap is also visually normalized with other icons.
183      */
createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk)184     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
185         return createBadgedIconBitmap(icon, user, iconAppTargetSdk, false);
186     }
187 
188     /**
189      * Returns a bitmap suitable for displaying as an icon at various launcher UIs like all apps
190      * view or workspace. The icon is badged for {@param user}.
191      * The bitmap is also visually normalized with other icons.
192      */
createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk, boolean isInstantApp)193     public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk,
194             boolean isInstantApp) {
195         float[] scale = new float[1];
196         icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
197         Bitmap bitmap = createIconBitmap(icon, scale[0]);
198         if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
199             mCanvas.setBitmap(bitmap);
200             getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
201             mCanvas.setBitmap(null);
202         }
203 
204         final Bitmap result;
205         if (user != null && !Process.myUserHandle().equals(user)) {
206             BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
207             Drawable badged = mPm.getUserBadgedIcon(drawable, user);
208             if (badged instanceof BitmapDrawable) {
209                 result = ((BitmapDrawable) badged).getBitmap();
210             } else {
211                 result = createIconBitmap(badged, 1f);
212             }
213         } else if (isInstantApp) {
214             badgeWithDrawable(bitmap, mContext.getDrawable(R.drawable.ic_instant_app_badge));
215             result = bitmap;
216         } else {
217             result = bitmap;
218         }
219         return BitmapInfo.fromBitmap(result);
220     }
221 
222     /**
223      * Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
224      * normalized with other icons and has enough spacing to add shadow.
225      */
createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk)226     public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
227         RectF iconBounds = new RectF();
228         float[] scale = new float[1];
229         icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, iconBounds, scale);
230         return createIconBitmap(icon,
231                 Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
232     }
233 
234     /**
235      * Sets the background color used for wrapped adaptive icon
236      */
setWrapperBackgroundColor(int color)237     public void setWrapperBackgroundColor(int color) {
238         mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
239     }
240 
normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk, RectF outIconBounds, float[] outScale)241     private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
242             RectF outIconBounds, float[] outScale) {
243         float scale = 1f;
244         if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
245             boolean[] outShape = new boolean[1];
246             if (mWrapperIcon == null) {
247                 mWrapperIcon = mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper)
248                         .mutate();
249             }
250             AdaptiveIconDrawable dr = (AdaptiveIconDrawable) mWrapperIcon;
251             dr.setBounds(0, 0, 1, 1);
252             scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
253             if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
254                 FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
255                 fsd.setDrawable(icon);
256                 fsd.setScale(scale);
257                 icon = dr;
258                 scale = getNormalizer().getScale(icon, outIconBounds, null, null);
259 
260                 ((ColorDrawable) dr.getBackground()).setColor(mWrapperBackgroundColor);
261             }
262         } else {
263             scale = getNormalizer().getScale(icon, outIconBounds, null, null);
264         }
265 
266         outScale[0] = scale;
267         return icon;
268     }
269 
270     /**
271      * Adds the {@param badge} on top of {@param target} using the badge dimensions.
272      */
badgeWithDrawable(Bitmap target, Drawable badge)273     public void badgeWithDrawable(Bitmap target, Drawable badge) {
274         mCanvas.setBitmap(target);
275         badgeWithDrawable(mCanvas, badge);
276         mCanvas.setBitmap(null);
277     }
278 
279     /**
280      * Adds the {@param badge} on top of {@param target} using the badge dimensions.
281      */
badgeWithDrawable(Canvas target, Drawable badge)282     private void badgeWithDrawable(Canvas target, Drawable badge) {
283         int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
284         badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
285                 mIconBitmapSize, mIconBitmapSize);
286         badge.draw(target);
287     }
288 
289     /**
290      * @param scale the scale to apply before drawing {@param icon} on the canvas
291      */
createIconBitmap(Drawable icon, float scale)292     private Bitmap createIconBitmap(Drawable icon, float scale) {
293         int width = mIconBitmapSize;
294         int height = mIconBitmapSize;
295 
296         if (icon instanceof PaintDrawable) {
297             PaintDrawable painter = (PaintDrawable) icon;
298             painter.setIntrinsicWidth(width);
299             painter.setIntrinsicHeight(height);
300         } else if (icon instanceof BitmapDrawable) {
301             // Ensure the bitmap has a density.
302             BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
303             Bitmap bitmap = bitmapDrawable.getBitmap();
304             if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
305                 bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
306             }
307         }
308 
309         int sourceWidth = icon.getIntrinsicWidth();
310         int sourceHeight = icon.getIntrinsicHeight();
311         if (sourceWidth > 0 && sourceHeight > 0) {
312             // Scale the icon proportionally to the icon dimensions
313             final float ratio = (float) sourceWidth / sourceHeight;
314             if (sourceWidth > sourceHeight) {
315                 height = (int) (width / ratio);
316             } else if (sourceHeight > sourceWidth) {
317                 width = (int) (height * ratio);
318             }
319         }
320         // no intrinsic size --> use default size
321         int textureWidth = mIconBitmapSize;
322         int textureHeight = mIconBitmapSize;
323 
324         Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
325                 Bitmap.Config.ARGB_8888);
326         mCanvas.setBitmap(bitmap);
327 
328         final int left = (textureWidth-width) / 2;
329         final int top = (textureHeight-height) / 2;
330 
331         mOldBounds.set(icon.getBounds());
332         if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
333             int offset = Math.max((int) Math.ceil(BLUR_FACTOR * textureWidth), Math.max(left, top));
334             int size = Math.max(width, height);
335             icon.setBounds(offset, offset, offset + size, offset + size);
336         } else {
337             icon.setBounds(left, top, left+width, top+height);
338         }
339         mCanvas.save();
340         mCanvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
341         icon.draw(mCanvas);
342         mCanvas.restore();
343         icon.setBounds(mOldBounds);
344         mCanvas.setBitmap(null);
345 
346         return bitmap;
347     }
348 
createShortcutIcon(ShortcutInfoCompat shortcutInfo)349     public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo) {
350         return createShortcutIcon(shortcutInfo, true /* badged */);
351     }
352 
createShortcutIcon(ShortcutInfoCompat shortcutInfo, boolean badged)353     public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, boolean badged) {
354         return createShortcutIcon(shortcutInfo, badged, null);
355     }
356 
createShortcutIcon(ShortcutInfoCompat shortcutInfo, boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider)357     public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo,
358             boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
359         Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
360                 .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
361         IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
362 
363         final Bitmap unbadgedBitmap;
364         if (unbadgedDrawable != null) {
365             unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
366         } else {
367             if (fallbackIconProvider != null) {
368                 // Fallback icons are already badged and with appropriate shadow
369                 Bitmap fullIcon = fallbackIconProvider.get();
370                 if (fullIcon != null) {
371                     return createIconBitmap(fullIcon);
372                 }
373             }
374             unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon;
375         }
376 
377         BitmapInfo result = new BitmapInfo();
378         if (!badged) {
379             result.color = Themes.getColorAccent(mContext);
380             result.icon = unbadgedBitmap;
381             return result;
382         }
383 
384         final Bitmap unbadgedfinal = unbadgedBitmap;
385         final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
386 
387         result.color = badge.iconColor;
388         result.icon = BitmapRenderer.createHardwareBitmap(mIconBitmapSize, mIconBitmapSize, (c) -> {
389             getShadowGenerator().recreateIcon(unbadgedfinal, c);
390             badgeWithDrawable(c, new FastBitmapDrawable(badge));
391         });
392         return result;
393     }
394 
getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache)395     public ItemInfoWithIcon getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) {
396         ComponentName cn = shortcutInfo.getActivity();
397         String badgePkg = shortcutInfo.getBadgePackage(mContext);
398         boolean hasBadgePkgSet = !badgePkg.equals(shortcutInfo.getPackage());
399         if (cn != null && !hasBadgePkgSet) {
400             // Get the app info for the source activity.
401             AppInfo appInfo = new AppInfo();
402             appInfo.user = shortcutInfo.getUserHandle();
403             appInfo.componentName = cn;
404             appInfo.intent = new Intent(Intent.ACTION_MAIN)
405                     .addCategory(Intent.CATEGORY_LAUNCHER)
406                     .setComponent(cn);
407             cache.getTitleAndIcon(appInfo, false);
408             return appInfo;
409         } else {
410             PackageItemInfo pkgInfo = new PackageItemInfo(badgePkg);
411             cache.getTitleAndIconForApp(pkgInfo, false);
412             return pkgInfo;
413         }
414     }
415 
416     /**
417      * An extension of {@link BitmapDrawable} which returns the bitmap pixel size as intrinsic size.
418      * This allows the badging to be done based on the action bitmap size rather than
419      * the scaled bitmap size.
420      */
421     private static class FixedSizeBitmapDrawable extends BitmapDrawable {
422 
FixedSizeBitmapDrawable(Bitmap bitmap)423         public FixedSizeBitmapDrawable(Bitmap bitmap) {
424             super(null, bitmap);
425         }
426 
427         @Override
getIntrinsicHeight()428         public int getIntrinsicHeight() {
429             return getBitmap().getWidth();
430         }
431 
432         @Override
getIntrinsicWidth()433         public int getIntrinsicWidth() {
434             return getBitmap().getWidth();
435         }
436     }
437 }
438