1 /* 2 * Copyright (C) 2020 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 package com.android.settingslib.notification; 17 18 import android.annotation.ColorInt; 19 import android.content.Context; 20 import android.content.pm.ApplicationInfo; 21 import android.content.pm.LauncherApps; 22 import android.content.pm.PackageManager; 23 import android.content.pm.ShortcutInfo; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.graphics.ColorFilter; 27 import android.graphics.Paint; 28 import android.graphics.Rect; 29 import android.graphics.drawable.Drawable; 30 import android.os.UserHandle; 31 import android.util.IconDrawableFactory; 32 import android.util.Log; 33 34 import com.android.launcher3.icons.BaseIconFactory; 35 import com.android.settingslib.R; 36 import com.android.settingslib.Utils; 37 38 /** 39 * Factory for creating normalized conversation icons. 40 * We are not using Launcher's IconFactory because conversation rendering only runs on the UI 41 * thread, so there is no need to manage a pool across multiple threads. Launcher's rendering 42 * also includes shadows, which are only appropriate on top of wallpaper, not embedded in UI. 43 */ 44 public class ConversationIconFactory extends BaseIconFactory { 45 // Geometry of the various parts of the design. All values are 1dp on a 56x56dp icon grid. 46 // Space is left around the "head" (main avatar) for 47 // ........ 48 // .HHHHHH. 49 // .HHHrrrr 50 // .HHHrBBr 51 // ....rrrr 52 // This is trying to recreate the view layout in notification_template_material_conversation.xml 53 54 private static final float HEAD_SIZE = 52f; 55 private static final float BADGE_SIZE = 12f; 56 private static final float BADGE_CENTER = 46f; 57 private static final float CIRCLE_MARGIN = 36f; 58 private static final float BADGE_ORIGIN = HEAD_SIZE - BADGE_SIZE; // 40f 59 private static final float BASE_ICON_SIZE = 56f; 60 61 private static final float OUT_CIRCLE_DIA = (BASE_ICON_SIZE - CIRCLE_MARGIN); // 20f 62 private static final float INN_CIRCLE_DIA = (float) Math.sqrt(2 * BADGE_SIZE * BADGE_SIZE) ; 63 private static final float OUT_CIRCLE_RAD = OUT_CIRCLE_DIA / 2; 64 private static final float INN_CIRCLE_RAD = INN_CIRCLE_DIA / 2; 65 // Android draws strokes centered on the radius, so our actual radius is an avg of the outside 66 // and inside of the ring stroke 67 private static final float CIRCLE_RADIUS = 68 INN_CIRCLE_RAD + ((OUT_CIRCLE_RAD - INN_CIRCLE_RAD) / 2); 69 private static final float RING_STROKE_WIDTH = (OUT_CIRCLE_DIA - INN_CIRCLE_DIA) / 2; 70 71 final LauncherApps mLauncherApps; 72 final PackageManager mPackageManager; 73 final IconDrawableFactory mIconDrawableFactory; 74 private int mImportantConversationColor; 75 ConversationIconFactory(Context context, LauncherApps la, PackageManager pm, IconDrawableFactory iconDrawableFactory, int iconSizePx)76 public ConversationIconFactory(Context context, LauncherApps la, PackageManager pm, 77 IconDrawableFactory iconDrawableFactory, int iconSizePx) { 78 super(context, context.getResources().getConfiguration().densityDpi, 79 iconSizePx); 80 mLauncherApps = la; 81 mPackageManager = pm; 82 mIconDrawableFactory = iconDrawableFactory; 83 mImportantConversationColor = context.getResources().getColor( 84 R.color.important_conversation, null); 85 } 86 87 /** 88 * Returns the conversation info drawable 89 */ getBaseIconDrawable(ShortcutInfo shortcutInfo)90 public Drawable getBaseIconDrawable(ShortcutInfo shortcutInfo) { 91 return mLauncherApps.getShortcutIconDrawable(shortcutInfo, mFillResIconDpi); 92 } 93 94 /** 95 * Get the {@link Drawable} that represents the app icon, badged with the work profile icon 96 * if appropriate. 97 */ getAppBadge(String packageName, int userId)98 public Drawable getAppBadge(String packageName, int userId) { 99 Drawable badge = null; 100 try { 101 final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( 102 packageName, PackageManager.GET_META_DATA, userId); 103 badge = Utils.getBadgedIcon(mContext, appInfo); 104 } catch (PackageManager.NameNotFoundException e) { 105 badge = mPackageManager.getDefaultActivityIcon(); 106 } 107 return badge; 108 } 109 110 /** 111 * Returns a {@link Drawable} for the entire conversation. The shortcut icon will be badged 112 * with the launcher icon of the app specified by packageName. 113 */ getConversationDrawable(ShortcutInfo info, String packageName, int uid, boolean important)114 public Drawable getConversationDrawable(ShortcutInfo info, String packageName, int uid, 115 boolean important) { 116 return getConversationDrawable(getBaseIconDrawable(info), packageName, uid, important); 117 } 118 119 /** 120 * Returns a {@link Drawable} for the entire conversation. The drawable will be badged 121 * with the launcher icon of the app specified by packageName. 122 */ getConversationDrawable(Drawable baseIcon, String packageName, int uid, boolean important)123 public Drawable getConversationDrawable(Drawable baseIcon, String packageName, int uid, 124 boolean important) { 125 return new ConversationIconDrawable(baseIcon, 126 getAppBadge(packageName, UserHandle.getUserId(uid)), 127 mIconBitmapSize, 128 mImportantConversationColor, 129 important); 130 } 131 132 /** 133 * Custom Drawable that overlays a badge drawable (e.g. notification small icon or app icon) on 134 * a base icon (conversation/person avatar), plus decorations indicating conversation 135 * importance. 136 */ 137 public static class ConversationIconDrawable extends Drawable { 138 private Drawable mBaseIcon; 139 private Drawable mBadgeIcon; 140 private int mIconSize; 141 private Paint mRingPaint; 142 private boolean mShowRing; 143 private Paint mPaddingPaint; 144 ConversationIconDrawable(Drawable baseIcon, Drawable badgeIcon, int iconSize, @ColorInt int ringColor, boolean showImportanceRing)145 public ConversationIconDrawable(Drawable baseIcon, 146 Drawable badgeIcon, 147 int iconSize, 148 @ColorInt int ringColor, 149 boolean showImportanceRing) { 150 mBaseIcon = baseIcon; 151 mBadgeIcon = badgeIcon; 152 mIconSize = iconSize; 153 mShowRing = showImportanceRing; 154 mRingPaint = new Paint(); 155 mRingPaint.setStyle(Paint.Style.STROKE); 156 mRingPaint.setColor(ringColor); 157 mPaddingPaint = new Paint(); 158 mPaddingPaint.setStyle(Paint.Style.FILL_AND_STROKE); 159 mPaddingPaint.setColor(Color.WHITE); 160 } 161 162 /** 163 * Show or hide the importance ring. 164 */ setImportant(boolean important)165 public void setImportant(boolean important) { 166 if (important != mShowRing) { 167 mShowRing = important; 168 invalidateSelf(); 169 } 170 } 171 172 @Override getIntrinsicWidth()173 public int getIntrinsicWidth() { 174 return mIconSize; 175 } 176 177 @Override getIntrinsicHeight()178 public int getIntrinsicHeight() { 179 return mIconSize; 180 } 181 182 // Similar to badgeWithDrawable, but relying on the bounds of each underlying drawable 183 @Override draw(Canvas canvas)184 public void draw(Canvas canvas) { 185 final Rect bounds = getBounds(); 186 187 // scale to our internal grid 188 final float scale = bounds.width() / BASE_ICON_SIZE; 189 final int ringStrokeWidth = (int) (RING_STROKE_WIDTH * scale); 190 final int headSize = (int) (HEAD_SIZE * scale); 191 final int badgePadding = (int) (BADGE_ORIGIN * scale); 192 final int badgeCenter = (int) (BADGE_CENTER * scale); 193 194 mPaddingPaint.setStrokeWidth(ringStrokeWidth); 195 final float radius = (int) (CIRCLE_RADIUS * scale); // stroke outside 196 if (mBaseIcon != null) { 197 mBaseIcon.setBounds(0, 198 0, 199 headSize , 200 headSize); 201 mBaseIcon.draw(canvas); 202 } else { 203 Log.w("ConversationIconFactory", "ConversationIconDrawable has null base icon"); 204 } 205 if (mBadgeIcon != null) { 206 canvas.drawCircle(badgeCenter, badgeCenter, radius, mPaddingPaint); 207 mBadgeIcon.setBounds( 208 badgePadding, 209 badgePadding, 210 headSize, 211 headSize); 212 mBadgeIcon.draw(canvas); 213 } else { 214 Log.w("ConversationIconFactory", "ConversationIconDrawable has null badge icon"); 215 } 216 if (mShowRing) { 217 mRingPaint.setStrokeWidth(ringStrokeWidth); 218 canvas.drawCircle(badgeCenter, badgeCenter, radius, mRingPaint); 219 } 220 } 221 222 @Override setAlpha(int alpha)223 public void setAlpha(int alpha) { 224 // unimplemented 225 } 226 227 @Override setColorFilter(ColorFilter colorFilter)228 public void setColorFilter(ColorFilter colorFilter) { 229 // unimplemented 230 } 231 232 @Override getOpacity()233 public int getOpacity() { 234 return 0; 235 } 236 } 237 } 238