Home | History | Annotate | Download | only in drawable
      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      */
     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      */
     88     public static int getSizeForList(Context context) {
     89         return (int) context.getResources().getDimension(R.dimen.circle_avatar_size);
     90     }
     91 
     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      */
    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 
    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 
    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 
    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 
    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 
    172     public void setBadgeRadius(float radius) {
    173         mBadgeRadius = radius;
    174         onBoundsChange(getBounds());
    175     }
    176 
    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      */
    186     public void setPadding(float padding) {
    187         mPadding = padding;
    188         onBoundsChange(getBounds());
    189     }
    190 
    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 
    199     public void setFrameWidth(float width) {
    200         initFramePaint();
    201         mFrameWidth = width;
    202         mFramePaint.setStrokeWidth(width);
    203         onBoundsChange(getBounds());
    204     }
    205 
    206     public void setFramePadding(float padding) {
    207         initFramePaint();
    208         mFramePadding = padding;
    209         onBoundsChange(getBounds());
    210     }
    211 
    212     public void setFrameColor(int color) {
    213         initFramePaint();
    214         mFramePaint.setColor(color);
    215         invalidateSelf();
    216     }
    217 
    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      */
    229     public void setIntrinsicSize(int size) {
    230         mSize = size;
    231     }
    232 
    233     @Override
    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
    256     public void setAlpha(int alpha) {
    257         mPaint.setAlpha(alpha);
    258         super.invalidateSelf();
    259     }
    260 
    261     @Override
    262     public void setColorFilter(ColorFilter colorFilter) {
    263     }
    264 
    265     @Override
    266     public void setTintList(ColorStateList tintList) {
    267         mTintColor = tintList;
    268         super.invalidateSelf();
    269     }
    270 
    271     @Override
    272     public void setTintMode(@NonNull PorterDuff.Mode mode) {
    273         mTintMode = mode;
    274         super.invalidateSelf();
    275     }
    276 
    277     @Override
    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      */
    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 
    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
    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
    395     public void invalidateSelf() {
    396         super.invalidateSelf();
    397         mInvalidated = true;
    398     }
    399 
    400     @Override
    401     public boolean isStateful() {
    402         return mFrameColor != null && mFrameColor.isStateful();
    403     }
    404 
    405     @Override
    406     public int getOpacity() {
    407         return PixelFormat.TRANSLUCENT;
    408     }
    409 
    410     @Override
    411     public int getIntrinsicWidth() {
    412         return (mSize <= 0 ? (int) mIntrinsicRadius * 2 : mSize);
    413     }
    414 
    415     @Override
    416     public int getIntrinsicHeight() {
    417         return getIntrinsicWidth();
    418     }
    419 
    420     @Override
    421     public void invalidateDrawable(@NonNull Drawable who) {
    422         invalidateSelf();
    423     }
    424 
    425     @Override
    426     public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
    427         scheduleSelf(what, when);
    428     }
    429 
    430     @Override
    431     public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
    432         unscheduleSelf(what);
    433     }
    434 }
    435