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 */
16package com.android.mail.bitmap;
17
18import android.content.res.Resources;
19import android.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.graphics.BitmapShader;
22import android.graphics.Canvas;
23import android.graphics.Color;
24import android.graphics.ColorFilter;
25import android.graphics.Matrix;
26import android.graphics.Paint;
27import android.graphics.Paint.Style;
28import android.graphics.Rect;
29import android.graphics.Shader.TileMode;
30import android.graphics.drawable.Drawable;
31
32import com.android.bitmap.BitmapCache;
33import com.android.bitmap.RequestKey;
34import com.android.bitmap.ReusableBitmap;
35import com.android.mail.R;
36import com.android.mail.bitmap.ContactResolver.ContactDrawableInterface;
37
38public class AccountAvatarDrawable extends Drawable implements ContactDrawableInterface {
39
40    private final BitmapCache mCache;
41    private final ContactResolver mContactResolver;
42
43    private ContactRequest mContactRequest;
44    private ReusableBitmap mBitmap;
45    private final float mBorderWidth;
46    private final Paint mBitmapPaint;
47    private final Paint mBorderPaint;
48    private final Matrix mMatrix;
49
50    private int mDecodeWidth;
51    private int mDecodeHeight;
52
53    private static Bitmap DEFAULT_AVATAR = null;
54
55    public AccountAvatarDrawable(final Resources res, final BitmapCache cache,
56            final ContactResolver contactResolver) {
57        mCache = cache;
58        mContactResolver = contactResolver;
59        mBitmapPaint = new Paint();
60        mBitmapPaint.setAntiAlias(true);
61        mBitmapPaint.setFilterBitmap(true);
62        mBitmapPaint.setDither(true);
63
64        mBorderWidth = res.getDimensionPixelSize(R.dimen.avatar_border_width);
65
66        mBorderPaint = new Paint();
67        mBorderPaint.setColor(Color.TRANSPARENT);
68        mBorderPaint.setStyle(Style.STROKE);
69        mBorderPaint.setStrokeWidth(mBorderWidth);
70        mBorderPaint.setAntiAlias(true);
71
72        mMatrix = new Matrix();
73
74        if (DEFAULT_AVATAR == null) {
75            DEFAULT_AVATAR = BitmapFactory.decodeResource(res, R.drawable.avatar_placeholder_gray);
76        }
77    }
78
79    @Override
80    public void draw(final Canvas canvas) {
81        final Rect bounds = getBounds();
82        if (!isVisible() || bounds.isEmpty()) {
83            return;
84        }
85
86        if (mBitmap != null && mBitmap.bmp != null) {
87            // Draw sender image.
88            drawBitmap(mBitmap.bmp, mBitmap.getLogicalWidth(), mBitmap.getLogicalHeight(), canvas);
89        } else {
90            // Draw the default image
91            drawBitmap(DEFAULT_AVATAR, DEFAULT_AVATAR.getWidth(), DEFAULT_AVATAR.getHeight(),
92                    canvas);
93        }
94    }
95
96    /**
97     * Draw the bitmap onto the canvas at the current bounds taking into account the current scale.
98     */
99    private void drawBitmap(final Bitmap bitmap, final int width, final int height,
100            final Canvas canvas) {
101        final Rect bounds = getBounds();
102        // Draw bitmap through shader first.
103        final BitmapShader shader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
104        mMatrix.reset();
105
106        // Fit bitmap to bounds.
107        final float boundsWidth = (float) bounds.width();
108        final float boundsHeight = (float) bounds.height();
109        final float scale = Math.max(boundsWidth / width, boundsHeight / height);
110        mMatrix.postScale(scale, scale);
111
112        // Translate bitmap to dst bounds.
113        mMatrix.postTranslate(bounds.left, bounds.top);
114
115        shader.setLocalMatrix(mMatrix);
116        mBitmapPaint.setShader(shader);
117        canvas.drawCircle(bounds.centerX(), bounds.centerY(), bounds.width() / 2, mBitmapPaint);
118
119        // Then draw the border.
120        final float radius = bounds.width() / 2f - mBorderWidth / 2;
121        canvas.drawCircle(bounds.centerX(), bounds.centerY(), radius, mBorderPaint);
122    }
123
124    @Override
125    public void setAlpha(final int alpha) {
126        mBitmapPaint.setAlpha(alpha);
127    }
128
129    @Override
130    public void setColorFilter(final ColorFilter cf) {
131        mBitmapPaint.setColorFilter(cf);
132    }
133
134    @Override
135    public int getOpacity() {
136        return 0;
137    }
138
139    public void setDecodeDimensions(final int decodeWidth, final int decodeHeight) {
140        mDecodeWidth = decodeWidth;
141        mDecodeHeight = decodeHeight;
142    }
143
144    public void unbind() {
145        setImage(null);
146    }
147
148    public void bind(final String name, final String email) {
149        setImage(new ContactRequest(name, email));
150    }
151
152    private void setImage(final ContactRequest contactRequest) {
153        if (mContactRequest != null && mContactRequest.equals(contactRequest)) {
154            return;
155        }
156
157        if (mBitmap != null) {
158            mBitmap.releaseReference();
159            mBitmap = null;
160        }
161
162        mContactResolver.remove(mContactRequest, this);
163        mContactRequest = contactRequest;
164
165        if (contactRequest == null) {
166            invalidateSelf();
167            return;
168        }
169
170        final ReusableBitmap cached = mCache.get(contactRequest, true /* incrementRefCount */);
171        if (cached != null) {
172            setBitmap(cached);
173        } else {
174            decode();
175        }
176    }
177
178    private void setBitmap(final ReusableBitmap bmp) {
179        if (mBitmap != null && mBitmap != bmp) {
180            mBitmap.releaseReference();
181        }
182        mBitmap = bmp;
183        invalidateSelf();
184    }
185
186    private void decode() {
187        if (mContactRequest == null) {
188            return;
189        }
190        // Add to batch.
191        mContactResolver.add(mContactRequest, this);
192    }
193
194    @Override
195    public int getDecodeWidth() {
196        return mDecodeWidth;
197    }
198
199    @Override
200    public int getDecodeHeight() {
201        return mDecodeHeight;
202    }
203
204    @Override
205    public void onDecodeComplete(final RequestKey key, final ReusableBitmap result) {
206        final ContactRequest request = (ContactRequest) key;
207        // Remove from batch.
208        mContactResolver.remove(request, this);
209        if (request.equals(mContactRequest)) {
210            setBitmap(result);
211        } else {
212            // if the requests don't match (i.e. this request is stale), decrement the
213            // ref count to allow the bitmap to be pooled
214            if (result != null) {
215                result.releaseReference();
216            }
217        }
218    }
219}
220
221