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