From 3feaeb6903964a3d8ff53bf8f1e7e8a8c532bcaf Mon Sep 17 00:00:00 2001 From: Stephan Fuhrmann Date: Sun, 21 Jul 2013 16:58:38 +0200 Subject: [PATCH] GMail-app-style generated colorful one-letter contact pictures for pictureless contacts --- .../activity/misc/ContactPictureLoader.java | 85 ++++++++++++++++--- .../fsck/k9/fragment/MessageListFragment.java | 10 +-- src/com/fsck/k9/view/MessageHeader.java | 10 +-- 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/src/com/fsck/k9/activity/misc/ContactPictureLoader.java b/src/com/fsck/k9/activity/misc/ContactPictureLoader.java index 8479738bf..8fe474f77 100644 --- a/src/com/fsck/k9/activity/misc/ContactPictureLoader.java +++ b/src/com/fsck/k9/activity/misc/ContactPictureLoader.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.util.concurrent.RejectedExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import android.app.ActivityManager; import android.content.ContentResolver; @@ -12,6 +14,9 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -20,6 +25,7 @@ import android.os.Build; import android.support.v4.util.LruCache; import android.widget.QuickContactBadge; import com.fsck.k9.helper.Contacts; +import com.fsck.k9.mail.Address; public class ContactPictureLoader { /** @@ -41,7 +47,6 @@ public class ContactPictureLoader { private ContentResolver mContentResolver; private Resources mResources; private Contacts mContactsHelper; - private Bitmap mDefaultPicture; private int mPictureSizeInPx; /** @@ -68,7 +73,6 @@ public class ContactPictureLoader { mContentResolver = appContext.getContentResolver(); mResources = appContext.getResources(); mContactsHelper = Contacts.getInstance(appContext); - mDefaultPicture = BitmapFactory.decodeResource(mResources, defaultPictureResource); float scale = mResources.getDisplayMetrics().density; mPictureSizeInPx = (int) (PICTURE_SIZE * scale); @@ -115,7 +119,8 @@ public class ContactPictureLoader { * @see #mBitmapCache * @see #mUnknownContactsCache */ - public void loadContactPicture(String email, QuickContactBadge badge) { + public void loadContactPicture(Address address, QuickContactBadge badge) { + String email = address.getAddress(); Bitmap bitmap = getBitmapFromCache(email); if (bitmap != null) { // The picture was found in the bitmap cache @@ -123,21 +128,72 @@ public class ContactPictureLoader { } else if (isEmailInUnknownContactsCache(email)) { // This email address doesn't belong to a contact we have a picture for. Use the // default picture. - badge.setImageBitmap(mDefaultPicture); + badge.setImageBitmap(calculateFallbackBitmap(address)); } else if (cancelPotentialWork(email, badge)) { // Query the contacts database in a background thread and try to load the contact // picture, if there is one. ContactPictureRetrievalTask task = new ContactPictureRetrievalTask(badge); - AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, mDefaultPicture, task); + AsyncDrawable asyncDrawable = new AsyncDrawable(mResources, calculateFallbackBitmap(address), task); badge.setImageDrawable(asyncDrawable); try { - task.exec(email); + task.exec(address.getAddress(), address.getPersonal()); } catch (RejectedExecutionException e) { // We flooded the thread pool queue... fall back to using the default picture - badge.setImageBitmap(mDefaultPicture); + badge.setImageBitmap(calculateFallbackBitmap(address)); } } } + + private int calcUnknownContactColor(Address address) { + int val = address.getAddress().toLowerCase().hashCode(); + int rgb = + (0xff) << 24 | + (~val & 0xff) << 16 | + (val & 0xff) << 8 | + (val>>>8 & 0xff); + return rgb; + } + + private char calcUnknownContactLetter(Address address) { + String letter = ""; + Pattern p = Pattern.compile("[^a-zA-Z]*([a-zA-Z]).*"); + String str = address.getPersonal() != null ? address.getPersonal() : address.getAddress(); + Matcher m = p.matcher(str); + if (m.matches()) { + letter = m.group(1).toUpperCase(); + } + + return letter.length() == 0 ? 'K' : letter.charAt(0); + } + + /** Calculates a bitmap with a color and a capital letter for + * contacts without picture. + * */ + private Bitmap calculateFallbackBitmap(Address address) { + Bitmap result = Bitmap.createBitmap(mPictureSizeInPx, mPictureSizeInPx, Bitmap.Config.ARGB_8888); + + Canvas canvas = new Canvas(result); + + int rgb = calcUnknownContactColor(address); + result.eraseColor(rgb); + + String letter = Character.toString(calcUnknownContactLetter(address)); + + Paint paint = new Paint(); + paint.setAntiAlias(true); + paint.setStyle(Paint.Style.FILL); + paint.setARGB(255, 255, 255, 255); + paint.setFakeBoldText(true); + paint.setTextSize(mPictureSizeInPx); + Rect rect = new Rect(); + paint.getTextBounds(letter, 0, 1, rect); + float width = paint.measureText(letter); + canvas.drawText(letter, + mPictureSizeInPx/2f-width/2f, + mPictureSizeInPx/2f+rect.height()/2f, paint); + + return result; + } private void addBitmapToCache(String key, Bitmap bitmap) { if (getBitmapFromCache(key) == null) { @@ -176,7 +232,7 @@ public class ContactPictureLoader { final ContactPictureRetrievalTask task = getContactPictureRetrievalTask(badge); if (task != null && email != null) { - String emailFromTask = task.getEmail(); + String emailFromTask = task.getAddress().getAddress(); if (!email.equals(emailFromTask)) { // Cancel previous task task.cancel(true); @@ -208,7 +264,7 @@ public class ContactPictureLoader { */ class ContactPictureRetrievalTask extends AsyncTask { private final WeakReference mQuickContactBadgeReference; - private String mEmail; + private Address mAddress; ContactPictureRetrievalTask(QuickContactBadge badge) { mQuickContactBadgeReference = new WeakReference(badge); @@ -222,14 +278,15 @@ public class ContactPictureLoader { } } - public String getEmail() { - return mEmail; + public Address getAddress() { + return mAddress; } @Override protected Bitmap doInBackground(String... args) { String email = args[0]; - mEmail = email; + String personal = args[1]; + mAddress = new Address(email, personal); final Uri x = mContactsHelper.getPhotoUri(email); Bitmap bitmap = null; if (x != null) { @@ -256,8 +313,8 @@ public class ContactPictureLoader { } if (bitmap == null) { - bitmap = mDefaultPicture; - + bitmap = calculateFallbackBitmap(mAddress); + // Remember that we don't have a contact picture for this email address addEmailToUnknownContactsCache(email); } else { diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java index 4ad3eff95..66dbc6691 100644 --- a/src/com/fsck/k9/fragment/MessageListFragment.java +++ b/src/com/fsck/k9/fragment/MessageListFragment.java @@ -1875,15 +1875,15 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick CharSequence displayName = mMessageHelper.getDisplayName(account, fromAddrs, toAddrs); CharSequence displayDate = DateUtils.getRelativeTimeSpanString(context, cursor.getLong(DATE_COLUMN)); - String counterpartyAddress = null; + Address counterpartyAddress = null; if (fromMe) { if (toAddrs.length > 0) { - counterpartyAddress = toAddrs[0].getAddress(); + counterpartyAddress = toAddrs[0]; } else if (ccAddrs.length > 0) { - counterpartyAddress = ccAddrs[0].getAddress(); + counterpartyAddress = ccAddrs[0]; } } else if (fromAddrs.length > 0) { - counterpartyAddress = fromAddrs[0].getAddress(); + counterpartyAddress = fromAddrs[0]; } int threadCount = (mThreadedList) ? cursor.getInt(THREAD_COUNT_COLUMN) : 0; @@ -1923,7 +1923,7 @@ public class MessageListFragment extends SherlockFragment implements OnItemClick holder.position = cursor.getPosition(); if (holder.contactBadge != null) { - holder.contactBadge.assignContactFromEmail(counterpartyAddress, true); + holder.contactBadge.assignContactFromEmail(counterpartyAddress.getAddress(), true); if (counterpartyAddress != null) { /* * At least in Android 2.2 a different background + padding is used when no diff --git a/src/com/fsck/k9/view/MessageHeader.java b/src/com/fsck/k9/view/MessageHeader.java index 3c8f87e0d..cc3d2a95f 100644 --- a/src/com/fsck/k9/view/MessageHeader.java +++ b/src/com/fsck/k9/view/MessageHeader.java @@ -226,15 +226,15 @@ public class MessageHeader extends ScrollView implements OnClickListener { Address[] ccAddrs = message.getRecipients(Message.RecipientType.CC); boolean fromMe = mMessageHelper.toMe(account, fromAddrs); - String counterpartyAddress = null; + Address counterpartyAddress = null; if (fromMe) { if (toAddrs.length > 0) { - counterpartyAddress = toAddrs[0].getAddress(); + counterpartyAddress = toAddrs[0]; } else if (ccAddrs.length > 0) { - counterpartyAddress = ccAddrs[0].getAddress(); + counterpartyAddress = ccAddrs[0]; } } else if (fromAddrs.length > 0) { - counterpartyAddress = fromAddrs[0].getAddress(); + counterpartyAddress = fromAddrs[0]; } /* @@ -275,7 +275,7 @@ public class MessageHeader extends ScrollView implements OnClickListener { mDateView.setText(dateTime); if (K9.showContactPicture()) { - mContactBadge.assignContactFromEmail(counterpartyAddress, true); + mContactBadge.assignContactFromEmail(counterpartyAddress.getAddress(), true); if (counterpartyAddress != null) { mContactsPictureLoader.loadContactPicture(counterpartyAddress, mContactBadge); } else {