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