Merge "Choose thread icon more carefully" into ub-gmail-ur14-dev
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index 632075f..028c3e1 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -67,6 +67,7 @@
 import com.android.mail.bitmap.CheckableContactFlipDrawable;
 import com.android.mail.bitmap.ContactDrawable;
 import com.android.mail.perf.Timer;
+import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.UIProvider;
@@ -77,7 +78,6 @@
 import com.android.mail.ui.ControllableActivity;
 import com.android.mail.ui.ConversationCheckedSet;
 import com.android.mail.ui.ConversationSetObserver;
-import com.android.mail.ui.DividedImageCanvas.InvalidateCallback;
 import com.android.mail.ui.FolderDisplayer;
 import com.android.mail.ui.SwipeableItemView;
 import com.android.mail.ui.SwipeableListView;
@@ -89,12 +89,11 @@
 import com.android.mail.utils.ViewUtils;
 import com.google.common.annotations.VisibleForTesting;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
 public class ConversationItemView extends View
-        implements SwipeableItemView, ToggleableItem, InvalidateCallback, ConversationSetObserver,
+        implements SwipeableItemView, ToggleableItem, ConversationSetObserver,
         BadgeSpan.BadgeSpanDimensions {
 
     // Timer.
@@ -184,7 +183,7 @@
 
     private final Context mContext;
 
-    public ConversationItemViewModel mHeader;
+    private ConversationItemViewModel mHeader;
     private boolean mDownEvent;
     private boolean mChecked = false;
     private ConversationCheckedSet mCheckedConversationSet;
@@ -194,7 +193,7 @@
     private boolean mDividerEnabled;
     private AnimatedAdapter mAdapter;
     private float mAnimatedHeightFraction = 1.0f;
-    private final String mAccount;
+    private final Account mAccount;
     private ControllableActivity mActivity;
     private final TextView mSendersTextView;
     private final TextView mSubjectTextView;
@@ -512,7 +511,7 @@
         }
     }
 
