• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.settingslib.drawable;
18 
19 import android.annotation.NonNull;
20 import android.app.admin.DevicePolicyManager;
21 import android.content.Context;
22 import android.content.res.ColorStateList;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapShader;
25 import android.graphics.Canvas;
26 import android.graphics.Color;
27 import android.graphics.ColorFilter;
28 import android.graphics.Matrix;
29 import android.graphics.Paint;
30 import android.graphics.PixelFormat;
31 import android.graphics.PorterDuff;
32 import android.graphics.PorterDuffColorFilter;
33 import android.graphics.PorterDuffXfermode;
34 import android.graphics.Rect;
35 import android.graphics.RectF;
36 import android.graphics.Shader;
37 import android.graphics.drawable.BitmapDrawable;
38 import android.graphics.drawable.Drawable;
39 
40 import com.android.settingslib.R;
41 
42 /**
43  * Converts the user avatar icon to a circularly clipped one with an optional badge and frame
44  */
45 public class UserIconDrawable extends Drawable implements Drawable.Callback {
46 
47     private Drawable mUserDrawable;
48     private Bitmap mUserIcon;
49     private Bitmap mBitmap; // baked representation. Required for transparent border around badge
50     private final Paint mIconPaint = new Paint();
51     private final Paint mPaint = new Paint();
52     private final Matrix mIconMatrix = new Matrix();
53     private float mIntrinsicRadius;
54     private float mDisplayRadius;
55     private float mPadding = 0;
56     private int mSize = 0; // custom "intrinsic" size for this drawable if non-zero
57     private boolean mInvalidated = true;
58     private ColorStateList mTintColor = null;
59     private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_ATOP;
60 
61     private float mFrameWidth;
62     private float mFramePadding;
63     private ColorStateList mFrameColor = null;
64     private Paint mFramePaint;
65 
66     private Drawable mBadge;
67     private Paint mClearPaint;
68     private float mBadgeRadius;
69     private float mBadgeMargin;
70 
71     /**
72      * Gets the system default managed-user badge as a drawable
73      * @param context
74      * @return drawable containing just the badge
75      */
getManagedUserBadgeDrawable(Context context)76     public static Drawable getManagedUserBadgeDrawable(Context context) {
77         int displayDensity = context.getResources().getDisplayMetrics().densityDpi;
78         return context.getResources().getDrawableForDensity(
79                 com.android.internal.R.drawable.ic_corp_user_badge,
80                 displayDensity, context.getTheme());
81     }
82 
83     /**
84      * Gets the preferred list-item size of this drawable.
85      * @param context
86      * @return size in pixels
87      */
getSizeForList(Context context)88     public static int getSizeForList(Context context) {
89         return (int) context.getResources().getDimension(R.dimen.circle_avatar_size);
90     }
91 
UserIconDrawable()92     public UserIconDrawable() {
93         this(0);
94     }
95 
96     /**
97      * Use this constructor if the drawable is intended to be placed in listviews
98      * @param intrinsicSize if 0, the intrinsic size will come from the icon itself
99      */
UserIconDrawable(int intrinsicSize)100     public UserIconDrawable(int intrinsicSize) {
101         super();
102         mIconPaint.setAntiAlias(true);
103         mIconPaint.setFilterBitmap(true);
104         mPaint.setFilterBitmap(true);
105         mPaint.setAntiAlias(true);
106         if (intrinsicSize > 0) {
107             setBounds(0, 0, intrinsicSize, intrinsicSize);
108             setIntrinsicSize(intrinsicSize);
109         }
110         setIcon(null);
111     }
112 
setIcon(Bitmap icon)113     public UserIconDrawable setIcon(Bitmap icon) {
114         if (mUserDrawable != null) {
115             mUserDrawable.setCallback(null);
116             mUserDrawable = null;
117         }
118         mUserIcon = icon;
119         if (mUserIcon == null) {
120             mIconPaint.setShader(null);
121             mBitmap = null;
122         } else {
123             mIconPaint.setShader(new BitmapShader(icon, Shader.TileMode.CLAMP,
124                     Shader.TileMode.CLAMP));
125         }
126         onBoundsChange(getBounds());
127         return this;
128     }
129 
setIconDrawable(Drawable icon)130     public UserIconDrawable setIconDrawable(Drawable icon) {
131         if (mUserDrawable != null) {
132             mUserDrawable.setCallback(null);
133         }
134         mUserIcon = null;
135         mUserDrawable = icon;
136         if (mUserDrawable == null) {
137             mBitmap = null;
138         } else {
139             mUserDrawable.setCallback(this);
140         }
141         onBoundsChange(getBounds());
142         return this;
143     }
144 
setBadge(Drawable badge)145     public UserIconDrawable setBadge(Drawable badge) {
146         mBadge = badge;
147         if (mBadge != null) {
148             if (mClearPaint == null) {
149                 mClearPaint = new Paint();
150                 mClearPaint.setAntiAlias(true);
151                 mClearPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
152                 mClearPaint.setStyle(Paint.Style.FILL);
153             }
154             // update metrics
155             onBoundsChange(getBounds());
156         } else {
157             invalidateSelf();
158         }
159         return this;
160     }
161 
setBadgeIfManagedUser(Context context, int userId)162     public UserIconDrawable setBadgeIfManagedUser(Context context, int userId) {
163         Drawable badge = null;
164         boolean isManaged = context.getSystemService(DevicePolicyManager.class)
165                 .getProfileOwnerAsUser(userId) != null;
166         if (isManaged) {
167             badge = getManagedUserBadgeDrawable(context);
168         }
169         return setBadge(badge);
170     }
171 
setBadgeRadius(float radius)172     public void setBadgeRadius(float radius) {
173         mBadgeRadius = radius;
174         onBoundsChange(getBounds());
175     }
176 
setBadgeMargin(float margin)177     public void setBadgeMargin(float margin) {
178         mBadgeMargin = margin;
179         onBoundsChange(getBounds());
180     }
181 
182     /**
183      * Sets global padding of icon/frame. Doesn't effect the badge.
184      * @param padding
185      */
setPadding(float padding)186     public void setPadding(float padding) {
187         mPadding = padding;
188         onBoundsChange(getBounds());
189     }
190 
initFramePaint()191     private void initFramePaint() {
192         if (mFramePaint == null) {
193             mFramePaint = new Paint();
194             mFramePaint.setStyle(Paint.Style.STROKE);
195             mFramePaint.setAntiAlias(true);
196         }
197     }
198 
setFrameWidth(float width)199     public void setFrameWidth(float width) {
200         initFramePaint();
201         mFrameWidth = width;
202         mFramePaint.setStrokeWidth(width);
203         onBoundsChange(getBounds());
204     }
205 
setFramePadding(float padding)206     public void setFramePadding(float padding) {
207         initFramePaint();
208         mFramePadding = padding;
209         onBoundsChange(getBounds());
210     }
211 
setFrameColor(int color)212     public void setFrameColor(int color) {
213         initFramePaint();
214         mFramePaint.setColor(color);
215         invalidateSelf();
216     }
217 
setFrameColor(ColorStateList colorList)218     public void setFrameColor(ColorStateList colorList) {
219         initFramePaint();
220         mFrameColor = colorList;
221         invalidateSelf();
222     }
223 
224     /**
225      * This sets the "intrinsic" size of this drawable. Useful for views which use the drawable's
226      * intrinsic size for layout. It is independent of the bounds.
227      * @param size if 0, the intrinsic size will be set to the displayed icon's size
228      */
setIntrinsicSize(int size)229     public void setIntrinsicSize(int size) {
230         mSize = size;
231     }
232 
233     @Override
draw(Canvas canvas)234     public void draw(Canvas canvas) {
235         if (mInvalidated) {
236             rebake();
237         }
238         if (mBitmap != null) {
239             if (mTintColor == null) {
240                 mPaint.setColorFilter(null);
241             } else {
242                 int color = mTintColor.getColorForState(getState(), mTintColor.getDefaultColor());
243                 if (mPaint.getColorFilter() == null) {
244                     mPaint.setColorFilter(new PorterDuffColorFilter(color, mTintMode));
245                 } else {
246                     ((PorterDuffColorFilter) mPaint.getColorFilter()).setMode(mTintMode);
247                     ((PorterDuffColorFilter) mPaint.getColorFilter()).setColor(color);
248                 }
249             }
250 
251             canvas.drawBitmap(mBitmap, 0, 0, mPaint);
252         }
253     }
254 
255     @Override
setAlpha(int alpha)256     public void setAlpha(int alpha) {
257         mPaint.setAlpha(alpha);
258         super.invalidateSelf();
259     }
260 
261     @Override
setColorFilter(ColorFilter colorFilter)262     public void setColorFilter(ColorFilter colorFilter) {
263     }
264 
265     @Override
setTintList(ColorStateList tintList)266     public void setTintList(ColorStateList tintList) {
267         mTintColor = tintList;
268         super.invalidateSelf();
269     }
270 
271     @Override
setTintMode(@onNull PorterDuff.Mode mode)272     public void setTintMode(@NonNull PorterDuff.Mode mode) {
273         mTintMode = mode;
274         super.invalidateSelf();
275     }
276 
277     @Override
getConstantState()278     public ConstantState getConstantState() {
279         return new BitmapDrawable(mBitmap).getConstantState();
280     }
281 
282     /**
283      * This 'bakes' the current state of this icon into a bitmap and removes/recycles the source
284      * bitmap/drawable. Use this when no more changes will be made and an intrinsic size is set.
285      * This effectively turns this into a static drawable.
286      */
bake()287     public UserIconDrawable bake() {
288         if (mSize <= 0) {
289             throw new IllegalStateException("Baking requires an explicit intrinsic size");
290         }
291         onBoundsChange(new Rect(0, 0, mSize, mSize));
292         rebake();
293         mFrameColor = null;
294         mFramePaint = null;
295         mClearPaint = null;
296         if (mUserDrawable != null) {
297             mUserDrawable.setCallback(null);
298             mUserDrawable = null;
299         } else if (mUserIcon != null) {
300             mUserIcon.recycle();
301             mUserIcon = null;
302         }
303         return this;
304     }
305 
rebake()306     private void rebake() {
307         mInvalidated = false;
308 
309         if (mBitmap == null || (mUserDrawable == null && mUserIcon == null)) {
310             return;
311         }
312 
313         final Canvas canvas = new Canvas(mBitmap);
314         canvas.drawColor(0, PorterDuff.Mode.CLEAR);
315 
316         if(mUserDrawable != null) {
317             mUserDrawable.draw(canvas);
318         } else if (mUserIcon != null) {
319             int saveId = canvas.save();
320             canvas.concat(mIconMatrix);
321             canvas.drawCircle(mUserIcon.getWidth() * 0.5f, mUserIcon.getHeight() * 0.5f,
322                     mIntrinsicRadius, mIconPaint);
323             canvas.restoreToCount(saveId);
324         }
325 
326         if (mFrameColor != null) {
327             mFramePaint.setColor(mFrameColor.getColorForState(getState(), Color.TRANSPARENT));
328         }
329         if ((mFrameWidth + mFramePadding) > 0.001f) {
330             float radius = mDisplayRadius - mPadding - mFrameWidth * 0.5f;
331             canvas.drawCircle(getBounds().exactCenterX(), getBounds().exactCenterY(),
332                     radius, mFramePaint);
333         }
334 
335         if ((mBadge != null) && (mBadgeRadius > 0.001f)) {
336             final float badgeDiameter = mBadgeRadius * 2f;
337             final float badgeTop = mBitmap.getHeight() - badgeDiameter;
338             float badgeLeft = mBitmap.getWidth() - badgeDiameter;
339 
340             mBadge.setBounds((int) badgeLeft, (int) badgeTop,
341                     (int) (badgeLeft + badgeDiameter), (int) (badgeTop + badgeDiameter));
342 
343             final float borderRadius = mBadge.getBounds().width() * 0.5f + mBadgeMargin;
344             canvas.drawCircle(badgeLeft + mBadgeRadius, badgeTop + mBadgeRadius,
345                     borderRadius, mClearPaint);
346 
347             mBadge.draw(canvas);
348         }
349     }
350 
351     @Override
onBoundsChange(Rect bounds)352     protected void onBoundsChange(Rect bounds) {
353         if (bounds.isEmpty() || (mUserIcon == null && mUserDrawable == null)) {
354             return;
355         }
356 
357         // re-create bitmap if applicable
358         float newDisplayRadius = Math.min(bounds.width(), bounds.height()) * 0.5f;
359         int size = (int) (newDisplayRadius * 2);
360         if (mBitmap == null || size != ((int) (mDisplayRadius * 2))) {
361             mDisplayRadius = newDisplayRadius;
362             if (mBitmap != null) {
363                 mBitmap.recycle();
364             }
365             mBitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
366         }
367 
368         // update metrics
369         mDisplayRadius = Math.min(bounds.width(), bounds.height()) * 0.5f;
370         final float iconRadius = mDisplayRadius - mFrameWidth - mFramePadding - mPadding;
371         RectF dstRect = new RectF(bounds.exactCenterX() - iconRadius,
372                                   bounds.exactCenterY() - iconRadius,
373                                   bounds.exactCenterX() + iconRadius,
374                                   bounds.exactCenterY() + iconRadius);
375         if (mUserDrawable != null) {
376             Rect rounded = new Rect();
377             dstRect.round(rounded);
378             mIntrinsicRadius = Math.min(mUserDrawable.getIntrinsicWidth(),
379                                         mUserDrawable.getIntrinsicHeight()) * 0.5f;
380             mUserDrawable.setBounds(rounded);
381         } else if (mUserIcon != null) {
382             // Build square-to-square transformation matrix
383             final float iconCX = mUserIcon.getWidth() * 0.5f;
384             final float iconCY = mUserIcon.getHeight() * 0.5f;
385             mIntrinsicRadius = Math.min(iconCX, iconCY);
386             RectF srcRect = new RectF(iconCX - mIntrinsicRadius, iconCY - mIntrinsicRadius,
387                                       iconCX + mIntrinsicRadius, iconCY + mIntrinsicRadius);
388             mIconMatrix.setRectToRect(srcRect, dstRect, Matrix.ScaleToFit.FILL);
389         }
390 
391         invalidateSelf();
392     }
393 
394     @Override
invalidateSelf()395     public void invalidateSelf() {
396         super.invalidateSelf();
397         mInvalidated = true;
398     }
399 
400     @Override
isStateful()401     public boolean isStateful() {
402         return mFrameColor != null && mFrameColor.isStateful();
403     }
404 
405     @Override
getOpacity()406     public int getOpacity() {
407         return PixelFormat.TRANSLUCENT;
408     }
409 
410     @Override
getIntrinsicWidth()411     public int getIntrinsicWidth() {
412         return (mSize <= 0 ? (int) mIntrinsicRadius * 2 : mSize);
413     }
414 
415     @Override
getIntrinsicHeight()416     public int getIntrinsicHeight() {
417         return getIntrinsicWidth();
418     }
419 
420     @Override
invalidateDrawable(@onNull Drawable who)421     public void invalidateDrawable(@NonNull Drawable who) {
422         invalidateSelf();
423     }
424 
425     @Override
scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)426     public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
427         scheduleSelf(what, when);
428     }
429 
430     @Override
unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)431     public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
432         unscheduleSelf(what);
433     }
434 }
435