blob: d1f1811cb71a54dcb31fa4f16c52409e43b7b2ec [file] [log] [blame]
Yorke Lee9df5e192014-02-12 14:58:25 -08001/*
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;
Brian Attwellf71ca772014-08-11 22:08:59 -070021import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
Yorke Lee9df5e192014-02-12 14:58:25 -080023import 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;
Yorke Lee9df5e192014-02-12 14:58:25 -080031
32import com.android.contacts.common.R;
Yorke Lee9df5e192014-02-12 14:58:25 -080033
34import junit.framework.Assert;
35
36/**
37 * A drawable that encapsulates all the functionality needed to display a letter tile to
38 * represent a contact image.
39 */
40public 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;
Brian Attwellf71ca772014-08-11 22:08:59 -070051 private static Bitmap DEFAULT_PERSON_AVATAR;
52 private static Bitmap DEFAULT_BUSINESS_AVATAR;
53 private static Bitmap DEFAULT_VOICEMAIL_AVATAR;
Yorke Lee9df5e192014-02-12 14:58:25 -080054
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
guanxiongliu7c30f7b2016-07-21 22:52:08 -070066 /** 54% opacity */
67 private static final int ALPHA = 138;
68
Yorke Lee9df5e192014-02-12 14:58:25 -080069 private int mContactType = TYPE_DEFAULT;
70 private float mScale = 1.0f;
71 private float mOffset = 0.0f;
Yorke Leec4a2a232014-04-28 17:53:42 -070072 private boolean mIsCircle = false;
Yorke Lee9df5e192014-02-12 14:58:25 -080073
Ta-wei Yen75bd9342015-11-05 12:58:43 -080074 private int mColor;
75 private Character mLetter = null;
Yorke Lee9df5e192014-02-12 14:58:25 -080076
Ta-wei Yen75bd9342015-11-05 12:58:43 -080077 public LetterTileDrawable(final Resources res) {
Yorke Lee9df5e192014-02-12 14:58:25 -080078 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);
Brian Attwellf71ca772014-08-11 22:08:59 -070083 DEFAULT_PERSON_AVATAR = BitmapFactory.decodeResource(res,
guanxiongliu7c30f7b2016-07-21 22:52:08 -070084 R.drawable.ic_person_avatar);
Brian Attwellf71ca772014-08-11 22:08:59 -070085 DEFAULT_BUSINESS_AVATAR = BitmapFactory.decodeResource(res,
Brian Attwella8fa55d2014-08-28 22:19:30 -070086 R.drawable.ic_business_white_120dp);
Brian Attwellf71ca772014-08-11 22:08:59 -070087 DEFAULT_VOICEMAIL_AVATAR = BitmapFactory.decodeResource(res,
88 R.drawable.ic_voicemail_avatar);
Yorke Lee9df5e192014-02-12 14:58:25 -080089 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 }
Ta-wei Yen75bd9342015-11-05 12:58:43 -080094 mPaint = new Paint();
95 mPaint.setFilterBitmap(true);
96 mPaint.setDither(true);
97 mColor = sDefaultColor;
Yorke Lee9df5e192014-02-12 14:58:25 -080098 }
99
100 @Override
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 /**
Brian Attwellf71ca772014-08-11 22:08:59 -0700111 * Draw the bitmap onto the canvas at the current bounds taking into account the current scale.
Yorke Lee9df5e192014-02-12 14:58:25 -0800112 */
Brian Attwellf71ca772014-08-11 22:08:59 -0700113 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
Yorke Lee9df5e192014-02-12 14:58:25 -0800116 // 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
Brian Attwellf71ca772014-08-11 22:08:59 -0700127 // Source rectangle remains the entire bounds of the source bitmap.
128 sRect.set(0, 0, width, height);
129
guanxiongliu7c30f7b2016-07-21 22:52:08 -0700130 sPaint.setTextAlign(Align.CENTER);
131 sPaint.setAntiAlias(true);
132 sPaint.setAlpha(ALPHA);
133
134 canvas.drawBitmap(bitmap, sRect, destRect, sPaint);
Yorke Lee9df5e192014-02-12 14:58:25 -0800135 }
136
137 private void drawLetterTile(final Canvas canvas) {
138 // Draw background color.
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800139 sPaint.setColor(mColor);
Yorke Lee9df5e192014-02-12 14:58:25 -0800140
141 sPaint.setAlpha(mPaint.getAlpha());
Yorke Leec4a2a232014-04-28 17:53:42 -0700142 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 }
Yorke Lee9df5e192014-02-12 14:58:25 -0800150
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800151 // Draw letter/digit only if the first character is an english letter or there's a override
152
153 if (mLetter != null) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800154 // Draw letter or digit.
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800155 sFirstChar[0] = mLetter;
Yorke Lee9df5e192014-02-12 14:58:25 -0800156
157 // Scale text by canvas bounds and user selected scaling factor
Yorke Lee9df5e192014-02-12 14:58:25 -0800158 sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension);
Yorke Lee9df5e192014-02-12 14:58:25 -0800159 sPaint.getTextBounds(sFirstChar, 0, 1, sRect);
guanxiongliu7c30f7b2016-07-21 22:52:08 -0700160 sPaint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
Yorke Lee9df5e192014-02-12 14:58:25 -0800161 sPaint.setColor(sTileFontColor);
guanxiongliu7c30f7b2016-07-21 22:52:08 -0700162 sPaint.setAlpha(ALPHA);
Yorke Lee9df5e192014-02-12 14:58:25 -0800163
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(),
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800167 bounds.centerY() + mOffset * bounds.height() - sRect.exactCenterY(),
Yorke Lee9df5e192014-02-12 14:58:25 -0800168 sPaint);
169 } else {
170 // Draw the default image if there is no letter/digit to be drawn
Brian Attwellf71ca772014-08-11 22:08:59 -0700171 final Bitmap bitmap = getBitmapForContactType(mContactType);
172 drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(),
173 canvas);
Yorke Lee9df5e192014-02-12 14:58:25 -0800174 }
175 }
176
Brian Attwellc15a4d12014-06-05 00:16:17 -0700177 public int getColor() {
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800178 return mColor;
Brian Attwellc15a4d12014-06-05 00:16:17 -0700179 }
180
Yorke Lee9df5e192014-02-12 14:58:25 -0800181 /**
182 * Returns a deterministic color based on the provided contact identifier string.
183 */
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.
Brian Attwell5ca1b1d2014-07-15 13:46:25 -0700191 final int color = Math.abs(identifier.hashCode()) % sColors.length();
Yorke Lee9df5e192014-02-12 14:58:25 -0800192 return sColors.getColor(color, sDefaultColor);
193 }
194
Brian Attwellf71ca772014-08-11 22:08:59 -0700195 private static Bitmap getBitmapForContactType(int contactType) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800196 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
208 private static boolean isEnglishLetter(final char c) {
209 return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
210 }
211
212 @Override
213 public void setAlpha(final int alpha) {
214 mPaint.setAlpha(alpha);
215 }
216
217 @Override
218 public void setColorFilter(final ColorFilter cf) {
219 mPaint.setColorFilter(cf);
220 }
221
222 @Override
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 */
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800233 public LetterTileDrawable setScale(float scale) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800234 mScale = scale;
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800235 return this;
Yorke Lee9df5e192014-02-12 14:58:25 -0800236 }
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 */
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800250 public LetterTileDrawable setOffset(float offset) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800251 Assert.assertTrue(offset >= -0.5f && offset <= 0.5f);
252 mOffset = offset;
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800253 return this;
Yorke Lee9df5e192014-02-12 14:58:25 -0800254 }
255
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800256 public LetterTileDrawable setLetter(Character letter){
257 mLetter = letter;
258 return this;
Yorke Lee9df5e192014-02-12 14:58:25 -0800259 }
260
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800261 public LetterTileDrawable setColor(int color){
262 mColor = color;
263 return this;
264 }
265
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
278 public LetterTileDrawable setContactType(int contactType) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800279 mContactType = contactType;
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800280 return this;
Yorke Lee9df5e192014-02-12 14:58:25 -0800281 }
Yorke Leec4a2a232014-04-28 17:53:42 -0700282
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800283 public LetterTileDrawable setIsCircular(boolean isCircle) {
Yorke Leec4a2a232014-04-28 17:53:42 -0700284 mIsCircle = isCircle;
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800285 return this;
Yorke Leec4a2a232014-04-28 17:53:42 -0700286 }
Yorke Lee9df5e192014-02-12 14:58:25 -0800287}