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