1 /* 2 * Copyright (C) 2021 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 17 package com.android.systemui.people; 18 19 import android.annotation.ColorInt; 20 import android.content.Context; 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.PackageManager; 23 import android.graphics.Canvas; 24 import android.graphics.ColorFilter; 25 import android.graphics.Paint; 26 import android.graphics.PixelFormat; 27 import android.graphics.Rect; 28 import android.graphics.drawable.Drawable; 29 import android.util.IconDrawableFactory; 30 import android.util.Log; 31 import android.view.ContextThemeWrapper; 32 33 import androidx.core.graphics.drawable.RoundedBitmapDrawable; 34 35 import com.android.settingslib.Utils; 36 37 class PeopleStoryIconFactory implements AutoCloseable { 38 39 private static final int PADDING = 2; 40 private static final int RING_WIDTH = 2; 41 private static final int MAX_BADGE_SIZE = 40; 42 43 final PackageManager mPackageManager; 44 final IconDrawableFactory mIconDrawableFactory; 45 private int mImportantConversationColor; 46 private int mAccentColor; 47 private float mDensity; 48 private float mIconSize; 49 private Context mContext; 50 51 private final int mIconBitmapSize; 52 PeopleStoryIconFactory(Context context, PackageManager pm, IconDrawableFactory iconDrawableFactory, int iconSizeDp)53 PeopleStoryIconFactory(Context context, PackageManager pm, 54 IconDrawableFactory iconDrawableFactory, int iconSizeDp) { 55 mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_DayNight); 56 mIconBitmapSize = (int) (iconSizeDp * mContext.getResources().getDisplayMetrics().density); 57 mDensity = mContext.getResources().getDisplayMetrics().density; 58 mIconSize = mDensity * iconSizeDp; 59 mPackageManager = pm; 60 mIconDrawableFactory = iconDrawableFactory; 61 mImportantConversationColor = mContext.getColor( 62 com.android.launcher3.icons.R.color.important_conversation); 63 mAccentColor = Utils.getColorAttr(mContext, 64 com.android.internal.R.attr.colorAccentPrimaryVariant).getDefaultColor(); 65 } 66 67 68 /** 69 * Gets the {@link Drawable} that represents the app icon, badged with the work profile icon 70 * if appropriate. 71 */ getAppBadge(String packageName, int userId)72 private Drawable getAppBadge(String packageName, int userId) { 73 Drawable badge = null; 74 try { 75 final ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser( 76 packageName, PackageManager.GET_META_DATA, userId); 77 badge = Utils.getBadgedIcon(mContext, appInfo); 78 } catch (PackageManager.NameNotFoundException e) { 79 badge = mPackageManager.getDefaultActivityIcon(); 80 } 81 return badge; 82 } 83 84 /** 85 * Returns a {@link Drawable} for the entire conversation. The shortcut icon will be badged 86 * with the launcher icon of the app specified by packageName. 87 */ getPeopleTileDrawable(RoundedBitmapDrawable headDrawable, String packageName, int userId, boolean important, boolean newStory)88 public Drawable getPeopleTileDrawable(RoundedBitmapDrawable headDrawable, String packageName, 89 int userId, 90 boolean important, boolean newStory) { 91 return new PeopleStoryIconDrawable(headDrawable, getAppBadge(packageName, userId), 92 mIconBitmapSize, mImportantConversationColor, important, mIconSize, mDensity, 93 mAccentColor, newStory); 94 } 95 96 /** 97 * Custom drawable which overlays a badge drawable on a head icon (conversation/person avatar), 98 * with decorations indicating Important conversations and having a New Story. 99 */ 100 public static class PeopleStoryIconDrawable extends Drawable { 101 private float mFullIconSize; 102 private RoundedBitmapDrawable mAvatar; 103 private Drawable mBadgeIcon; 104 private int mIconSize; 105 private Paint mPriorityRingPaint; 106 private boolean mShowImportantRing; 107 private boolean mShowStoryRing; 108 private Paint mStoryPaint; 109 private float mDensity; 110 PeopleStoryIconDrawable(RoundedBitmapDrawable avatar, Drawable badgeIcon, int iconSize, @ColorInt int ringColor, boolean showImportantRing, float fullIconSize, float density, @ColorInt int accentColor, boolean showStoryRing)111 PeopleStoryIconDrawable(RoundedBitmapDrawable avatar, 112 Drawable badgeIcon, 113 int iconSize, 114 @ColorInt int ringColor, 115 boolean showImportantRing, float fullIconSize, float density, 116 @ColorInt int accentColor, boolean showStoryRing) { 117 avatar.setCircular(true); 118 mAvatar = avatar; 119 mBadgeIcon = badgeIcon; 120 mIconSize = iconSize; 121 mShowImportantRing = showImportantRing; 122 mPriorityRingPaint = new Paint(); 123 mPriorityRingPaint.setStyle(Paint.Style.FILL_AND_STROKE); 124 mPriorityRingPaint.setColor(ringColor); 125 mShowStoryRing = showStoryRing; 126 mStoryPaint = new Paint(); 127 mStoryPaint.setStyle(Paint.Style.STROKE); 128 mStoryPaint.setColor(accentColor); 129 mFullIconSize = fullIconSize; 130 mDensity = density; 131 } 132 133 @Override getIntrinsicWidth()134 public int getIntrinsicWidth() { 135 return mIconSize; 136 } 137 138 @Override getIntrinsicHeight()139 public int getIntrinsicHeight() { 140 return mIconSize; 141 } 142 143 @Override draw(Canvas canvas)144 public void draw(Canvas canvas) { 145 final Rect bounds = getBounds(); 146 final int minBound = Math.min(bounds.height(), bounds.width()); 147 // Scale head icon and app icon to our canvas. 148 float scale = minBound / mFullIconSize; 149 150 int paddingInDp = (int) (PADDING * mDensity); 151 int ringStrokeWidth = (int) (RING_WIDTH * mDensity); 152 mPriorityRingPaint.setStrokeWidth(ringStrokeWidth); 153 mStoryPaint.setStrokeWidth(ringStrokeWidth); 154 155 int scaledFullIconSize = (int) (mFullIconSize * scale); 156 int avatarSize = scaledFullIconSize - (paddingInDp * 2); 157 if (mAvatar != null) { 158 int leftAndTopPadding = paddingInDp; 159 int rightAndBottomPadding = avatarSize + paddingInDp; 160 if (mShowStoryRing) { 161 int headCenter = scaledFullIconSize / 2; 162 canvas.drawCircle(headCenter, headCenter, 163 getRadius(avatarSize, ringStrokeWidth), 164 mStoryPaint); 165 leftAndTopPadding += (ringStrokeWidth + paddingInDp); 166 rightAndBottomPadding -= (ringStrokeWidth + paddingInDp); 167 } 168 mAvatar.setBounds(leftAndTopPadding, 169 leftAndTopPadding, 170 rightAndBottomPadding, 171 rightAndBottomPadding); 172 mAvatar.draw(canvas); 173 } else { 174 Log.w("PeopleStoryIconFactory", "Null avatar icon"); 175 } 176 177 // Determine badge size from either the size relative to the head icon, or max size. 178 int maxBadgeSize = (int) (MAX_BADGE_SIZE * mDensity); 179 int badgeSizeRelativeToHead = (int) (avatarSize / 2.4); 180 int badgeSize = Math.min(maxBadgeSize, badgeSizeRelativeToHead); 181 if (mBadgeIcon != null) { 182 int leftAndTopPadding = scaledFullIconSize - badgeSize; 183 int rightAndBottomPadding = scaledFullIconSize; 184 if (mShowImportantRing) { 185 int badgeCenter = leftAndTopPadding + (badgeSize / 2); 186 canvas.drawCircle(badgeCenter, badgeCenter, 187 getRadius(badgeSize, ringStrokeWidth), 188 mPriorityRingPaint); 189 leftAndTopPadding += ringStrokeWidth; 190 rightAndBottomPadding -= ringStrokeWidth; 191 } 192 mBadgeIcon.setBounds( 193 leftAndTopPadding, 194 leftAndTopPadding, 195 rightAndBottomPadding, 196 rightAndBottomPadding); 197 mBadgeIcon.draw(canvas); 198 } else { 199 Log.w("PeopleStoryIconFactory", "Null badge icon"); 200 } 201 } 202 getRadius(int circleWidth, int circleStrokeWidth)203 private int getRadius(int circleWidth, int circleStrokeWidth) { 204 return (circleWidth - circleStrokeWidth) / 2; 205 } 206 207 @Override setAlpha(int alpha)208 public void setAlpha(int alpha) { 209 // unimplemented 210 } 211 212 @Override setColorFilter(ColorFilter colorFilter)213 public void setColorFilter(ColorFilter colorFilter) { 214 if (mAvatar != null) mAvatar.setColorFilter(colorFilter); 215 if (mBadgeIcon != null) mBadgeIcon.setColorFilter(colorFilter); 216 } 217 218 @Override getOpacity()219 public int getOpacity() { 220 return PixelFormat.TRANSLUCENT; 221 } 222 } 223 224 @Override close()225 public void close() { 226 } 227 } 228