[go: nahoru, domu]

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