1 /* 2 * Copyright (C) 2014 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.mail.bitmap; 17 18 import android.content.res.Resources; 19 import android.graphics.Bitmap; 20 import android.graphics.BitmapShader; 21 import android.graphics.Canvas; 22 import android.graphics.Color; 23 import android.graphics.ColorFilter; 24 import android.graphics.Matrix; 25 import android.graphics.Paint; 26 import android.graphics.Rect; 27 import android.graphics.Shader; 28 import android.graphics.drawable.Drawable; 29 30 import com.android.bitmap.BitmapCache; 31 import com.android.bitmap.RequestKey; 32 import com.android.bitmap.ReusableBitmap; 33 34 import com.android.mail.R; 35 import com.android.mail.bitmap.ContactResolver.ContactDrawableInterface; 36 37 /** 38 * A drawable that encapsulates all the functionality needed to display a contact image, 39 * including request creation/cancelling and data unbinding/re-binding. 40 * <p> 41 * The actual contact resolving and decoding is handled by {@link ContactResolver}. 42 * <p> 43 * For better performance, you should define a cache with {@link #setBitmapCache(BitmapCache)}. 44 */ 45 public abstract class AbstractAvatarDrawable extends Drawable implements ContactDrawableInterface { 46 protected final Resources mResources; 47 48 private BitmapCache mCache; 49 private ContactResolver mContactResolver; 50 51 protected ContactRequest mContactRequest; 52 protected ReusableBitmap mBitmap; 53 54 protected final float mBorderWidth; 55 protected final Paint mBitmapPaint; 56 protected final Paint mBorderPaint; 57 protected final Matrix mMatrix; 58 59 private int mDecodedWidth; 60 private int mDecodedHeight; 61 AbstractAvatarDrawable(final Resources res)62 public AbstractAvatarDrawable(final Resources res) { 63 mResources = res; 64 65 mBitmapPaint = new Paint(); 66 mBitmapPaint.setAntiAlias(true); 67 mBitmapPaint.setFilterBitmap(true); 68 mBitmapPaint.setDither(true); 69 70 mBorderWidth = res.getDimensionPixelSize(R.dimen.avatar_border_width); 71 72 mBorderPaint = new Paint(); 73 mBorderPaint.setColor(Color.TRANSPARENT); 74 mBorderPaint.setStyle(Paint.Style.STROKE); 75 mBorderPaint.setStrokeWidth(mBorderWidth); 76 mBorderPaint.setAntiAlias(true); 77 78 mMatrix = new Matrix(); 79 } 80 setBitmapCache(final BitmapCache cache)81 public void setBitmapCache(final BitmapCache cache) { 82 mCache = cache; 83 } 84 setContactResolver(final ContactResolver contactResolver)85 public void setContactResolver(final ContactResolver contactResolver) { 86 mContactResolver = contactResolver; 87 } 88 89 @Override draw(final Canvas canvas)90 public void draw(final Canvas canvas) { 91 final Rect bounds = getBounds(); 92 if (!isVisible() || bounds.isEmpty()) { 93 return; 94 } 95 96 if (mBitmap != null && mBitmap.bmp != null) { 97 // Draw sender image. 98 drawBitmap(mBitmap.bmp, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(), canvas); 99 } else { 100 // Draw the default image 101 drawDefaultAvatar(canvas); 102 } 103 } 104 drawDefaultAvatar(Canvas canvas)105 protected abstract void drawDefaultAvatar(Canvas canvas); 106 107 /** 108 * Draw the bitmap onto the canvas at the current bounds taking into account the current scale. 109 */ drawBitmap(final Bitmap bitmap, final int width, final int height, final Canvas canvas)110 protected void drawBitmap(final Bitmap bitmap, final int width, final int height, 111 final Canvas canvas) { 112 final Rect bounds = getBounds(); 113 // Draw bitmap through shader first. 114 final BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, 115 Shader.TileMode.CLAMP); 116 mMatrix.reset(); 117 118 // Fit bitmap to bounds. 119 final float boundsWidth = (float) bounds.width(); 120 final float boundsHeight = (float) bounds.height(); 121 final float scale = Math.max(boundsWidth / width, boundsHeight / height); 122 mMatrix.postScale(scale, scale); 123 124 // Translate bitmap to dst bounds. 125 mMatrix.postTranslate(bounds.left, bounds.top); 126 127 shader.setLocalMatrix(mMatrix); 128 mBitmapPaint.setShader(shader); 129 drawCircle(canvas, bounds, mBitmapPaint); 130 131 // Then draw the border. 132 final float radius = bounds.width() / 2f - mBorderWidth / 2; 133 canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, mBorderPaint); 134 } 135 136 /** 137 * Draws the largest circle that fits within the given <code>bounds</code>. 138 * 139 * @param canvas the canvas on which to draw 140 * @param bounds the bounding box of the circle 141 * @param paint the paint with which to draw 142 */ drawCircle(Canvas canvas, Rect bounds, Paint paint)143 protected static void drawCircle(Canvas canvas, Rect bounds, Paint paint) { 144 canvas.drawCircle(bounds.centerX(), bounds.centerY(), bounds.width() / 2, paint); 145 } 146 147 @Override getDecodeWidth()148 public int getDecodeWidth() { 149 return mDecodedWidth; 150 } 151 152 @Override getDecodeHeight()153 public int getDecodeHeight() { 154 return mDecodedHeight; 155 } 156 setDecodeDimensions(final int decodeWidth, final int decodeHeight)157 public void setDecodeDimensions(final int decodeWidth, final int decodeHeight) { 158 mDecodedWidth = decodeWidth; 159 mDecodedHeight = decodeHeight; 160 } 161 unbind()162 public void unbind() { 163 setImage(null); 164 } 165 bind(final String name, final String email)166 public void bind(final String name, final String email) { 167 setImage(new ContactRequest(name, email)); 168 } 169 setImage(final ContactRequest contactRequest)170 private void setImage(final ContactRequest contactRequest) { 171 if (mContactRequest != null && mContactRequest.equals(contactRequest)) { 172 return; 173 } 174 175 if (mBitmap != null) { 176 mBitmap.releaseReference(); 177 mBitmap = null; 178 } 179 180 if (mContactResolver != null) { 181 mContactResolver.remove(mContactRequest, this); 182 } 183 184 mContactRequest = contactRequest; 185 186 if (contactRequest == null) { 187 invalidateSelf(); 188 return; 189 } 190 191 ReusableBitmap cached = null; 192 if (mCache != null) { 193 cached = mCache.get(contactRequest, true /* incrementRefCount */); 194 } 195 196 if (cached != null) { 197 setBitmap(cached); 198 } else { 199 decode(); 200 } 201 } 202 decode()203 private void decode() { 204 if (mContactRequest == null) { 205 return; 206 } 207 // Add to batch. 208 mContactResolver.add(mContactRequest, this); 209 } 210 setBitmap(final ReusableBitmap bmp)211 private void setBitmap(final ReusableBitmap bmp) { 212 if (mBitmap != null && mBitmap != bmp) { 213 mBitmap.releaseReference(); 214 } 215 mBitmap = bmp; 216 invalidateSelf(); 217 } 218 219 @Override onDecodeComplete(RequestKey key, ReusableBitmap result)220 public void onDecodeComplete(RequestKey key, ReusableBitmap result) { 221 final ContactRequest request = (ContactRequest) key; 222 // Remove from batch. 223 mContactResolver.remove(request, this); 224 if (request.equals(mContactRequest)) { 225 setBitmap(result); 226 } else { 227 // if the requests don't match (i.e. this request is stale), decrement the 228 // ref count to allow the bitmap to be pooled 229 if (result != null) { 230 result.releaseReference(); 231 } 232 } 233 } 234 235 @Override setAlpha(int alpha)236 public void setAlpha(int alpha) { 237 mBitmapPaint.setAlpha(alpha); 238 } 239 240 @Override setColorFilter(ColorFilter cf)241 public void setColorFilter(ColorFilter cf) { 242 mBitmapPaint.setColorFilter(cf); 243 } 244 245 @Override getOpacity()246 public int getOpacity() { 247 return 0; 248 } 249 } 250