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.mail.photomanager; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.content.res.TypedArray; 22 import android.graphics.Bitmap; 23 import android.graphics.BitmapFactory; 24 import android.graphics.Canvas; 25 import android.graphics.Paint.Align; 26 import android.graphics.Rect; 27 import android.graphics.Typeface; 28 import android.text.TextPaint; 29 import android.text.TextUtils; 30 31 import com.android.mail.R; 32 import com.android.mail.photomanager.ContactPhotoManager.ContactIdentifier; 33 import com.android.mail.photomanager.PhotoManager.DefaultImageProvider; 34 import com.android.mail.photomanager.PhotoManager.PhotoIdentifier; 35 import com.android.mail.ui.DividedImageCanvas; 36 import com.android.mail.ui.ImageCanvas; 37 import com.android.mail.ui.ImageCanvas.Dimensions; 38 import com.android.mail.utils.LogTag; 39 import com.android.mail.utils.LogUtils; 40 41 /** 42 * LetterTileProvider is an implementation of the DefaultImageProvider. When no 43 * matching contact photo is found, and there is a supplied displayName or email 44 * address whose first letter corresponds to an English alphabet letter (or 45 * number), this method creates a bitmap with the letter in the center of a 46 * tile. If there is no English alphabet character (or digit), it creates a 47 * bitmap with the default contact avatar. 48 */ 49 public class LetterTileProvider implements DefaultImageProvider { 50 private static final String TAG = LogTag.getLogTag(); 51 private final Bitmap mDefaultBitmap; 52 private final Bitmap[] mBitmapBackgroundCache; 53 private final Bitmap[] mDefaultBitmapCache; 54 private final Typeface mSansSerifLight; 55 private final Rect mBounds; 56 private final int mTileLetterFontSize; 57 private final int mTileLetterFontSizeSmall; 58 private final int mTileFontColor; 59 private final TextPaint mPaint = new TextPaint(); 60 private final TypedArray mColors; 61 private final int mDefaultColor; 62 private final Canvas mCanvas = new Canvas(); 63 private final Dimensions mDims = new Dimensions(); 64 private final char[] mFirstChar = new char[1]; 65 66 private static final int POSSIBLE_BITMAP_SIZES = 3; 67 68 // This should match the total number of colors defined in colors.xml for letter_tile_color 69 private static final int NUM_OF_TILE_COLORS = 8; 70 71 public LetterTileProvider(Context context) { 72 final Resources res = context.getResources(); 73 mTileLetterFontSize = res.getDimensionPixelSize(R.dimen.tile_letter_font_size); 74 mTileLetterFontSizeSmall = res 75 .getDimensionPixelSize(R.dimen.tile_letter_font_size_small); 76 mTileFontColor = res.getColor(R.color.letter_tile_font_color); 77 mSansSerifLight = Typeface.create("sans-serif-light", Typeface.NORMAL); 78 mBounds = new Rect(); 79 mPaint.setTypeface(mSansSerifLight); 80 mPaint.setColor(mTileFontColor); 81 mPaint.setTextAlign(Align.CENTER); 82 mPaint.setAntiAlias(true); 83 mBitmapBackgroundCache = new Bitmap[POSSIBLE_BITMAP_SIZES]; 84 85 mDefaultBitmap = BitmapFactory.decodeResource(res, R.drawable.ic_generic_man); 86 mDefaultBitmapCache = new Bitmap[POSSIBLE_BITMAP_SIZES]; 87 88 mColors = res.obtainTypedArray(R.array.letter_tile_colors); 89 mDefaultColor = res.getColor(R.color.letter_tile_default_color); 90 } 91 92 @Override 93 public void applyDefaultImage(PhotoIdentifier id, ImageCanvas view, int extent) { 94 ContactIdentifier contactIdentifier = (ContactIdentifier) id; 95 DividedImageCanvas dividedImageView = (DividedImageCanvas) view; 96 97 final String displayName = contactIdentifier.name; 98 final String address = (String) contactIdentifier.getKey(); 99 100 // don't apply again if existing letter is there (and valid) 101 if (dividedImageView.hasImageFor(address)) { 102 return; 103 } 104 105 dividedImageView.getDesiredDimensions(address, mDims); 106 107 final Bitmap bitmap = getLetterTile(mDims, displayName, address); 108 109 if (bitmap == null) { 110 return; 111 } 112 113 dividedImageView.addDivisionImage(bitmap, address); 114 } 115 116 public Bitmap getLetterTile(final Dimensions dimensions, final String displayName, 117 final String address) { 118 final String display = !TextUtils.isEmpty(displayName) ? displayName : address; 119 final char firstChar = display.charAt(0); 120 121 // get an empty bitmap 122 final Bitmap bitmap = getBitmap(dimensions, false /* getDefault */); 123 if (bitmap == null) { 124 LogUtils.w(TAG, "LetterTileProvider width(%d) or height(%d) is 0 for name %s and " 125 + "address %s.", dimensions.width, dimensions.height, displayName, address); 126 return null; 127 } 128 129 final Canvas c = mCanvas; 130 c.setBitmap(bitmap); 131 c.drawColor(pickColor(address)); 132 133 // If its a valid English alphabet letter, 134 // draw the letter on top of the color 135 if (isEnglishLetterOrDigit(firstChar)) { 136 mFirstChar[0] = Character.toUpperCase(firstChar); 137 mPaint.setTextSize(getFontSize(dimensions.scale)); 138 mPaint.getTextBounds(mFirstChar, 0, 1, mBounds); 139 c.drawText(mFirstChar, 0, 1, 0 + dimensions.width / 2, 140 0 + dimensions.height / 2 + (mBounds.bottom - mBounds.top) / 2, mPaint); 141 } else { // draw the generic icon on top 142 c.drawBitmap(getBitmap(dimensions, true /* getDefault */), 0, 0, null); 143 } 144 145 return bitmap; 146 } 147 148 private static boolean isEnglishLetterOrDigit(char c) { 149 return ('A' <= c && c <= 'Z') 150 || ('a' <= c && c <= 'z') 151 || ('0' <= c && c <= '9'); 152 } 153 154 private Bitmap getBitmap(final Dimensions d, boolean getDefault) { 155 if (d.width <= 0 || d.height <= 0) { 156 LogUtils.w(TAG, 157 "LetterTileProvider width(%d) or height(%d) is 0.", d.width, d.height); 158 return null; 159 } 160 final int pos; 161 float scale = d.scale; 162 if (scale == Dimensions.SCALE_ONE) { 163 pos = 0; 164 } else if (scale == Dimensions.SCALE_HALF) { 165 pos = 1; 166 } else { 167 pos = 2; 168 } 169 170 final Bitmap[] cache = (getDefault) ? mDefaultBitmapCache : mBitmapBackgroundCache; 171 172 Bitmap bitmap = cache[pos]; 173 // ensure bitmap is suitable for the desired w/h 174 // (two-pane uses two different sets of dimensions depending on pane width) 175 if (bitmap == null || bitmap.getWidth() != d.width || bitmap.getHeight() != d.height) { 176 // create and place the bitmap 177 if (getDefault) { 178 bitmap = BitmapUtil.centerCrop(mDefaultBitmap, d.width, d.height); 179 } else { 180 bitmap = Bitmap.createBitmap(d.width, d.height, Bitmap.Config.ARGB_8888); 181 } 182 cache[pos] = bitmap; 183 } 184 return bitmap; 185 } 186 187 private int getFontSize(float scale) { 188 if (scale == Dimensions.SCALE_ONE) { 189 return mTileLetterFontSize; 190 } else { 191 return mTileLetterFontSizeSmall; 192 } 193 } 194 195 private int pickColor(String emailAddress) { 196 // String.hashCode() implementation is not supposed to change across java versions, so 197 // this should guarantee the same email address always maps to the same color. 198 int color = Math.abs(emailAddress.hashCode()) % NUM_OF_TILE_COLORS; 199 return mColors.getColor(color, mDefaultColor); 200 } 201 } 202