LetterTileProvider.java revision 3b965d78774a42358ce6bbdcc43b4c8df130a60e
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
17package com.android.mail.photomanager;
18
19import android.content.Context;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.graphics.Bitmap;
23import android.graphics.BitmapFactory;
24import android.graphics.Canvas;
25import android.graphics.Paint.Align;
26import android.graphics.Rect;
27import android.graphics.Typeface;
28import android.text.TextPaint;
29import android.text.TextUtils;
30
31import com.android.mail.R;
32import com.android.mail.photomanager.ContactPhotoManager.ContactIdentifier;
33import com.android.mail.photomanager.PhotoManager.DefaultImageProvider;
34import com.android.mail.photomanager.PhotoManager.PhotoIdentifier;
35import com.android.mail.ui.DividedImageCanvas;
36import com.android.mail.ui.ImageCanvas;
37import com.android.mail.ui.ImageCanvas.Dimensions;
38import com.android.mail.utils.LogTag;
39import 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 */
49public 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 = contactIdentifier.emailAddress;
99
100        // don't apply again if existing letter is there (and valid)
101        if (dividedImageView.hasImageFor(address)) {
102            return;
103        }
104
105        Bitmap bitmap = null;
106        final String display = !TextUtils.isEmpty(displayName) ? displayName : address;
107        final char firstChar = display.charAt(0);
108        dividedImageView.getDesiredDimensions(address, mDims);
109
110        // get an empty bitmap
111        bitmap = getBitmap(mDims, false /* getDefault */);
112        if (bitmap == null) {
113            LogUtils.w(TAG, "LetterTileProvider width(%d) or height(%d) is 0" +
114                    " for name %s and address %s.",
115                    dividedImageView.getWidth(), dividedImageView.getHeight(), displayName,
116                    address);
117            return;
118        }
119
120        final Canvas c = mCanvas;
121        c.setBitmap(bitmap);
122        c.drawColor(pickColor(address));
123
124        // If its a valid English alphabet letter,
125        // draw the letter on top of the color
126        if (isEnglishLetterOrDigit(firstChar)) {
127            mFirstChar[0] = Character.toUpperCase(firstChar);
128            mPaint.setTextSize(getFontSize(mDims.scale));
129            mPaint.getTextBounds(mFirstChar, 0, 1, mBounds);
130            c.drawText(mFirstChar, 0, 1, 0 + mDims.width / 2,
131                    0 + mDims.height / 2 + (mBounds.bottom - mBounds.top) / 2, mPaint);
132        } else { // draw the generic icon on top
133            c.drawBitmap(getBitmap(mDims, true /* getDefault */), 0, 0, null);
134        }
135
136        dividedImageView.addDivisionImage(bitmap, address);
137    }
138
139    private static boolean isEnglishLetterOrDigit(char c) {
140        return ('A' <= c && c <= 'Z')
141                || ('a' <= c && c <= 'z')
142                || ('0' <= c && c <= '9');
143    }
144
145    private Bitmap getBitmap(final Dimensions d, boolean getDefault) {
146        if (d.width <= 0 || d.height <= 0) {
147            LogUtils.w(TAG,
148                    "LetterTileProvider width(%d) or height(%d) is 0.", d.width, d.height);
149            return null;
150        }
151        final int pos;
152        float scale = d.scale;
153        if (scale == Dimensions.SCALE_ONE) {
154            pos = 0;
155        } else if (scale == Dimensions.SCALE_HALF) {
156            pos = 1;
157        } else {
158            pos = 2;
159        }
160
161        final Bitmap[] cache = (getDefault) ? mDefaultBitmapCache : mBitmapBackgroundCache;
162
163        Bitmap bitmap = cache[pos];
164        // ensure bitmap is suitable for the desired w/h
165        // (two-pane uses two different sets of dimensions depending on pane width)
166        if (bitmap == null || bitmap.getWidth() != d.width || bitmap.getHeight() != d.height) {
167            // create and place the bitmap
168            if (getDefault) {
169                bitmap = BitmapUtil.centerCrop(mDefaultBitmap, d.width, d.height);
170            } else {
171                bitmap = Bitmap.createBitmap(d.width, d.height, Bitmap.Config.ARGB_8888);
172            }
173            cache[pos] = bitmap;
174        }
175        return bitmap;
176    }
177
178    private int getFontSize(float scale)  {
179        if (scale == Dimensions.SCALE_ONE) {
180            return mTileLetterFontSize;
181        } else {
182            return mTileLetterFontSizeSmall;
183        }
184    }
185
186    private int pickColor(String emailAddress) {
187        // String.hashCode() implementation is not supposed to change across java versions, so
188        // this should guarantee the same email address always maps to the same color.
189        int color = Math.abs(emailAddress.hashCode()) % NUM_OF_TILE_COLORS;
190        return mColors.getColor(color, mDefaultColor);
191    }
192}
193