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