diff options
Diffstat (limited to 'java/com/android/contacts/common/lettertiles')
-rw-r--r-- | java/com/android/contacts/common/lettertiles/LetterTileDrawable.java | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/java/com/android/contacts/common/lettertiles/LetterTileDrawable.java b/java/com/android/contacts/common/lettertiles/LetterTileDrawable.java new file mode 100644 index 000000000..7e1839c1e --- /dev/null +++ b/java/com/android/contacts/common/lettertiles/LetterTileDrawable.java @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.contacts.common.lettertiles; + +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Outline; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Rect; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.support.annotation.IntDef; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import com.android.contacts.common.R; +import com.android.dialer.common.Assert; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * A drawable that encapsulates all the functionality needed to display a letter tile to represent a + * contact image. + */ +public class LetterTileDrawable extends Drawable { + + /** + * ContactType indicates the avatar type of the contact. For a person or for the default when no + * name is provided, it is {@link #TYPE_DEFAULT}, otherwise, for a business it is {@link + * #TYPE_BUSINESS}, and voicemail contacts should use {@link #TYPE_VOICEMAIL}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({TYPE_PERSON, TYPE_BUSINESS, TYPE_VOICEMAIL}) + public @interface ContactType {} + + /** Contact type constants */ + public static final int TYPE_PERSON = 1; + public static final int TYPE_BUSINESS = 2; + public static final int TYPE_VOICEMAIL = 3; + @ContactType public static final int TYPE_DEFAULT = TYPE_PERSON; + + /** + * Shape indicates the letter tile shape. It can be either a {@link #SHAPE_CIRCLE}, otherwise, it + * is a {@link #SHAPE_RECTANGLE}. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({SHAPE_CIRCLE, SHAPE_RECTANGLE}) + public @interface Shape {} + + /** Shape constants */ + public static final int SHAPE_CIRCLE = 1; + + public static final int SHAPE_RECTANGLE = 2; + + /** 54% opacity */ + private static final int ALPHA = 138; + + /** Reusable components to avoid new allocations */ + private static final Paint sPaint = new Paint(); + + private static final Rect sRect = new Rect(); + private static final char[] sFirstChar = new char[1]; + /** Letter tile */ + private static TypedArray sColors; + + private static int sDefaultColor; + private static int sTileFontColor; + private static float sLetterToTileRatio; + private static Bitmap sDefaultPersonAvatar; + private static Bitmap sDefaultBusinessAvatar; + private static Bitmap sDefaultVoicemailAvatar; + private static final String TAG = LetterTileDrawable.class.getSimpleName(); + private final Paint mPaint; + private int mContactType = TYPE_DEFAULT; + private float mScale = 1.0f; + private float mOffset = 0.0f; + private boolean mIsCircle = false; + + private int mColor; + private Character mLetter = null; + + private boolean mAvatarWasVoicemailOrBusiness = false; + private String mDisplayName; + + public LetterTileDrawable(final Resources res) { + if (sColors == null) { + sColors = res.obtainTypedArray(R.array.letter_tile_colors); + sDefaultColor = res.getColor(R.color.letter_tile_default_color); + sTileFontColor = res.getColor(R.color.letter_tile_font_color); + sLetterToTileRatio = res.getFraction(R.dimen.letter_to_tile_ratio, 1, 1); + sDefaultPersonAvatar = + BitmapFactory.decodeResource( + res, R.drawable.product_logo_avatar_anonymous_white_color_120); + sDefaultBusinessAvatar = + BitmapFactory.decodeResource(res, R.drawable.ic_business_white_120dp); + sDefaultVoicemailAvatar = BitmapFactory.decodeResource(res, R.drawable.ic_voicemail_avatar); + sPaint.setTypeface( + Typeface.create(res.getString(R.string.letter_tile_letter_font_family), Typeface.NORMAL)); + sPaint.setTextAlign(Align.CENTER); + sPaint.setAntiAlias(true); + } + mPaint = new Paint(); + mPaint.setFilterBitmap(true); + mPaint.setDither(true); + mColor = sDefaultColor; + } + + private static Bitmap getBitmapForContactType(int contactType) { + switch (contactType) { + case TYPE_BUSINESS: + return sDefaultBusinessAvatar; + case TYPE_VOICEMAIL: + return sDefaultVoicemailAvatar; + case TYPE_PERSON: + default: + return sDefaultPersonAvatar; + } + } + + private static boolean isEnglishLetter(final char c) { + return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z'); + } + + @Override + public void draw(final Canvas canvas) { + final Rect bounds = getBounds(); + if (!isVisible() || bounds.isEmpty()) { + return; + } + // Draw letter tile. + drawLetterTile(canvas); + } + + /** + * Draw the bitmap onto the canvas at the current bounds taking into account the current scale. + */ + private void drawBitmap( + final Bitmap bitmap, final int width, final int height, final Canvas canvas) { + // The bitmap should be drawn in the middle of the canvas without changing its width to + // height ratio. + final Rect destRect = copyBounds(); + + // Crop the destination bounds into a square, scaled and offset as appropriate + final int halfLength = (int) (mScale * Math.min(destRect.width(), destRect.height()) / 2); + + destRect.set( + destRect.centerX() - halfLength, + (int) (destRect.centerY() - halfLength + mOffset * destRect.height()), + destRect.centerX() + halfLength, + (int) (destRect.centerY() + halfLength + mOffset * destRect.height())); + + // Source rectangle remains the entire bounds of the source bitmap. + sRect.set(0, 0, width, height); + + sPaint.setTextAlign(Align.CENTER); + sPaint.setAntiAlias(true); + sPaint.setAlpha(ALPHA); + + canvas.drawBitmap(bitmap, sRect, destRect, sPaint); + } + + private void drawLetterTile(final Canvas canvas) { + // Draw background color. + sPaint.setColor(mColor); + + sPaint.setAlpha(mPaint.getAlpha()); + final Rect bounds = getBounds(); + final int minDimension = Math.min(bounds.width(), bounds.height()); + + if (mIsCircle) { + canvas.drawCircle(bounds.centerX(), bounds.centerY(), minDimension / 2, sPaint); + } else { + canvas.drawRect(bounds, sPaint); + } + + // Draw letter/digit only if the first character is an english letter or there's a override + + if (mLetter != null) { + // Draw letter or digit. + sFirstChar[0] = mLetter; + + // Scale text by canvas bounds and user selected scaling factor + sPaint.setTextSize(mScale * sLetterToTileRatio * minDimension); + sPaint.getTextBounds(sFirstChar, 0, 1, sRect); + sPaint.setTypeface(Typeface.create("sans-serif", Typeface.NORMAL)); + sPaint.setColor(sTileFontColor); + sPaint.setAlpha(ALPHA); + + // Draw the letter in the canvas, vertically shifted up or down by the user-defined + // offset + canvas.drawText( + sFirstChar, + 0, + 1, + bounds.centerX(), + bounds.centerY() + mOffset * bounds.height() - sRect.exactCenterY(), + sPaint); + } else { + // Draw the default image if there is no letter/digit to be drawn + final Bitmap bitmap = getBitmapForContactType(mContactType); + drawBitmap(bitmap, bitmap.getWidth(), bitmap.getHeight(), canvas); + } + } + + public int getColor() { + return mColor; + } + + public LetterTileDrawable setColor(int color) { + mColor = color; + return this; + } + + /** Returns a deterministic color based on the provided contact identifier string. */ + private int pickColor(final String identifier) { + if (TextUtils.isEmpty(identifier) || mContactType == TYPE_VOICEMAIL) { + return sDefaultColor; + } + // String.hashCode() implementation is not supposed to change across java versions, so + // this should guarantee the same email address always maps to the same color. + // The email should already have been normalized by the ContactRequest. + final int color = Math.abs(identifier.hashCode()) % sColors.length(); + return sColors.getColor(color, sDefaultColor); + } + + @Override + public void setAlpha(final int alpha) { + mPaint.setAlpha(alpha); + } + + @Override + public void setColorFilter(final ColorFilter cf) { + mPaint.setColorFilter(cf); + } + + @Override + public int getOpacity() { + return android.graphics.PixelFormat.OPAQUE; + } + + @Override + public void getOutline(Outline outline) { + if (mIsCircle) { + outline.setOval(getBounds()); + } else { + outline.setRect(getBounds()); + } + + outline.setAlpha(1); + } + + /** + * Scale the drawn letter tile to a ratio of its default size + * + * @param scale The ratio the letter tile should be scaled to as a percentage of its default size, + * from a scale of 0 to 2.0f. The default is 1.0f. + */ + public LetterTileDrawable setScale(float scale) { + mScale = scale; + return this; + } + + /** + * Assigns the vertical offset of the position of the letter tile to the ContactDrawable + * + * @param offset The provided offset must be within the range of -0.5f to 0.5f. If set to -0.5f, + * the letter will be shifted upwards by 0.5 times the height of the canvas it is being drawn + * on, which means it will be drawn with the center of the letter starting at the top edge of + * the canvas. If set to 0.5f, the letter will be shifted downwards by 0.5 times the height of + * the canvas it is being drawn on, which means it will be drawn with the center of the letter + * starting at the bottom edge of the canvas. The default is 0.0f. + */ + public LetterTileDrawable setOffset(float offset) { + Assert.checkArgument(offset >= -0.5f && offset <= 0.5f); + mOffset = offset; + return this; + } + + public LetterTileDrawable setLetter(Character letter) { + mLetter = letter; + return this; + } + + public Character getLetter() { + return this.mLetter; + } + + private LetterTileDrawable setLetterAndColorFromContactDetails( + final String displayName, final String identifier) { + if (displayName != null && displayName.length() > 0 && isEnglishLetter(displayName.charAt(0))) { + mLetter = Character.toUpperCase(displayName.charAt(0)); + } else { + mLetter = null; + } + mColor = pickColor(identifier); + return this; + } + + public LetterTileDrawable setContactType(@ContactType int contactType) { + mContactType = contactType; + return this; + } + + @ContactType + public int getContactType() { + return this.mContactType; + } + + public LetterTileDrawable setIsCircular(boolean isCircle) { + mIsCircle = isCircle; + return this; + } + + /** + * Creates a canonical letter tile for use across dialer fragments. + * + * @param displayName The display name to produce the letter in the tile. Null values or numbers + * yield no letter. + * @param identifierForTileColor The string used to produce the tile color. + * @param shape The shape of the tile. + * @param contactType The type of contact, e.g. TYPE_VOICEMAIL. + * @return this + */ + public LetterTileDrawable setCanonicalDialerLetterTileDetails( + @Nullable final String displayName, + @Nullable final String identifierForTileColor, + @Shape final int shape, + final int contactType) { + setContactType(contactType); + /** + * During hangup, we lose the call state for special types of contacts, like voicemail. To help + * callers avoid extraneous LetterTileDrawable allocations, we keep track of the special case + * until we encounter a new display name. + */ + if (contactType == TYPE_VOICEMAIL || contactType == TYPE_BUSINESS) { + this.mAvatarWasVoicemailOrBusiness = true; + } else if (displayName != null && !displayName.equals(mDisplayName)) { + this.mAvatarWasVoicemailOrBusiness = false; + } + this.mDisplayName = displayName; + if (shape == SHAPE_CIRCLE) { + this.setIsCircular(true); + } else { + this.setIsCircular(false); + } + + /** + * To preserve style, we don't use contactType to set the tile icon. In the future, when all + * callers surface this detail, we can use this to better style the tile icon. + */ + if (mAvatarWasVoicemailOrBusiness) { + this.setLetterAndColorFromContactDetails(null, displayName); + return this; + } else { + if (identifierForTileColor != null) { + this.setLetterAndColorFromContactDetails(displayName, identifierForTileColor); + return this; + } else { + this.setLetterAndColorFromContactDetails(displayName, displayName); + return this; + } + } + } +} |