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.Drawable;
     38 
     39 import com.android.settingslib.R;
     40 
     41 /**
     42  * Converts the user avatar icon to a circularly clipped one with an optional badge and frame
     43  */
     44 public 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