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