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