blob: b80fd4f76dc458a77bc4c1ee25e036e5f07ac996 [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
Gary Mai69c182a2016-12-05 13:07:03 -080017package com.android.contacts.lettertiles;
Yorke Lee9df5e192014-02-12 14:58:25 -080018
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;
Gary Mai02c3dee2017-05-05 15:49:11 -070029import android.graphics.drawable.AdaptiveIconDrawable;
Yorke Lee9df5e192014-02-12 14:58:25 -080030import android.graphics.drawable.Drawable;
31import android.text.TextUtils;
Yorke Lee9df5e192014-02-12 14:58:25 -080032
Arthur Wang3f6a2442016-12-05 14:51:59 -080033import com.android.contacts.R;
Yorke Lee9df5e192014-02-12 14:58:25 -080034
Walter Jang34ae3fc2017-02-10 08:37:11 -080035import com.google.common.base.Preconditions;
Yorke Lee9df5e192014-02-12 14:58:25 -080036
37/**
38 * A drawable that encapsulates all the functionality needed to display a letter tile to
39 * represent a contact image.
40 */
41public class LetterTileDrawable extends Drawable {
42
43 private final String TAG = LetterTileDrawable.class.getSimpleName();
44
45 private final Paint mPaint;
46
47 /** Letter tile */
48 private static TypedArray sColors;
49 private static int sDefaultColor;
50 private static int sTileFontColor;
51 private static float sLetterToTileRatio;
Brian Attwellf71ca772014-08-11 22:08:59 -070052 private static Bitmap DEFAULT_PERSON_AVATAR;
53 private static Bitmap DEFAULT_BUSINESS_AVATAR;
54 private static Bitmap DEFAULT_VOICEMAIL_AVATAR;
Yorke Lee9df5e192014-02-12 14:58:25 -080055
56 /** Reusable components to avoid new allocations */
57 private static final Paint sPaint = new Paint();
58 private static final Rect sRect = new Rect();
59 private static final char[] sFirstChar = new char[1];
60
61 /** Contact type constants */
62 public static final int TYPE_PERSON = 1;
63 public static final int TYPE_BUSINESS = 2;
64 public static final int TYPE_VOICEMAIL = 3;
65 public static final int TYPE_DEFAULT = TYPE_PERSON;
66
guanxiongliu7c30f7b2016-07-21 22:52:08 -070067 /** 54% opacity */
68 private static final int ALPHA = 138;
69
Yorke Lee9df5e192014-02-12 14:58:25 -080070 private int mContactType = TYPE_DEFAULT;
71 private float mScale = 1.0f;
72 private float mOffset = 0.0f;
Yorke Leec4a2a232014-04-28 17:53:42 -070073 private boolean mIsCircle = false;
Yorke Lee9df5e192014-02-12 14:58:25 -080074
Ta-wei Yen75bd9342015-11-05 12:58:43 -080075 private int mColor;
76 private Character mLetter = null;
Yorke Lee9df5e192014-02-12 14:58:25 -080077
Ta-wei Yen75bd9342015-11-05 12:58:43 -080078 public LetterTileDrawable(final Resources res) {
Yorke Lee9df5e192014-02-12 14:58:25 -080079 if (sColors == null) {
80 sColors = res.obtainTypedArray(R.array.letter_tile_colors);
81 sDefaultColor = res.getColor(R.color.letter_tile_default_color);
82 sTileFontColor = res.getColor(R.color.letter_tile_font_color);
83 sLetterToTileRatio = res.getFraction(R.dimen.letter_to_tile_ratio, 1, 1);
Brian Attwellf71ca772014-08-11 22:08:59 -070084 DEFAULT_PERSON_AVATAR = BitmapFactory.decodeResource(res,
guanxiongliu7c30f7b2016-07-21 22:52:08 -070085 R.drawable.ic_person_avatar);
Brian Attwellf71ca772014-08-11 22:08:59 -070086 DEFAULT_BUSINESS_AVATAR = BitmapFactory.decodeResource(res,
Brian Attwella8fa55d2014-08-28 22:19:30 -070087 R.drawable.ic_business_white_120dp);
Brian Attwellf71ca772014-08-11 22:08:59 -070088 DEFAULT_VOICEMAIL_AVATAR = BitmapFactory.decodeResource(res,
89 R.drawable.ic_voicemail_avatar);
Yorke Lee9df5e192014-02-12 14:58:25 -080090 sPaint.setTypeface(Typeface.create(
91 res.getString(R.string.letter_tile_letter_font_family), Typeface.NORMAL));
92 sPaint.setTextAlign(Align.CENTER);
93 sPaint.setAntiAlias(true);
94 }
Ta-wei Yen75bd9342015-11-05 12:58:43 -080095 mPaint = new Paint();
96 mPaint.setFilterBitmap(true);
97 mPaint.setDither(true);
98 mColor = sDefaultColor;
Yorke Lee9df5e192014-02-12 14:58:25 -080099 }
100
101 @Override
102 public void draw(final Canvas canvas) {
103 final Rect bounds = getBounds();
104 if (!isVisible() || bounds.isEmpty()) {
105 return;
106 }
107 // Draw letter tile.
108 drawLetterTile(canvas);
109 }
110
111 /**
Brian Attwellf71ca772014-08-11 22:08:59 -0700112 * Draw the bitmap onto the canvas at the current bounds taking into account the current scale.
Yorke Lee9df5e192014-02-12 14:58:25 -0800113 */
Brian Attwellf71ca772014-08-11 22:08:59 -0700114 private void drawBitmap(final Bitmap bitmap, final int width, final int height,
115 final Canvas canvas) {
116 // The bitmap should be drawn in the middle of the canvas without changing its width to
Yorke Lee9df5e192014-02-12 14:58:25 -0800117 // height ratio.
118 final Rect destRect = copyBounds();
119
120 // Crop the destination bounds into a square, scaled and offset as appropriate
121 final int halfLength = (int) (mScale * Math.min(destRect.width(), destRect.height()) / 2);
122
123 destRect.set(destRect.centerX() - halfLength,
124 (int) (destRect.centerY() - halfLength + mOffset * destRect.height()),
125 destRect.centerX() + halfLength,
126 (int) (destRect.centerY() + halfLength + mOffset * destRect.height()));
127
Brian Attwellf71ca772014-08-11 22:08:59 -0700128 // Source rectangle remains the entire bounds of the source bitmap.
129 sRect.set(0, 0, width, height);
130
guanxiongliu7c30f7b2016-07-21 22:52:08 -0700131 sPaint.setTextAlign(Align.CENTER);
132 sPaint.setAntiAlias(true);
133 sPaint.setAlpha(ALPHA);
134
135 canvas.drawBitmap(bitmap, sRect, destRect, sPaint);
Yorke Lee9df5e192014-02-12 14:58:25 -0800136 }
137
138 private void drawLetterTile(final Canvas canvas) {
139 // Draw background color.
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800140 sPaint.setColor(mColor);
Yorke Lee9df5e192014-02-12 14:58:25 -0800141
142 sPaint.setAlpha(mPaint.getAlpha());
Yorke Leec4a2a232014-04-28 17:53:42 -0700143 final Rect bounds = getBounds();
144 final int minDimension = Math.min(bounds.width(), bounds.height());
145
146 if (mIsCircle) {
147 canvas.drawCircle(bounds.centerX(), bounds.centerY(), minDimension / 2, sPaint);
148 } else {
149 canvas.drawRect(bounds, sPaint);
150 }
Yorke Lee9df5e192014-02-12 14:58:25 -0800151
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800152 // Draw letter/digit only if the first character is an english letter or there's a override
153
Yingren Wang1f9f8512019-02-22 14:16:35 +0800154 if (mLetter != null) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800155 // Draw letter or digit.
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800156 sFirstChar[0] = mLetter;
Yorke Lee9df5e192014-02-12 14:58:25 -0800157
158 // Scale text by canvas bounds and user selected scaling factor
Yorke Lee9df5e192014-02-12 14:58:25 -0800159 sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension);
Yorke Lee9df5e192014-02-12 14:58:25 -0800160 sPaint.getTextBounds(sFirstChar, 0, 1, sRect);
guanxiongliu7c30f7b2016-07-21 22:52:08 -0700161 sPaint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL));
Yorke Lee9df5e192014-02-12 14:58:25 -0800162 sPaint.setColor(sTileFontColor);
guanxiongliu7c30f7b2016-07-21 22:52:08 -0700163 sPaint.setAlpha(ALPHA);
Yorke Lee9df5e192014-02-12 14:58:25 -0800164
165 // Draw the letter in the canvas, vertically shifted up or down by the user-defined
166 // offset
167 canvas.drawText(sFirstChar, 0, 1, bounds.centerX(),
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800168 bounds.centerY() + mOffset * bounds.height() - sRect.exactCenterY(),
Yorke Lee9df5e192014-02-12 14:58:25 -0800169 sPaint);
170 } else {
171 // Draw the default image if there is no letter/digit to be drawn
Yingren Wang1f9f8512019-02-22 14:16:35 +0800172 final Bitmap bitmap = getBitmapForContactType(mContactType);
Brian Attwellf71ca772014-08-11 22:08:59 -0700173 drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(),
174 canvas);
Yorke Lee9df5e192014-02-12 14:58:25 -0800175 }
176 }
177
Brian Attwellc15a4d12014-06-05 00:16:17 -0700178 public int getColor() {
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800179 return mColor;
Brian Attwellc15a4d12014-06-05 00:16:17 -0700180 }
181
Yorke Lee9df5e192014-02-12 14:58:25 -0800182 /**
183 * Returns a deterministic color based on the provided contact identifier string.
184 */
185 private int pickColor(final String identifier) {
186 if (TextUtils.isEmpty(identifier) || mContactType == TYPE_VOICEMAIL) {
187 return sDefaultColor;
188 }
189 // String.hashCode() implementation is not supposed to change across java versions, so
190 // this should guarantee the same email address always maps to the same color.
191 // The email should already have been normalized by the ContactRequest.
Brian Attwell5ca1b1d2014-07-15 13:46:25 -0700192 final int color = Math.abs(identifier.hashCode()) % sColors.length();
Yorke Lee9df5e192014-02-12 14:58:25 -0800193 return sColors.getColor(color, sDefaultColor);
194 }
195
Yingren Wang1f9f8512019-02-22 14:16:35 +0800196 private static Bitmap getBitmapForContactType(int contactType) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800197 switch (contactType) {
198 case TYPE_PERSON:
199 return DEFAULT_PERSON_AVATAR;
200 case TYPE_BUSINESS:
201 return DEFAULT_BUSINESS_AVATAR;
202 case TYPE_VOICEMAIL:
203 return DEFAULT_VOICEMAIL_AVATAR;
204 default:
205 return DEFAULT_PERSON_AVATAR;
206 }
207 }
208
209 private static boolean isEnglishLetter(final char c) {
210 return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
211 }
212
213 @Override
214 public void setAlpha(final int alpha) {
215 mPaint.setAlpha(alpha);
216 }
217
218 @Override
219 public void setColorFilter(final ColorFilter cf) {
220 mPaint.setColorFilter(cf);
221 }
222
223 @Override
224 public int getOpacity() {
225 return android.graphics.PixelFormat.OPAQUE;
226 }
227
228 /**
229 * Scale the drawn letter tile to a ratio of its default size
230 *
231 * @param scale The ratio the letter tile should be scaled to as a percentage of its default
232 * size, from a scale of 0 to 2.0f. The default is 1.0f.
233 */
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800234 public LetterTileDrawable setScale(float scale) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800235 mScale = scale;
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800236 return this;
Yorke Lee9df5e192014-02-12 14:58:25 -0800237 }
238
239 /**
240 * Assigns the vertical offset of the position of the letter tile to the ContactDrawable
241 *
242 * @param offset The provided offset must be within the range of -0.5f to 0.5f.
243 * If set to -0.5f, the letter will be shifted upwards by 0.5 times the height of the canvas
244 * it is being drawn on, which means it will be drawn with the center of the letter starting
245 * at the top edge of the canvas.
246 * If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of the canvas
247 * it is being drawn on, which means it will be drawn with the center of the letter starting
248 * at the bottom edge of the canvas.
249 * The default is 0.0f.
250 */
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800251 public LetterTileDrawable setOffset(float offset) {
Walter Jang34ae3fc2017-02-10 08:37:11 -0800252 Preconditions.checkArgument(offset >= -0.5f && offset <= 0.5f);
Yorke Lee9df5e192014-02-12 14:58:25 -0800253 mOffset = offset;
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800254 return this;
Yorke Lee9df5e192014-02-12 14:58:25 -0800255 }
256
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800257 public LetterTileDrawable setLetter(Character letter){
258 mLetter = letter;
259 return this;
Yorke Lee9df5e192014-02-12 14:58:25 -0800260 }
261
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800262 public LetterTileDrawable setColor(int color){
263 mColor = color;
264 return this;
265 }
266
267 public LetterTileDrawable setLetterAndColorFromContactDetails(final String displayName,
268 final String identifier) {
269 if (displayName != null && displayName.length() > 0
270 && isEnglishLetter(displayName.charAt(0))) {
271 mLetter = Character.toUpperCase(displayName.charAt(0));
272 }else{
273 mLetter = null;
274 }
275 mColor = pickColor(identifier);
276 return this;
277 }
278
279 public LetterTileDrawable setContactType(int contactType) {
Yorke Lee9df5e192014-02-12 14:58:25 -0800280 mContactType = contactType;
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800281 return this;
Yorke Lee9df5e192014-02-12 14:58:25 -0800282 }
Yorke Leec4a2a232014-04-28 17:53:42 -0700283
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800284 public LetterTileDrawable setIsCircular(boolean isCircle) {
Yorke Leec4a2a232014-04-28 17:53:42 -0700285 mIsCircle = isCircle;
Ta-wei Yen75bd9342015-11-05 12:58:43 -0800286 return this;
Yorke Leec4a2a232014-04-28 17:53:42 -0700287 }
Gary Mai02c3dee2017-05-05 15:49:11 -0700288
289 /**
290 * Returns the scale percentage as a float for LetterTileDrawables used in AdaptiveIcons.
291 */
292 public static float getAdaptiveIconScale() {
293 return 1 / (1 + (2 * AdaptiveIconDrawable.getExtraInsetFraction()));
294 }
Yorke Lee9df5e192014-02-12 14:58:25 -0800295}