1 package com.android.launcher3.icons;
2 
3 import android.content.Context;
4 import android.content.Intent;
5 import android.content.pm.LauncherApps;
6 import android.content.pm.ShortcutInfo;
7 import android.graphics.Bitmap;
8 import android.graphics.Canvas;
9 import android.graphics.Path;
10 import android.graphics.Rect;
11 import android.graphics.drawable.AdaptiveIconDrawable;
12 import android.graphics.drawable.Drawable;
13 import android.graphics.drawable.Icon;
14 import android.os.Build;
15 
16 import androidx.annotation.NonNull;
17 import androidx.annotation.Nullable;
18 
19 /**
20  * Factory for creating normalized bubble icons and app badges.
21  */
22 public class BubbleIconFactory extends BaseIconFactory {
23 
24     private final int mRingColor;
25     private final int mRingWidth;
26 
27     private final BaseIconFactory mBadgeFactory;
28 
29     /**
30      * Creates a bubble icon factory.
31      *
32      * @param context the context for the factory.
33      * @param iconSize the size of the bubble icon (i.e. the large icon for the bubble).
34      * @param badgeSize the size of the badge (i.e. smaller icon shown on top of the large icon).
35      * @param ringColor the color of the ring optionally shown around the badge.
36      * @param ringWidth the width of the ring optionally shown around the badge.
37      */
BubbleIconFactory(Context context, int iconSize, int badgeSize, int ringColor, int ringWidth)38     public BubbleIconFactory(Context context, int iconSize, int badgeSize, int ringColor,
39             int ringWidth) {
40         super(context, context.getResources().getConfiguration().densityDpi, iconSize);
41         mRingColor = ringColor;
42         mRingWidth = ringWidth;
43 
44         mBadgeFactory = new BaseIconFactory(context,
45                 context.getResources().getConfiguration().densityDpi,
46                 badgeSize);
47     }
48 
49     /**
50      * Returns the drawable that the developer has provided to display in the bubble.
51      */
getBubbleDrawable(@onNull final Context context, @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic)52     public Drawable getBubbleDrawable(@NonNull final Context context,
53             @Nullable final ShortcutInfo shortcutInfo, @Nullable final Icon ic) {
54         if (shortcutInfo != null) {
55             LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
56             int density = context.getResources().getConfiguration().densityDpi;
57             return launcherApps.getShortcutIconDrawable(shortcutInfo, density);
58         } else {
59             if (ic != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
60                 if (ic.getType() == Icon.TYPE_URI
61                         || ic.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
62                     context.grantUriPermission(context.getPackageName(),
63                             ic.getUri(),
64                             Intent.FLAG_GRANT_READ_URI_PERMISSION);
65                 }
66                 return ic.loadDrawable(context);
67             }
68             return null;
69         }
70     }
71 
72     /**
73      * Creates the bitmap for the provided drawable and returns the scale used for
74      * drawing the actual drawable. This is used for the larger icon shown for the bubble.
75      */
getBubbleBitmap(@onNull Drawable icon, float[] outScale)76     public Bitmap getBubbleBitmap(@NonNull Drawable icon, float[] outScale) {
77         if (outScale == null) {
78             outScale = new float[1];
79         }
80         icon = normalizeAndWrapToAdaptiveIcon(icon,
81                 null /* outscale */,
82                 outScale);
83         return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW);
84     }
85 
86     /**
87      * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This
88      * will include the workprofile indicator on the badge if appropriate.
89      */
getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation)90     public BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) {
91         if (userBadgedAppIcon instanceof AdaptiveIconDrawable) {
92             AdaptiveIconDrawable ad = (AdaptiveIconDrawable) userBadgedAppIcon;
93             userBadgedAppIcon = new CircularAdaptiveIcon(ad.getBackground(),
94                     ad.getForeground());
95         }
96         if (isImportantConversation) {
97             userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon);
98         }
99         Bitmap userBadgedBitmap = mBadgeFactory.createIconBitmap(
100                 userBadgedAppIcon, 1, MODE_WITH_SHADOW);
101         return mBadgeFactory.createIconBitmap(userBadgedBitmap);
102     }
103 
104     private class CircularRingDrawable extends CircularAdaptiveIcon {
105         final Rect mInnerBounds = new Rect();
106 
107         final Drawable mDr;
108 
CircularRingDrawable(Drawable dr)109         CircularRingDrawable(Drawable dr) {
110             super(null, null);
111             mDr = dr;
112         }
113 
114         @Override
draw(Canvas canvas)115         public void draw(Canvas canvas) {
116             int save = canvas.save();
117             canvas.clipPath(getIconMask());
118             canvas.drawColor(mRingColor);
119             mInnerBounds.set(getBounds());
120             mInnerBounds.inset(mRingWidth, mRingWidth);
121             canvas.translate(mInnerBounds.left, mInnerBounds.top);
122             mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height());
123             mDr.draw(canvas);
124             canvas.restoreToCount(save);
125         }
126     }
127 
128     private static class CircularAdaptiveIcon extends AdaptiveIconDrawable {
129 
130         final Path mPath = new Path();
131 
CircularAdaptiveIcon(Drawable bg, Drawable fg)132         CircularAdaptiveIcon(Drawable bg, Drawable fg) {
133             super(bg, fg);
134         }
135 
136         @Override
getIconMask()137         public Path getIconMask() {
138             mPath.reset();
139             Rect bounds = getBounds();
140             mPath.addOval(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW);
141             return mPath;
142         }
143 
144         @Override
draw(Canvas canvas)145         public void draw(Canvas canvas) {
146             int save = canvas.save();
147             canvas.clipPath(getIconMask());
148 
149             Drawable d;
150             if ((d = getBackground()) != null) {
151                 d.draw(canvas);
152             }
153             if ((d = getForeground()) != null) {
154                 d.draw(canvas);
155             }
156             canvas.restoreToCount(save);
157         }
158     }
159 }
160