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