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 true /* shrinkNonAdaptiveIcons */, 82 null /* outscale */, 83 outScale); 84 return createIconBitmap(icon, outScale[0], MODE_WITH_SHADOW); 85 } 86 87 /** 88 * Returns a {@link BitmapInfo} for the app-badge that is shown on top of each bubble. This 89 * will include the workprofile indicator on the badge if appropriate. 90 */ getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation)91 public BitmapInfo getBadgeBitmap(Drawable userBadgedAppIcon, boolean isImportantConversation) { 92 if (userBadgedAppIcon instanceof AdaptiveIconDrawable) { 93 AdaptiveIconDrawable ad = (AdaptiveIconDrawable) userBadgedAppIcon; 94 userBadgedAppIcon = new CircularAdaptiveIcon(ad.getBackground(), 95 ad.getForeground()); 96 } 97 if (isImportantConversation) { 98 userBadgedAppIcon = new CircularRingDrawable(userBadgedAppIcon); 99 } 100 Bitmap userBadgedBitmap = mBadgeFactory.createIconBitmap( 101 userBadgedAppIcon, 1, MODE_WITH_SHADOW); 102 return mBadgeFactory.createIconBitmap(userBadgedBitmap); 103 } 104 105 private class CircularRingDrawable extends CircularAdaptiveIcon { 106 final Rect mInnerBounds = new Rect(); 107 108 final Drawable mDr; 109 CircularRingDrawable(Drawable dr)110 CircularRingDrawable(Drawable dr) { 111 super(null, null); 112 mDr = dr; 113 } 114 115 @Override draw(Canvas canvas)116 public void draw(Canvas canvas) { 117 int save = canvas.save(); 118 canvas.clipPath(getIconMask()); 119 canvas.drawColor(mRingColor); 120 mInnerBounds.set(getBounds()); 121 mInnerBounds.inset(mRingWidth, mRingWidth); 122 canvas.translate(mInnerBounds.left, mInnerBounds.top); 123 mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height()); 124 mDr.draw(canvas); 125 canvas.restoreToCount(save); 126 } 127 } 128 129 private static class CircularAdaptiveIcon extends AdaptiveIconDrawable { 130 131 final Path mPath = new Path(); 132 CircularAdaptiveIcon(Drawable bg, Drawable fg)133 CircularAdaptiveIcon(Drawable bg, Drawable fg) { 134 super(bg, fg); 135 } 136 137 @Override getIconMask()138 public Path getIconMask() { 139 mPath.reset(); 140 Rect bounds = getBounds(); 141 mPath.addOval(bounds.left, bounds.top, bounds.right, bounds.bottom, Path.Direction.CW); 142 return mPath; 143 } 144 145 @Override draw(Canvas canvas)146 public void draw(Canvas canvas) { 147 int save = canvas.save(); 148 canvas.clipPath(getIconMask()); 149 150 Drawable d; 151 if ((d = getBackground()) != null) { 152 d.draw(canvas); 153 } 154 if ((d = getForeground()) != null) { 155 d.draw(canvas); 156 } 157 canvas.restoreToCount(save); 158 } 159 } 160 } 161