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