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