LetterTileDrawable.java revision 48b20b26feb886d9e2926ce60316509274ffd831
1/*
2 * Copyright (C) 2013 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
17package com.android.contacts.common.lettertiles;
18
19import android.content.res.Resources;
20import android.content.res.TypedArray;
21import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
23import android.graphics.Canvas;
24import android.graphics.ColorFilter;
25import android.graphics.Paint;
26import android.graphics.Paint.Align;
27import android.graphics.Rect;
28import android.graphics.Typeface;
29import android.graphics.drawable.Drawable;
30import android.text.TextUtils;
31import android.util.Log;
32
33import com.android.contacts.common.R;
34import com.android.contacts.common.util.BitmapUtil;
35
36import junit.framework.Assert;
37
38/**
39 * A drawable that encapsulates all the functionality needed to display a letter tile to
40 * represent a contact image.
41 */
42public class LetterTileDrawable extends Drawable {
43
44    private final String TAG = LetterTileDrawable.class.getSimpleName();
45
46    private final Paint mPaint;
47
48    /** Letter tile */
49    private static TypedArray sColors;
50    private static int sDefaultColor;
51    private static int sTileFontColor;
52    private static float sLetterToTileRatio;
53    private static Bitmap DEFAULT_PERSON_AVATAR;
54    private static Bitmap DEFAULT_BUSINESS_AVATAR;
55    private static Bitmap DEFAULT_VOICEMAIL_AVATAR;
56
57    /** Reusable components to avoid new allocations */
58    private static final Paint sPaint = new Paint();
59    private static final Rect sRect = new Rect();
60    private static final char[] sFirstChar = new char[1];
61
62    /** Contact type constants */
63    public static final int TYPE_PERSON = 1;
64    public static final int TYPE_BUSINESS = 2;
65    public static final int TYPE_VOICEMAIL = 3;
66    public static final int TYPE_DEFAULT = TYPE_PERSON;
67
68    private String mDisplayName;
69    private String mIdentifier;
70    private int mContactType = TYPE_DEFAULT;
71    private float mScale = 1.0f;
72    private float mOffset = 0.0f;
73    private boolean mIsCircle = false;
74
75    public LetterTileDrawable(final Resources res) {
76        mPaint = new Paint();
77        mPaint.setFilterBitmap(true);
78        mPaint.setDither(true);
79
80        if (sColors == null) {
81            sColors = res.obtainTypedArray(R.array.letter_tile_colors);
82            sDefaultColor = res.getColor(R.color.letter_tile_default_color);
83            sTileFontColor = res.getColor(R.color.letter_tile_font_color);
84            sLetterToTileRatio = res.getFraction(R.dimen.letter_to_tile_ratio, 1, 1);
85            DEFAULT_PERSON_AVATAR = BitmapFactory.decodeResource(res,
86                    R.drawable.ic_person_white_120dp);
87            DEFAULT_BUSINESS_AVATAR = BitmapFactory.decodeResource(res,
88                    R.drawable.ic_business_white_120dp);
89            DEFAULT_VOICEMAIL_AVATAR = BitmapFactory.decodeResource(res,
90                    R.drawable.ic_voicemail_avatar);
91            sPaint.setTypeface(Typeface.create(
92                    res.getString(R.string.letter_tile_letter_font_family), Typeface.NORMAL));
93            sPaint.setTextAlign(Align.CENTER);
94            sPaint.setAntiAlias(true);
95        }
96    }
97
98    @Override
99    public void draw(final Canvas canvas) {
100        final Rect bounds = getBounds();
101        if (!isVisible() || bounds.isEmpty()) {
102            return;
103        }
104        // Draw letter tile.
105        drawLetterTile(canvas);
106    }
107
108    /**
109     * Draw the bitmap onto the canvas at the current bounds taking into account the current scale.
110     */
111    private void drawBitmap(final Bitmap bitmap, final int width, final int height,
112            final Canvas canvas) {
113        // The bitmap should be drawn in the middle of the canvas without changing its width to
114        // height ratio.
115        final Rect destRect = copyBounds();
116
117        // Crop the destination bounds into a square, scaled and offset as appropriate
118        final int halfLength = (int) (mScale * Math.min(destRect.width(), destRect.height()) / 2);
119
120        destRect.set(destRect.centerX() - halfLength,
121                (int) (destRect.centerY() - halfLength + mOffset * destRect.height()),
122                destRect.centerX() + halfLength,
123                (int) (destRect.centerY() + halfLength + mOffset * destRect.height()));
124
125        // Source rectangle remains the entire bounds of the source bitmap.
126        sRect.set(0, 0, width, height);
127
128        canvas.drawBitmap(bitmap, sRect, destRect, mPaint);
129    }
130
131    private void drawLetterTile(final Canvas canvas) {
132        // Draw background color.
133        sPaint.setColor(pickColor(mIdentifier));
134
135        sPaint.setAlpha(mPaint.getAlpha());
136        final Rect bounds = getBounds();
137        final int minDimension = Math.min(bounds.width(), bounds.height());
138
139        if (mIsCircle) {
140            canvas.drawCircle(bounds.centerX(), bounds.centerY(), minDimension / 2, sPaint);
141        } else {
142            canvas.drawRect(bounds, sPaint);
143        }
144
145        // Draw letter/digit only if the first character is an english letter
146        if (mDisplayName != null && isEnglishLetter(mDisplayName.charAt(0))) {
147            // Draw letter or digit.
148            sFirstChar[0] = Character.toUpperCase(mDisplayName.charAt(0));
149
150            // Scale text by canvas bounds and user selected scaling factor
151            sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension);
152            //sPaint.setTextSize(sTileLetterFontSize);
153            sPaint.getTextBounds(sFirstChar, 0, 1, sRect);
154            sPaint.setColor(sTileFontColor);
155
156            // Draw the letter in the canvas, vertically shifted up or down by the user-defined
157            // offset
158            canvas.drawText(sFirstChar, 0, 1, bounds.centerX(),
159                    bounds.centerY() + mOffset * bounds.height() + sRect.height() / 2,
160                    sPaint);
161        } else {
162            // Draw the default image if there is no letter/digit to be drawn
163            final Bitmap bitmap = getBitmapForContactType(mContactType);
164            drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(),
165                    canvas);
166        }
167    }
168
169    public int getColor() {
170        return pickColor(mIdentifier);
171    }
172
173    /**
174     * Returns a deterministic color based on the provided contact identifier string.
175     */
176    private int pickColor(final String identifier) {
177        if (TextUtils.isEmpty(identifier) || mContactType == TYPE_VOICEMAIL) {
178            return sDefaultColor;
179        }
180        // String.hashCode() implementation is not supposed to change across java versions, so
181        // this should guarantee the same email address always maps to the same color.
182        // The email should already have been normalized by the ContactRequest.
183        final int color = Math.abs(identifier.hashCode()) % sColors.length();
184        return sColors.getColor(color, sDefaultColor);
185    }
186
187    private static Bitmap getBitmapForContactType(int contactType) {
188        switch (contactType) {
189            case TYPE_PERSON:
190                return DEFAULT_PERSON_AVATAR;
191            case TYPE_BUSINESS:
192                return DEFAULT_BUSINESS_AVATAR;
193            case TYPE_VOICEMAIL:
194                return DEFAULT_VOICEMAIL_AVATAR;
195            default:
196                return DEFAULT_PERSON_AVATAR;
197        }
198    }
199
200    private static boolean isEnglishLetter(final char c) {
201        return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
202    }
203
204    @Override
205    public void setAlpha(final int alpha) {
206        mPaint.setAlpha(alpha);
207    }
208
209    @Override
210    public void setColorFilter(final ColorFilter cf) {
211        mPaint.setColorFilter(cf);
212    }
213
214    @Override
215    public int getOpacity() {
216        return android.graphics.PixelFormat.OPAQUE;
217    }
218
219    /**
220     * Scale the drawn letter tile to a ratio of its default size
221     *
222     * @param scale The ratio the letter tile should be scaled to as a percentage of its default
223     * size, from a scale of 0 to 2.0f. The default is 1.0f.
224     */
225    public void setScale(float scale) {
226        mScale = scale;
227    }
228
229    /**
230     * Assigns the vertical offset of the position of the letter tile to the ContactDrawable
231     *
232     * @param offset The provided offset must be within the range of -0.5f to 0.5f.
233     * If set to -0.5f, the letter will be shifted upwards by 0.5 times the height of the canvas
234     * it is being drawn on, which means it will be drawn with the center of the letter starting
235     * at the top edge of the canvas.
236     * If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of the canvas
237     * it is being drawn on, which means it will be drawn with the center of the letter starting
238     * at the bottom edge of the canvas.
239     * The default is 0.0f.
240     */
241    public void setOffset(float offset) {
242        Assert.assertTrue(offset >= -0.5f && offset <= 0.5f);
243        mOffset = offset;
244    }
245
246    public void setContactDetails(final String displayName, final String identifier) {
247        mDisplayName = displayName;
248        mIdentifier = identifier;
249    }
250
251    public void setContactType(int contactType) {
252        mContactType = contactType;
253    }
254
255    public void setIsCircular(boolean isCircle) {
256        mIsCircle = isCircle;
257    }
258}
259