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