-    public ConversationItemView(Context context, String account) {
+    public ConversationItemView(Context context, Account account) {
         super(context);
         Utils.traceBeginSection("CIVC constructor");
         setClickable(true);
@@ -640,8 +639,8 @@
             final boolean swipeEnabled, final boolean importanceMarkersEnabled,
             final boolean showChevronsEnabled, final AnimatedAdapter adapter) {
         Utils.traceBeginSection("CIVC.bind");
-        bind(ConversationItemViewModel.forConversation(mAccount, conversation), activity,
-                null /* conversationItemAreaClickListener */,
+        bind(ConversationItemViewModel.forConversation(mAccount.getEmailAddress(), conversation),
+                activity, null /* conversationItemAreaClickListener */,
                 set, folder, checkboxOrSenderImage, swipeEnabled, importanceMarkersEnabled,
                 showChevronsEnabled, adapter, -1 /* backgroundOverrideResId */,
                 null /* photoBitmap */, false /* useFullMargins */, true /* mDividerEnabled */);
@@ -680,8 +679,7 @@
             final boolean newlyBound = header.conversation.id != mHeader.conversation.id;
             // If this was previously bound to a different conversation, remove any contact photo
             // manager requests.
-            if (newlyBound || (mHeader.displayableNames != null && !mHeader
-                    .displayableNames.equals(header.displayableNames))) {
+            if (newlyBound || (!mHeader.displayableNames.equals(header.displayableNames))) {
                 mSendersImageView.getContactDrawable().unbind();
             }
 
@@ -951,18 +949,20 @@
                     .createMessageInfo(context, mHeader.conversation, true);
             int maxChars = ConversationItemViewCoordinates.getSendersLength(context,
                     mCoordinates.getMode(), mHeader.conversation.hasAttachments);
-            mHeader.displayableEmails = new ArrayList<String>();
-            mHeader.displayableNames = new ArrayList<String>();
-            mHeader.styledNames = new ArrayList<SpannableString>();
+
+            mHeader.mSenderAvatarModel.clear();
+            mHeader.displayableNames.clear();
+            mHeader.styledNames.clear();
 
             SendersView.format(context, mHeader.conversation.conversationInfo,
                     mHeader.messageInfoString.toString(), maxChars, mHeader.styledNames,
-                    mHeader.displayableNames, mHeader.displayableEmails, mAccount,
-                    mDisplayedFolder.shouldShowRecipients(), true);
+                    mHeader.displayableNames, mHeader.mSenderAvatarModel,
+                    mAccount.getEmailAddress(), mDisplayedFolder.shouldShowRecipients(), true);
 
-            if (mHeader.displayableEmails.isEmpty() && mHeader.hasDraftMessage) {
-                mHeader.displayableEmails.add(mAccount);
-                mHeader.displayableNames.add(mAccount);
+            if (mHeader.mSenderAvatarModel.isNotPopulated() && mHeader.hasDraftMessage) {
+                mHeader.mSenderAvatarModel.populate(mAccount.getDisplayName(),
+                        mAccount.getEmailAddress());
+                mHeader.displayableNames.add(mAccount.getDisplayName());
             }
 
             // If we have displayable senders, load their thumbnails
@@ -996,8 +996,7 @@
     // is immutable.
     private void loadImages() {
         if (mGadgetMode != ConversationItemViewCoordinates.GADGET_CONTACT_PHOTO
-                || mHeader.displayableEmails == null
-                || mHeader.displayableEmails.isEmpty()) {
+                || mHeader.mSenderAvatarModel.isNotPopulated()) {
             return;
         }
         if (mCoordinates.contactImagesWidth <= 0 || mCoordinates.contactImagesHeight <= 0) {
@@ -1015,7 +1014,8 @@
         final ContactDrawable drawable = mSendersImageView.getContactDrawable();
         drawable.setDecodeDimensions(mCoordinates.contactImagesWidth,
                 mCoordinates.contactImagesHeight);
-        drawable.bind(mHeader.displayableNames.get(0), mHeader.displayableEmails.get(0));
+        drawable.bind(mHeader.mSenderAvatarModel.getName(),
+                mHeader.mSenderAvatarModel.getEmailAddress());
         Utils.traceEndSection();
     }
 
@@ -1935,7 +1935,7 @@
         return sScrollSlop;
     }
 
-    public String getAccount() {
-        return mAccount;
+    public String getAccountEmailAddress() {
+        return mAccount.getEmailAddress();
     }
 }
diff --git a/src/com/android/mail/browse/ConversationItemViewModel.java b/src/com/android/mail/browse/ConversationItemViewModel.java
index cc30d85..84cf59a 100644
--- a/src/com/android/mail/browse/ConversationItemViewModel.java
+++ b/src/com/android/mail/browse/ConversationItemViewModel.java
@@ -24,7 +24,6 @@
 import android.text.StaticLayout;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
-import android.text.style.CharacterStyle;
 import android.util.LruCache;
 import android.util.Pair;
 
@@ -119,21 +118,20 @@
     private String mContentDescription;
 
     /**
-     * Email addresses corresponding to the senders/recipients that will be displayed on the top
-     * line; used to generate the conversation icon.
+     * The email address and name of the sender whose avatar will be drawn as a conversation icon.
      */
-    public ArrayList<String> displayableEmails;
+    public final SenderAvatarModel mSenderAvatarModel = new SenderAvatarModel();
 
     /**
      * Display names corresponding to the email address for the senders/recipients that will be
      * displayed on the top line.
      */
-    public ArrayList<String> displayableNames;
+    public final ArrayList<String> displayableNames = new ArrayList<>();
 
     /**
      * A styled version of the {@link #displayableNames} to be displayed on the top line.
      */
-    public ArrayList<SpannableString> styledNames;
+    public final ArrayList<SpannableString> styledNames = new ArrayList<>();
 
     /**
      * Returns the view model for a conversation. If the model doesn't exist for this conversation
@@ -318,4 +316,45 @@
             sConversationHeaderMap.evictAll();
         }
     }
+
+    /**
+     * This mutable model stores the name and email address of the sender for whom an avatar will
+     * be drawn as the conversation icon.
+     */
+    public static final class SenderAvatarModel {
+        private String mEmailAddress;
+        private String mName;
+
+        public String getEmailAddress() {
+            return mEmailAddress;
+        }
+
+        public String getName() {
+            return mName;
+        }
+
+        /**
+         * Removes the name and email address of the participant of this avatar.
+         */
+        public void clear() {
+            populate(null, null);
+        }
+
+        /**
+         * @param name the name of the participant of this avatar
+         * @param emailAddress the email address of the participant of this avatar
+         */
+        public void populate(String name, String emailAddress) {
+            mName = name;
+            mEmailAddress = emailAddress;
+        }
+
+        /**
+         * @return <tt>true</tt> if this model does not yet contain enough data to produce an
+         *      avatar image; <tt>false</tt> otherwise
+         */
+        public boolean isNotPopulated() {
+            return mEmailAddress == null;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/mail/browse/SendersView.java b/src/com/android/mail/browse/SendersView.java
index 3f05ab5..734a59a 100644
--- a/src/com/android/mail/browse/SendersView.java
+++ b/src/com/android/mail/browse/SendersView.java
@@ -36,15 +36,19 @@
 import com.android.mail.providers.ConversationInfo;
 import com.android.mail.providers.ParticipantInfo;
 import com.android.mail.providers.UIProvider;
-import com.android.mail.ui.DividedImageCanvas;
 import com.android.mail.utils.ObjectCache;
 import com.google.common.base.Objects;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 public class SendersView {
+    /** The maximum number of senders to display for a given conversation */
+    private static final int MAX_SENDER_COUNT = 4;
+
     private static final Integer DOES_NOT_EXIST = -5;
     // FIXME(ath): make all of these statics instance variables, and have callers hold onto this
     // instance as long as appropriate (e.g. activity lifetime).
@@ -220,12 +224,13 @@
 
     public static void format(Context context, ConversationInfo conversationInfo,
             String messageInfo, int maxChars, ArrayList<SpannableString> styledSenders,
-            ArrayList<String> displayableSenderNames, ArrayList<String> displayableSenderEmails,
+            ArrayList<String> displayableSenderNames,
+            ConversationItemViewModel.SenderAvatarModel senderAvatarModel,
             String account, final boolean showToHeader, final boolean resourceCachingRequired) {
         try {
             getSenderResources(context, resourceCachingRequired);
             format(context, conversationInfo, messageInfo, maxChars, styledSenders,
-                    displayableSenderNames, displayableSenderEmails, account,
+                    displayableSenderNames, senderAvatarModel, account,
                     sUnreadStyleSpan, sReadStyleSpan, showToHeader, resourceCachingRequired);
         } finally {
             if (!resourceCachingRequired) {
@@ -236,14 +241,15 @@
 
     public static void format(Context context, ConversationInfo conversationInfo,
             String messageInfo, int maxChars, ArrayList<SpannableString> styledSenders,
-            ArrayList<String> displayableSenderNames, ArrayList<String> displayableSenderEmails,
+            ArrayList<String> displayableSenderNames,
+            ConversationItemViewModel.SenderAvatarModel senderAvatarModel,
             String account, final TextAppearanceSpan notificationUnreadStyleSpan,
             final CharacterStyle notificationReadStyleSpan, final boolean showToHeader,
             final boolean resourceCachingRequired) {
         try {
             getSenderResources(context, resourceCachingRequired);
             handlePriority(maxChars, messageInfo, conversationInfo, styledSenders,
-                    displayableSenderNames, displayableSenderEmails, account,
+                    displayableSenderNames, senderAvatarModel, account,
                     notificationUnreadStyleSpan, notificationReadStyleSpan, showToHeader);
         } finally {
             if (!resourceCachingRequired) {
@@ -254,10 +260,12 @@
 
     private static void handlePriority(int maxChars, String messageInfoString,
             ConversationInfo conversationInfo, ArrayList<SpannableString> styledSenders,
-            ArrayList<String> displayableSenderNames, ArrayList<String> displayableSenderEmails,
+            ArrayList<String> displayableSenderNames,
+            ConversationItemViewModel.SenderAvatarModel senderAvatarModel,
             String account, final TextAppearanceSpan unreadStyleSpan,
             final CharacterStyle readStyleSpan, final boolean showToHeader) {
-        boolean shouldAddPhotos = displayableSenderEmails != null;
+        final boolean shouldSelectSenders = displayableSenderNames != null;
+        final boolean shouldSelectAvatar = senderAvatarModel != null;
         int maxPriorityToInclude = -1; // inclusive
         int numCharsUsed = messageInfoString.length(); // draft, number drafts,
                                                        // count
@@ -297,18 +305,15 @@
         } finally {
             PRIORITY_LENGTH_MAP_CACHE.release(priorityToLength);
         }
-        // We want to include this entry if
-        // 1) The onlyShowUnread flags is not set
-        // 2) The above flag is set, and the message is unread
-        ParticipantInfo currentParticipant;
+
         SpannableString spannableDisplay;
-        CharacterStyle style;
         boolean appendedElided = false;
-        Map<String, Integer> displayHash = Maps.newHashMap();
-        String firstDisplayableSenderEmail = null;
-        String firstDisplayableSender = null;
+        final Map<String, Integer> displayHash = Maps.newHashMap();
+        final List<String> senderEmails = Lists.newArrayListWithExpectedSize(MAX_SENDER_COUNT);
+        String firstSenderEmail = null;
+        String firstSenderName = null;
         for (int i = 0; i < conversationInfo.participantInfos.size(); i++) {
-            currentParticipant = conversationInfo.participantInfos.get(i);
+            final ParticipantInfo currentParticipant = conversationInfo.participantInfos.get(i);
             final String currentEmail = currentParticipant.email;
 
             final String currentName = currentParticipant.name;
@@ -323,8 +328,8 @@
             }
 
             final int priority = currentParticipant.priority;
-            style = CharacterStyle.wrap(currentParticipant.readConversation ? readStyleSpan :
-                    unreadStyleSpan);
+            final CharacterStyle style = CharacterStyle.wrap(currentParticipant.readConversation ?
+                    readStyleSpan : unreadStyleSpan);
             if (priority <= maxPriorityToInclude) {
                 spannableDisplay = new SpannableString(sBidiFormatter.unicodeWrap(nameString));
                 // Don't duplicate senders; leave the first instance, unless the
@@ -340,8 +345,8 @@
                             && oldPos < styledSenders.size()) {
                         // Remove the old one!
                         styledSenders.set(oldPos, null);
-                        if (shouldAddPhotos && !TextUtils.isEmpty(currentEmail)) {
-                            displayableSenderEmails.remove(currentEmail);
+                        if (shouldSelectSenders && !TextUtils.isEmpty(currentEmail)) {
+                            senderEmails.remove(currentEmail);
                             displayableSenderNames.remove(currentName);
                         }
                     }
@@ -357,38 +362,67 @@
                     styledSenders.add(spannableDisplay);
                 }
             }
-            if (shouldAddPhotos) {
-                String senderEmail = TextUtils.isEmpty(currentName) ?
-                        account :
-                            TextUtils.isEmpty(currentEmail) ? currentName : currentEmail;
+
+            final String senderEmail = TextUtils.isEmpty(currentName) ? account :
+                    TextUtils.isEmpty(currentEmail) ? currentName : currentEmail;
+
+            if (shouldSelectSenders) {
                 if (i == 0) {
                     // Always add the first sender!
-                    firstDisplayableSenderEmail = senderEmail;
-                    firstDisplayableSender = currentName;
+                    firstSenderEmail = senderEmail;
+                    firstSenderName = currentName;
                 } else {
-                    if (!Objects.equal(firstDisplayableSenderEmail, senderEmail)) {
-                        int indexOf = displayableSenderEmails.indexOf(senderEmail);
+                    if (!Objects.equal(firstSenderEmail, senderEmail)) {
+                        int indexOf = senderEmails.indexOf(senderEmail);
                         if (indexOf > -1) {
-                            displayableSenderEmails.remove(indexOf);
+                            senderEmails.remove(indexOf);
                             displayableSenderNames.remove(indexOf);
                         }
-                        displayableSenderEmails.add(senderEmail);
+                        senderEmails.add(senderEmail);
                         displayableSenderNames.add(currentName);
-                        if (displayableSenderEmails.size() > DividedImageCanvas.MAX_DIVISIONS) {
-                            displayableSenderEmails.remove(0);
+                        if (senderEmails.size() > MAX_SENDER_COUNT) {
+                            senderEmails.remove(0);
                             displayableSenderNames.remove(0);
                         }
                     }
                 }
             }
+
+            // if the corresponding message from this participant is unread and no sender avatar
+            // is yet chosen, choose this one
+            if (shouldSelectAvatar && senderAvatarModel.isNotPopulated() &&
+                    !currentParticipant.readConversation) {
+                senderAvatarModel.populate(currentName, senderEmail);
+            }
         }
-        if (shouldAddPhotos && !TextUtils.isEmpty(firstDisplayableSenderEmail)) {
-            if (displayableSenderEmails.size() < DividedImageCanvas.MAX_DIVISIONS) {
-                displayableSenderEmails.add(0, firstDisplayableSenderEmail);
-                displayableSenderNames.add(0, firstDisplayableSender);
+
+        // always add the first sender to the display
+        if (shouldSelectSenders && !TextUtils.isEmpty(firstSenderEmail)) {
+            if (displayableSenderNames.size() < MAX_SENDER_COUNT) {
+                displayableSenderNames.add(0, firstSenderName);
             } else {
-                displayableSenderEmails.set(0, firstDisplayableSenderEmail);
-                displayableSenderNames.set(0, firstDisplayableSender);
+                displayableSenderNames.set(0, firstSenderName);
+            }
+        }
+
+        // if all messages in the thread were read, we must search for an appropriate avatar
+        if (shouldSelectAvatar && senderAvatarModel.isNotPopulated() &&
+                !conversationInfo.participantInfos.isEmpty()) {
+
+            // search for the last sender that is not the current account
+            for (int i = conversationInfo.participantInfos.size() - 1; i >= 0; i--) {
+                final ParticipantInfo participant = conversationInfo.participantInfos.get(i);
+                if (!TextUtils.isEmpty(participant.name)) {
+                    senderAvatarModel.populate(participant.name, participant.email);
+                    break;
+                }
+            }
+
+            // if we still don't have an avatar, the account is emailing itself; use the last sender
+            if (senderAvatarModel.isNotPopulated()) {
+                final int lastIndex = conversationInfo.participantInfos.size() - 1;
+                final ParticipantInfo lastSender = conversationInfo.participantInfos.get(lastIndex);
+                senderAvatarModel.populate(lastSender.name, lastSender.email);
             }
         }
     }
diff --git a/src/com/android/mail/browse/SwipeableConversationItemView.java b/src/com/android/mail/browse/SwipeableConversationItemView.java
index a08c028..866653a 100644
--- a/src/com/android/mail/browse/SwipeableConversationItemView.java
+++ b/src/com/android/mail/browse/SwipeableConversationItemView.java
@@ -23,6 +23,7 @@
 import android.widget.FrameLayout;
 import android.widget.ListView;
 
+import com.android.mail.providers.Account;
 import com.android.mail.providers.Conversation;
 import com.android.mail.providers.Folder;
 import com.android.mail.providers.UIProvider;
@@ -34,7 +35,7 @@
 
     private final ConversationItemView mConversationItemView;
 
-    public SwipeableConversationItemView(Context context, String account) {
+    public SwipeableConversationItemView(Context context, Account account) {
         super(context);
         mConversationItemView = new ConversationItemView(context, account);
         addView(mConversationItemView);
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index 30aa36c..b2fec88 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -371,7 +371,7 @@
     public View createConversationItemView(SwipeableConversationItemView view, Context context,
             Conversation conv) {
         if (view == null) {
-            view = new SwipeableConversationItemView(context, mAccount.getEmailAddress());
+            view = new SwipeableConversationItemView(context, mAccount);
         }
         view.bind(conv, mActivity, mBatchConversations, mFolder, getCheckboxSetting(),
                 mSwipeEnabled, mImportanceMarkersEnabled, mShowChevronsEnabled, this);
@@ -764,7 +764,7 @@
 
     @Override
     public View newView(Context context, Cursor cursor, ViewGroup parent) {
-        return new SwipeableConversationItemView(context, mAccount.getEmailAddress());
+        return new SwipeableConversationItemView(context, mAccount);
     }
 
     @Override
diff --git a/src/com/android/mail/ui/DividedImageCanvas.java b/src/com/android/mail/ui/DividedImageCanvas.java
deleted file mode 100644
index 274327e..0000000
--- a/src/com/android/mail/ui/DividedImageCanvas.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * 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.mail.ui;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Matrix;
-import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-
-import com.android.mail.R;
-import com.android.mail.utils.Utils;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-/**
- * DividedImageCanvas creates a canvas that can display into a minimum of 1
- * and maximum of 4 images. As images are added, they
- * are laid out according to the following algorithm:
- * 1 Image: Draw the bitmap filling the entire canvas.
- * 2 Images: Draw 2 bitmaps split vertically down the middle.
- * 3 Images: Draw 3 bitmaps: the first takes up all vertical space; the 2nd and 3rd are stacked in
- *           the second vertical position.
- * 4 Images: Divide the Canvas into 4 equal quadrants and draws 1 bitmap in each.
- */
-public class DividedImageCanvas implements ImageCanvas {
-    public static final int MAX_DIVISIONS = 4;
-
-    private final Map<String, Integer> mDivisionMap = Maps
-            .newHashMapWithExpectedSize(MAX_DIVISIONS);
-    private Bitmap mDividedBitmap;
-    private Canvas mCanvas;
-    private int mWidth;
-    private int mHeight;
-
-    private final Context mContext;
-    private final InvalidateCallback mCallback;
-    private final ArrayList<Bitmap> mDivisionImages = new ArrayList<Bitmap>(MAX_DIVISIONS);
-
-    /**
-     * Ignore any request to draw final output when not yet ready. This prevents partially drawn
-     * canvases from appearing.
-     */
-    private boolean mBitmapValid = false;
-
-    private int mGeneration;
-
-    private static final Paint sPaint = new Paint();
-    private static final Paint sClearPaint = new Paint();
-    private static final Rect sSrc = new Rect();
-    private static final Rect sDest = new Rect();
-
-    private static int sDividerLineWidth = -1;
-    private static int sDividerColor;
-
-    static {
-        sClearPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
-    }
-
-    public DividedImageCanvas(Context context, InvalidateCallback callback) {
-        mContext = context;
-        mCallback = callback;
-        setupDividerLines();
-    }
-
-    /**
-     * Get application context for this object.
-     */
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder("{");
-        sb.append(super.toString());
-        sb.append(" mDivisionMap=");
-        sb.append(mDivisionMap);
-        sb.append(" mDivisionImages=");
-        sb.append(mDivisionImages);
-        sb.append(" mWidth=");
-        sb.append(mWidth);
-        sb.append(" mHeight=");
-        sb.append(mHeight);
-        sb.append("}");
-        return sb.toString();
-    }
-
-    /**
-     * Set the id associated with each quadrant. The quadrants are laid out:
-     * TopLeft, TopRight, Bottom Left, Bottom Right
-     * @param keys
-     */
-    public void setDivisionIds(List<Object> keys) {
-        if (keys.size() > MAX_DIVISIONS) {
-            throw new IllegalArgumentException("too many divisionIds: " + keys);
-        }
-
-        boolean needClear = getDivisionCount() != keys.size();
-        if (!needClear) {
-            for (int i = 0; i < keys.size(); i++) {
-                String divisionId = transformKeyToDivisionId(keys.get(i));
-                // different item or different place
-                if (!mDivisionMap.containsKey(divisionId) || mDivisionMap.get(divisionId) != i) {
-                    needClear = true;
-                    break;
-                }
-            }
-        }
-
-        if (needClear) {
-            mDivisionMap.clear();
-            mDivisionImages.clear();
-            int i = 0;
-            for (Object key : keys) {
-                String divisionId = transformKeyToDivisionId(key);
-                mDivisionMap.put(divisionId, i);
-                mDivisionImages.add(null);
-                i++;
-            }
-        }
-    }
-
-    private void draw(Bitmap b, int left, int top, int right, int bottom) {
-        if (b != null) {
-            // Some times we load taller images compared to the destination rect on the canvas
-            int srcTop = 0;
-            int srcBottom = b.getHeight();
-            int destHeight = bottom - top;
-            if (b.getHeight() > bottom - top) {
-                srcTop = b.getHeight() / 2 - destHeight/2;
-                srcBottom = b.getHeight() / 2 + destHeight/2;
-            }
-
-//            todo:markwei do not scale very small bitmaps
-            // l t r b
-            sSrc.set(0, srcTop, b.getWidth(), srcBottom);
-            sDest.set(left, top, right, bottom);
-            mCanvas.drawRect(sDest, sClearPaint);
-            mCanvas.drawBitmap(b, sSrc, sDest, sPaint);
-        } else {
-            // clear
-            mCanvas.drawRect(left, top, right, bottom, sClearPaint);
-        }
-    }
-
-    /**
-     * Get the desired dimensions and scale for the bitmap to be placed in the
-     * location corresponding to id. Caller must allocate the Dimensions object.
-     * @param key
-     * @param outDim a {@link ImageCanvas.Dimensions} object to write results into
-     */
-    @Override
-    public void getDesiredDimensions(Object key, Dimensions outDim) {
-        Utils.traceBeginSection("get desired dimensions");
-        int w = 0, h = 0;
-        float scale = 0;
-        final Integer pos = mDivisionMap.get(transformKeyToDivisionId(key));
-        if (pos != null && pos >= 0) {
-            final int size = mDivisionMap.size();
-            switch (size) {
-                case 0:
-                    break;
-                case 1:
-                    w = mWidth;
-                    h = mHeight;
-                    scale = Dimensions.SCALE_ONE;
-                    break;
-                case 2:
-                    w = mWidth / 2;
-                    h = mHeight;
-                    scale = Dimensions.SCALE_HALF;
-                    break;
-                case 3:
-                    switch (pos) {
-                        case 0:
-                            w = mWidth / 2;
-                            h = mHeight;
-                            scale = Dimensions.SCALE_HALF;
-                            break;
-                        default:
-                            w = mWidth / 2;
-                            h = mHeight / 2;
-                            scale = Dimensions.SCALE_QUARTER;
-                    }
-                    break;
-                case 4:
-                    w = mWidth / 2;
-                    h = mHeight / 2;
-                    scale = Dimensions.SCALE_QUARTER;
-                    break;
-            }
-        }
-        outDim.width = w;
-        outDim.height = h;
-        outDim.scale = scale;
-        Utils.traceEndSection();
-    }
-
-    @Override
-    public void drawImage(Bitmap b, Object key) {
-        addDivisionImage(b, key);
-    }
-
-    /**
-     * Add a bitmap to this view in the quadrant matching its id.
-     * @param b Bitmap
-     * @param key Id to look for that was previously set in setDivisionIds.
-     */
-    public void addDivisionImage(Bitmap b, Object key) {
-        if (b != null) {
-            addOrClearDivisionImage(b, key);
-        }
-    }
-
-    public void clearDivisionImage(Object key) {
-        addOrClearDivisionImage(null, key);
-    }
-    private void addOrClearDivisionImage(Bitmap b, Object key) {
-        Utils.traceBeginSection("add or clear division image");
-        final Integer pos = mDivisionMap.get(transformKeyToDivisionId(key));
-        if (pos != null && pos >= 0) {
-            mDivisionImages.set(pos, b);
-            boolean complete = false;
-            final int width = mWidth;
-            final int height = mHeight;
-            // Different layouts depending on count.
-            final int size = mDivisionMap.size();
-            switch (size) {
-                case 0:
-                    // Do nothing.
-                    break;
-                case 1:
-                    // Draw the bitmap filling the entire canvas.
-                    draw(mDivisionImages.get(0), 0, 0, width, height);
-                    complete = true;
-                    break;
-                case 2:
-                    // Draw 2 bitmaps split vertically down the middle
-                    switch (pos) {
-                        case 0:
-                            draw(mDivisionImages.get(0), 0, 0, width / 2, height);
-                            break;
-                        case 1:
-                            draw(mDivisionImages.get(1), width / 2, 0, width, height);
-                            break;
-                    }
-                    complete = mDivisionImages.get(0) != null && mDivisionImages.get(1) != null
-                            || isPartialBitmapComplete();
-                    if (complete) {
-                        // Draw dividers
-                        drawVerticalDivider(width, height);
-                    }
-                    break;
-                case 3:
-                    // Draw 3 bitmaps: the first takes up all vertical
-                    // space, the 2nd and 3rd are stacked in the second vertical
-                    // position.
-                    switch (pos) {
-                        case 0:
-                            draw(mDivisionImages.get(0), 0, 0, width / 2, height);
-                            break;
-                        case 1:
-                            draw(mDivisionImages.get(1), width / 2, 0, width, height / 2);
-                            break;
-                        case 2:
-                            draw(mDivisionImages.get(2), width / 2, height / 2, width, height);
-                            break;
-                    }
-                    complete = mDivisionImages.get(0) != null && mDivisionImages.get(1) != null
-                            && mDivisionImages.get(2) != null || isPartialBitmapComplete();
-                    if (complete) {
-                        // Draw dividers
-                        drawVerticalDivider(width, height);
-                        drawHorizontalDivider(width / 2, height / 2, width, height / 2);
-                    }
-                    break;
-                default:
-                    // Draw all 4 bitmaps in a grid
-                    switch (pos) {
-                        case 0:
-                            draw(mDivisionImages.get(0), 0, 0, width / 2, height / 2);
-                            break;
-                        case 1:
-                            draw(mDivisionImages.get(1), width / 2, 0, width, height / 2);
-                            break;
-                        case 2:
-                            draw(mDivisionImages.get(2), 0, height / 2, width / 2, height);
-                            break;
-                        case 3:
-                            draw(mDivisionImages.get(3), width / 2, height / 2, width, height);
-                            break;
-                    }
-                    complete = mDivisionImages.get(0) != null && mDivisionImages.get(1) != null
-                            && mDivisionImages.get(2) != null && mDivisionImages.get(3) != null
-                            || isPartialBitmapComplete();
-                    if (complete) {
-                        // Draw dividers
-                        drawVerticalDivider(width, height);
-                        drawHorizontalDivider(0, height / 2, width, height / 2);
-                    }
-                    break;
-            }
-            // Create the new image bitmap.
-            if (complete) {
-                mBitmapValid = true;
-                mCallback.invalidate();
-            }
-        }
-        Utils.traceEndSection();
-    }
-
-    public boolean hasImageFor(Object key) {
-        final Integer pos = mDivisionMap.get(transformKeyToDivisionId(key));
-        return pos != null && mDivisionImages.get(pos) != null;
-    }
-
-    private void setupDividerLines() {
-        if (sDividerLineWidth == -1) {
-            Resources res = getContext().getResources();
-            sDividerLineWidth = res
-                    .getDimensionPixelSize(R.dimen.tile_divider_width);
-            sDividerColor = res.getColor(R.color.tile_divider_color);
-        }
-    }
-
-    private static void setupPaint() {
-        sPaint.setStrokeWidth(sDividerLineWidth);
-        sPaint.setColor(sDividerColor);
-    }
-
-    protected void drawVerticalDivider(int width, int height) {
-        int x1 = width / 2, y1 = 0, x2 = width/2, y2 = height;
-        setupPaint();
-        mCanvas.drawLine(x1, y1, x2, y2, sPaint);
-    }
-
-    protected void drawHorizontalDivider(int x1, int y1, int x2, int y2) {
-        setupPaint();
-        mCanvas.drawLine(x1, y1, x2, y2, sPaint);
-    }
-
-    protected boolean isPartialBitmapComplete() {
-        return false;
-    }
-
-    protected String transformKeyToDivisionId(Object key) {
-        return key.toString();
-    }
-
-    /**
-     * Draw the contents of the DividedImageCanvas to the supplied canvas.
-     */
-    public void draw(Canvas canvas) {
-        if (mDividedBitmap != null && mBitmapValid) {
-            canvas.drawBitmap(mDividedBitmap, 0, 0, null);
-        }
-    }
-
-    /**
-     * Draw the contents of the DividedImageCanvas to the supplied canvas.
-     */
-    public void draw(final Canvas canvas, final Matrix matrix) {
-        if (mDividedBitmap != null && mBitmapValid) {
-            canvas.drawBitmap(mDividedBitmap, matrix, null);
-        }
-    }
-
-    @Override
-    public void reset() {
-        if (mCanvas != null && mDividedBitmap != null) {
-            mBitmapValid = false;
-        }
-        mDivisionMap.clear();
-        mDivisionImages.clear();
-        mGeneration++;
-    }
-
-    @Override
-    public int getGeneration() {
-        return mGeneration;
-    }
-
-    /**
-     * Set the width and height of the canvas.
-     * @param width
-     * @param height
-     */
-    public void setDimensions(int width, int height) {
-        Utils.traceBeginSection("set dimensions");
-        if (mWidth == width && mHeight == height) {
-            Utils.traceEndSection();
-            return;
-        }
-
-        mWidth = width;
-        mHeight = height;
-
-        mDividedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        mCanvas = new Canvas(mDividedBitmap);
-
-        for (int i = 0; i < getDivisionCount(); i++) {
-            mDivisionImages.set(i, null);
-        }
-        mBitmapValid = false;
-        Utils.traceEndSection();
-    }
-
-    /**
-     * Get the resulting canvas width.
-     */
-    public int getWidth() {
-        return mWidth;
-    }
-
-    /**
-     * Get the resulting canvas height.
-     */
-    public int getHeight() {
-        return mHeight;
-    }
-
-    /**
-     * The class that will provided the canvas to which the DividedImageCanvas
-     * should render its contents must implement this interface.
-     */
-    public interface InvalidateCallback {
-        public void invalidate();
-    }
-
-    public int getDivisionCount() {
-        return mDivisionMap.size();
-    }
-
-    /**
-     * Get the division ids currently associated with this DivisionImageCanvas.
-     */
-    public ArrayList<String> getDivisionIds() {
-        return Lists.newArrayList(mDivisionMap.keySet());
-    }
-}
diff --git a/tests/src/com/android/mail/browse/SendersFormattingTests.java b/tests/src/com/android/mail/browse/SendersFormattingTests.java
index ecec4df..c55e03b 100644
--- a/tests/src/com/android/mail/browse/SendersFormattingTests.java
+++ b/tests/src/com/android/mail/browse/SendersFormattingTests.java
@@ -112,4 +112,98 @@
         assertEquals(before.firstUnreadSnippet, after.firstUnreadSnippet);
         assertEquals(before.lastSnippet, after.lastSnippet);
     }
+
+    public void testSenderAvatarIsSenderOfFirstUnreadMessage() {
+        final ConversationInfo conv = createConversationInfo();
+        conv.addParticipant(new ParticipantInfo("a", "a@a.com", 0, true));
+        conv.addParticipant(new ParticipantInfo("b", "b@b.com", 0, false));
+        conv.addParticipant(new ParticipantInfo("c", "c@c.com", 0, false));
+
+        final ArrayList<SpannableString> styledSenders = Lists.newArrayList();
+        final ArrayList<String> displayableSenderNames = Lists.newArrayList();
+        final ConversationItemViewModel.SenderAvatarModel senderAvatarModel =
+                new ConversationItemViewModel.SenderAvatarModel();
+
+        SendersView.format(getContext(), conv, "", 100, styledSenders, displayableSenderNames,
+                senderAvatarModel, null, false, false);
+
+        assertEquals("b@b.com", senderAvatarModel.getEmailAddress());
+        assertEquals("b", senderAvatarModel.getName());
+    }
+
+    public void testSenderAvatarIsLastSenderIfAllMessagesAreRead() {
+        final ConversationInfo conv = createConversationInfo();
+        conv.addParticipant(new ParticipantInfo("a", "a@a.com", 0, true));
+        conv.addParticipant(new ParticipantInfo("b", "b@b.com", 0, true));
+        conv.addParticipant(new ParticipantInfo("c", "c@c.com", 0, true));
+
+        final ArrayList<SpannableString> styledSenders = Lists.newArrayList();
+        final ArrayList<String> displayableSenderNames = Lists.newArrayList();
+        final ConversationItemViewModel.SenderAvatarModel senderAvatarModel =
+                new ConversationItemViewModel.SenderAvatarModel();
+
+        SendersView.format(getContext(), conv, "", 100, styledSenders, displayableSenderNames,
+                senderAvatarModel, null, false, false);
+
+        assertEquals("c@c.com", senderAvatarModel.getEmailAddress());
+        assertEquals("c", senderAvatarModel.getName());
+    }
+
+    public void testSenderAvatarIsLastSenderThatIsNotTheCurrentAccountIfAllMessagesAreRead() {
+        final ConversationInfo conv = createConversationInfo();
+        conv.addParticipant(new ParticipantInfo("a", "a@a.com", 0, true));
+        conv.addParticipant(new ParticipantInfo("b", "b@b.com", 0, true));
+        // empty name indicates it is the current account
+        conv.addParticipant(new ParticipantInfo("", "c@c.com", 0, true));
+
+        final ArrayList<SpannableString> styledSenders = Lists.newArrayList();
+        final ArrayList<String> displayableSenderNames = Lists.newArrayList();
+        final ConversationItemViewModel.SenderAvatarModel senderAvatarModel =
+                new ConversationItemViewModel.SenderAvatarModel();
+
+        SendersView.format(getContext(), conv, "", 100, styledSenders, displayableSenderNames,
+                senderAvatarModel, null, false, false);
+
+        assertEquals("b@b.com", senderAvatarModel.getEmailAddress());
+        assertEquals("b", senderAvatarModel.getName());
+    }
+
+    public void testSenderAvatarIsCurrentAccountIfAllSendersAreCurrentAccount() {
+        final ConversationInfo conv = createConversationInfo();
+        // empty name indicates it is the current account
+        conv.addParticipant(new ParticipantInfo("", "a@a.com", 0, true));
+
+        final ArrayList<SpannableString> styledSenders = Lists.newArrayList();
+        final ArrayList<String> displayableSenderNames = Lists.newArrayList();
+        final ConversationItemViewModel.SenderAvatarModel senderAvatarModel =
+                new ConversationItemViewModel.SenderAvatarModel();
+
+        SendersView.format(getContext(), conv, "", 100, styledSenders, displayableSenderNames,
+                senderAvatarModel, null, false, false);
+
+        assertEquals("a@a.com", senderAvatarModel.getEmailAddress());
+        assertEquals("", senderAvatarModel.getName());
+    }
+
+    /**
+     * Two senders in a thread should be kept distinct if they have unique email addresses, even if
+     * they happen to share the same name.
+     */
+    public void testSenderNamesWhenNamesMatchButEmailAddressesDiffer() {
+        final ConversationInfo conv = createConversationInfo();
+        conv.addParticipant(new ParticipantInfo("Andrew", "aholmes@awesome.com", 0, true));
+        conv.addParticipant(new ParticipantInfo("Andrew", "ajohnson@wicked.com", 0, true));
+
+        final ArrayList<SpannableString> styledSenders = Lists.newArrayList();
+        final ArrayList<String> displayableSenderNames = Lists.newArrayList();
+        final ConversationItemViewModel.SenderAvatarModel senderAvatarModel =
+                new ConversationItemViewModel.SenderAvatarModel();
+
+        SendersView.format(getContext(), conv, "", 100, styledSenders, displayableSenderNames,
+                senderAvatarModel, null, false, false);
+
+        assertEquals(2, displayableSenderNames.size());
+        assertEquals("Andrew", displayableSenderNames.get(0));
+        assertEquals("Andrew", displayableSenderNames.get(1));
+    }
 }
\ No newline at end of file