Merge commit '987e2caabf018cd354eb6565034f7c40fa62f99b' into HEAD
Change-Id: Ic6de2b040f7511d91c3b5f1a27c75d7ccb641974
diff --git a/src/com/android/mail/browse/ConversationCursor.java b/src/com/android/mail/browse/ConversationCursor.java
index bd95985..6df2d18 100644
--- a/src/com/android/mail/browse/ConversationCursor.java
+++ b/src/com/android/mail/browse/ConversationCursor.java
@@ -1946,11 +1946,16 @@
}
@Override
- public void setNotificationUri(ContentResolver cr, Uri uri) {
- throw new UnsupportedOperationException();
+ public Uri getNotificationUri() {
+ if (mUnderlyingCursor == null) {
+ return null;
+ } else {
+ return mUnderlyingCursor.getNotificationUri();
+ }
}
- public Uri getNotificationUri() {
+ @Override
+ public void setNotificationUri(ContentResolver cr, Uri uri) {
throw new UnsupportedOperationException();
}
diff --git a/src/com/android/mail/compose/ComposeActivity.java b/src/com/android/mail/compose/ComposeActivity.java
index 65841ec..9bd8ad7 100644
--- a/src/com/android/mail/compose/ComposeActivity.java
+++ b/src/com/android/mail/compose/ComposeActivity.java
@@ -157,7 +157,8 @@
private static final String EXTRA_ATTACHMENT_PREVIEWS = "attachmentPreviews";
// Extra that we can get passed from other activities
- private static final String EXTRA_TO = "to";
+ @VisibleForTesting
+ protected static final String EXTRA_TO = "to";
private static final String EXTRA_CC = "cc";
private static final String EXTRA_BCC = "bcc";
@@ -937,8 +938,12 @@
message.quotedTextOffset = !TextUtils.isEmpty(quotedText) ? QuotedTextView
.getQuotedTextOffset(quotedText.toString()) : -1;
message.accountUri = null;
- message.setFrom(selectedReplyFromAccount != null ? selectedReplyFromAccount.address
- : mAccount != null ? mAccount.getEmailAddress() : null);
+ final String email = selectedReplyFromAccount != null ? selectedReplyFromAccount.address
+ : mAccount != null ? mAccount.getEmailAddress() : null;
+ // TODO: this behavior is wrong. Pull the name from selectedReplyFromAccount.name
+ final String senderName = mAccount != null ? mAccount.getSenderName() : null;
+ final Address address = new Address(senderName, email);
+ message.setFrom(address.pack());
message.draftType = getDraftType(mode);
return message;
}
@@ -998,10 +1003,12 @@
// Otherwise, give the user the ability to choose which account to
// send mail from / save drafts to.
mFromStatic.setVisibility(View.GONE);
+ // TODO: do we want name or address here?
mFromStaticText.setText(mReplyFromAccount.name);
mFromSpinnerWrapper.setVisibility(View.VISIBLE);
} else {
mFromStatic.setVisibility(View.VISIBLE);
+ // TODO: do we want name or address here?
mFromStaticText.setText(mReplyFromAccount.name);
mFromSpinnerWrapper.setVisibility(View.GONE);
}
@@ -1945,10 +1952,10 @@
@Override
public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
+ final boolean superCreated = super.onCreateOptionsMenu(menu);
// Don't render any menu items when there are no accounts.
if (mAccounts == null || mAccounts.length == 0) {
- return true;
+ return superCreated;
}
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.compose_menu, menu);
diff --git a/src/com/android/mail/compose/FromAddressSpinner.java b/src/com/android/mail/compose/FromAddressSpinner.java
index 121cdf1..a9a0886 100644
--- a/src/com/android/mail/compose/FromAddressSpinner.java
+++ b/src/com/android/mail/compose/FromAddressSpinner.java
@@ -70,6 +70,7 @@
public ReplyFromAccount getMatchingReplyFromAccount(String accountString) {
if (!TextUtils.isEmpty(accountString)) {
for (ReplyFromAccount acct : mReplyFromAccounts) {
+ // TODO: Do not key off ReplyFromAccount.name b/11292541
if (accountString.equals(acct.name)) {
return acct;
}
@@ -147,6 +148,7 @@
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
ReplyFromAccount selection = (ReplyFromAccount) getItemAtPosition(position);
+ // TODO: Do not key off ReplyFromAccount.name b/11292541
if (!selection.name.equals(mAccount.name)) {
mAccount = selection;
mAccountChangedListener.onAccountChanged();
diff --git a/src/com/android/mail/providers/Account.java b/src/com/android/mail/providers/Account.java
index b5f7d83..bfe74a6 100644
--- a/src/com/android/mail/providers/Account.java
+++ b/src/com/android/mail/providers/Account.java
@@ -55,6 +55,11 @@
public final String name;
/**
+ * The real name associated with the account, e.g. "John Doe"
+ */
+ private final String senderName;
+
+ /**
* Account manager name. MUST MATCH SYSTEM ACCOUNT MANAGER NAME
*/
@@ -232,6 +237,7 @@
try {
json.put(AccountColumns.NAME, name);
json.put(AccountColumns.TYPE, type);
+ json.put(AccountColumns.SENDER_NAME, senderName);
json.put(AccountColumns.ACCOUNT_MANAGER_NAME, accountManagerName);
json.put(AccountColumns.PROVIDER_VERSION, providerVersion);
json.put(AccountColumns.URI, uri);
@@ -311,6 +317,7 @@
name = acctName;
type = acctType;
final JSONObject json = new JSONObject(jsonAccount);
+ senderName = json.optString(AccountColumns.SENDER_NAME);
final String amName = json.optString(AccountColumns.ACCOUNT_MANAGER_NAME);
// We need accountManagerName to be filled in, but we might be dealing with an old cache
// entry which doesn't have it, so use the display name instead in that case as a fallback
@@ -373,6 +380,7 @@
public Account(Cursor cursor) {
name = cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.NAME));
+ senderName = cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.SENDER_NAME));
type = cursor.getString(cursor.getColumnIndex(UIProvider.AccountColumns.TYPE));
accountManagerName = cursor.getString(
cursor.getColumnIndex(UIProvider.AccountColumns.ACCOUNT_MANAGER_NAME));
@@ -494,6 +502,7 @@
public Account(Parcel in, ClassLoader loader) {
name = in.readString();
+ senderName = in.readString();
type = in.readString();
accountManagerName = in.readString();
providerVersion = in.readInt();
@@ -538,6 +547,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
+ dest.writeString(senderName);
dest.writeString(type);
dest.writeString(accountManagerName);
dest.writeInt(providerVersion);
@@ -599,6 +609,7 @@
final Account other = (Account) o;
return TextUtils.equals(name, other.name) &&
+ TextUtils.equals(senderName, other.senderName) &&
TextUtils.equals(accountManagerName, other.accountManagerName) &&
TextUtils.equals(type, other.type) &&
capabilities == other.capabilities &&
@@ -651,6 +662,7 @@
@Override
public int hashCode() {
return Objects.hashCode(name,
+ senderName,
accountManagerName,
type,
capabilities,
@@ -701,6 +713,8 @@
}
// add the main account address
+ // TODO: name is incorrect here, use senderName once FromAddressSpinner is fixed
+ // b/11292541
mReplyFroms.add(new ReplyFromAccount(this, uri, getEmailAddress(), name,
getEmailAddress(), false /* isDefault */, false /* isCustom */));
@@ -743,6 +757,15 @@
return accountManagerName;
}
+ /**
+ * Returns the real name associated with the account, e.g. "John Doe" or null if no such name
+ * has been configured
+ * @return sender name
+ */
+ public String getSenderName() {
+ return senderName;
+ }
+
@SuppressWarnings("hiding")
public static final ClassLoaderCreator<Account> CREATOR = new ClassLoaderCreator<Account>() {
@Override
@@ -771,6 +794,7 @@
map.put(AccountColumns._ID, 0);
map.put(AccountColumns.NAME, name);
+ map.put(AccountColumns.SENDER_NAME, senderName);
map.put(AccountColumns.TYPE, type);
map.put(AccountColumns.ACCOUNT_MANAGER_NAME, accountManagerName);
map.put(AccountColumns.PROVIDER_VERSION, providerVersion);
diff --git a/src/com/android/mail/providers/Folder.java b/src/com/android/mail/providers/Folder.java
index fb25905..88d8d81 100644
--- a/src/com/android/mail/providers/Folder.java
+++ b/src/com/android/mail/providers/Folder.java
@@ -187,6 +187,11 @@
*/
public long lastMessageTimestamp;
+ /**
+ * A string of unread senders sorted by date, so we don't have to fetch this in multiple queries
+ */
+ public String unreadSenders;
+
/** An immutable, empty conversation list */
public static final Collection<Folder> EMPTY = Collections.emptyList();
@@ -215,6 +220,7 @@
private String mHierarchicalDesc;
private Uri mParent;
private long mLastMessageTimestamp;
+ private String mUnreadSenders;
public Folder build() {
return new Folder(mId, mPersistentId, mUri, mName, mCapabilities,
@@ -222,7 +228,7 @@
mUnseenCount, mUnreadCount, mTotalCount, mRefreshUri, mSyncStatus,
mLastSyncResult, mType, mIconResId, mNotificationIconResId, mBgColor,
mFgColor, mLoadMoreUri, mHierarchicalDesc, mParent,
- mLastMessageTimestamp);
+ mLastMessageTimestamp, mUnreadSenders);
}
public Builder setId(final int id) {
@@ -321,6 +327,10 @@
mLastMessageTimestamp = lastMessageTimestamp;
return this;
}
+ public Builder setUnreadSenders(final String unreadSenders) {
+ mUnreadSenders = unreadSenders;
+ return this;
+ }
}
public Folder(int id, String persistentId, Uri uri, String name, int capabilities,
@@ -328,7 +338,7 @@
int unseenCount, int unreadCount, int totalCount, Uri refreshUri, int syncStatus,
int lastSyncResult, int type, int iconResId, int notificationIconResId, String bgColor,
String fgColor, Uri loadMoreUri, String hierarchicalDesc, Uri parent,
- final long lastMessageTimestamp) {
+ final long lastMessageTimestamp, final String unreadSenders) {
this.id = id;
this.persistentId = persistentId;
this.folderUri = new FolderUri(uri);
@@ -359,6 +369,7 @@
this.hierarchicalDesc = hierarchicalDesc;
this.lastMessageTimestamp = lastMessageTimestamp;
this.parent = parent;
+ this.unreadSenders = unreadSenders;
}
public Folder(Cursor cursor) {
@@ -401,6 +412,13 @@
// A null parent URI means that this is a top-level folder.
final String parentString = cursor.getString(UIProvider.FOLDER_PARENT_URI_COLUMN);
parent = parentString == null ? Uri.EMPTY : Uri.parse(parentString);
+ final int unreadSendersColumn =
+ cursor.getColumnIndex(UIProvider.FolderColumns.UNREAD_SENDERS);
+ if (unreadSendersColumn != -1) {
+ unreadSenders = cursor.getString(unreadSendersColumn);
+ } else {
+ unreadSenders = null;
+ }
}
/**
@@ -451,6 +469,7 @@
parent = in.readParcelable(loader);
lastMessageTimestamp = in.readLong();
parent = in.readParcelable(loader);
+ unreadSenders = in.readString();
}
@Override
@@ -481,6 +500,7 @@
dest.writeParcelable(parent, 0);
dest.writeLong(lastMessageTimestamp);
dest.writeParcelable(parent, 0);
+ dest.writeString(unreadSenders);
}
/**
@@ -765,60 +785,69 @@
return (isDraft() || isTrash() || isType(FolderType.OUTBOX));
}
+ /**
+ * This method is only used for parsing folders out of legacy intent extras, and only the
+ * folderUri and conversationListUri fields are actually read before the object is discarded.
+ * TODO: replace this with a parsing function that just directly returns those values
+ * @param inString UR8 or earlier EXTRA_FOLDER intent extra string
+ * @return Constructed folder object
+ */
@Deprecated
public static Folder fromString(String inString) {
- if (TextUtils.isEmpty(inString)) {
- return null;
- }
- final Folder f = new Folder();
- int indexOf = inString.indexOf(SPLITTER);
- int id = -1;
- if (indexOf != -1) {
- id = Integer.valueOf(inString.substring(0, indexOf));
- } else {
- // If no separator was found, we can't parse this folder and the
- // TextUtils.split call would also fail. Return null.
- return null;
- }
- final String[] split = TextUtils.split(inString, SPLITTER_REGEX);
- if (split.length < 20) {
- LogUtils.e(LOG_TAG, "split.length %d", split.length);
- return null;
- }
- f.id = id;
- int index = 1;
- f.folderUri = new FolderUri(Folder.getValidUri(split[index++]));
- f.name = split[index++];
- f.hasChildren = Integer.parseInt(split[index++]) != 0;
- f.capabilities = Integer.parseInt(split[index++]);
- f.syncWindow = Integer.parseInt(split[index++]);
- f.conversationListUri = getValidUri(split[index++]);
- f.childFoldersListUri = getValidUri(split[index++]);
- f.unreadCount = Integer.parseInt(split[index++]);
- f.totalCount = Integer.parseInt(split[index++]);
- f.refreshUri = getValidUri(split[index++]);
- f.syncStatus = Integer.parseInt(split[index++]);
- f.lastSyncResult = Integer.parseInt(split[index++]);
- f.type = Integer.parseInt(split[index++]);
- f.iconResId = Integer.parseInt(split[index++]);
- f.bgColor = split[index++];
- f.fgColor = split[index++];
- if (f.bgColor != null) {
- f.bgColorInt = Integer.parseInt(f.bgColor);
- }
- if (f.fgColor != null) {
- f.fgColorInt = Integer.parseInt(f.fgColor);
- }
- f.loadMoreUri = getValidUri(split[index++]);
- f.hierarchicalDesc = split[index++];
- f.parent = Folder.getValidUri(split[index++]);
- return f;
- }
+ if (TextUtils.isEmpty(inString)) {
+ return null;
+ }
+ final Folder f = new Folder();
+ int indexOf = inString.indexOf(SPLITTER);
+ int id = -1;
+ if (indexOf != -1) {
+ id = Integer.valueOf(inString.substring(0, indexOf));
+ } else {
+ // If no separator was found, we can't parse this folder and the
+ // TextUtils.split call would also fail. Return null.
+ return null;
+ }
+ final String[] split = TextUtils.split(inString, SPLITTER_REGEX);
+ if (split.length < 20) {
+ LogUtils.e(LOG_TAG, "split.length %d", split.length);
+ return null;
+ }
+ f.id = id;
+ int index = 1;
+ f.folderUri = new FolderUri(Folder.getValidUri(split[index++]));
+ f.name = split[index++];
+ f.hasChildren = Integer.parseInt(split[index++]) != 0;
+ f.capabilities = Integer.parseInt(split[index++]);
+ f.syncWindow = Integer.parseInt(split[index++]);
+ f.conversationListUri = getValidUri(split[index++]);
+ f.childFoldersListUri = getValidUri(split[index++]);
+ f.unreadCount = Integer.parseInt(split[index++]);
+ f.totalCount = Integer.parseInt(split[index++]);
+ f.refreshUri = getValidUri(split[index++]);
+ f.syncStatus = Integer.parseInt(split[index++]);
+ f.lastSyncResult = Integer.parseInt(split[index++]);
+ f.type = Integer.parseInt(split[index++]);
+ f.iconResId = Integer.parseInt(split[index++]);
+ f.bgColor = split[index++];
+ f.fgColor = split[index++];
+ if (f.bgColor != null) {
+ f.bgColorInt = Integer.parseInt(f.bgColor);
+ }
+ if (f.fgColor != null) {
+ f.fgColorInt = Integer.parseInt(f.fgColor);
+ }
+ f.loadMoreUri = getValidUri(split[index++]);
+ f.hierarchicalDesc = split[index++];
+ f.parent = Folder.getValidUri(split[index++]);
+ f.unreadSenders = null;
+
+ return f;
+ }
private static Uri getValidUri(String uri) {
- if (TextUtils.isEmpty(uri)) {
- return null;
- }
- return Uri.parse(uri);
+ if (TextUtils.isEmpty(uri)) {
+ return null;
+ }
+ return Uri.parse(uri);
}
}
diff --git a/src/com/android/mail/providers/Message.java b/src/com/android/mail/providers/Message.java
index 35e1d79..792ed12 100644
--- a/src/com/android/mail/providers/Message.java
+++ b/src/com/android/mail/providers/Message.java
@@ -25,8 +25,9 @@
import android.os.Parcelable;
import android.provider.BaseColumns;
import android.text.Html;
-import android.text.SpannedString;
+import android.text.SpannableString;
import android.text.TextUtils;
+import android.text.util.Linkify;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
@@ -605,7 +606,9 @@
if (!TextUtils.isEmpty(bodyHtml)) {
body = bodyHtml;
} else if (!TextUtils.isEmpty(bodyText)) {
- body = Html.toHtml(new SpannedString(bodyText));
+ final SpannableString spannable = new SpannableString(bodyText);
+ Linkify.addLinks(spannable, Linkify.EMAIL_ADDRESSES);
+ body = Html.toHtml(spannable);
}
return body;
}
diff --git a/src/com/android/mail/providers/ReplyFromAccount.java b/src/com/android/mail/providers/ReplyFromAccount.java
index ff13ae7..875d22e 100644
--- a/src/com/android/mail/providers/ReplyFromAccount.java
+++ b/src/com/android/mail/providers/ReplyFromAccount.java
@@ -71,7 +71,7 @@
json.put(IS_DEFAULT, isDefault);
json.put(IS_CUSTOM_FROM, isCustomFrom);
} catch (JSONException e) {
- LogUtils.wtf(LOG_TAG, e, "Could not serialize account with name " + name);
+ LogUtils.wtf(LOG_TAG, e, "Could not serialize account with address " + address);
}
return json;
}
@@ -111,13 +111,14 @@
List<ReplyFromAccount> replyFromAccounts) {
Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(possibleCustomFrom);
if (tokens != null && tokens.length > 0) {
- String parsedFromAddress = tokens[0].getAddress();
- if (TextUtils.equals(account.getEmailAddress(), parsedFromAddress)) {
+ String parsedFromAddress = Utils.normalizeEmailAddress(tokens[0].getAddress());
+ if (TextUtils.equals(Utils.normalizeEmailAddress(account.getEmailAddress()),
+ parsedFromAddress)) {
return true;
}
for (ReplyFromAccount replyFromAccount : replyFromAccounts) {
- if (TextUtils.equals(replyFromAccount.address, parsedFromAddress)
- && replyFromAccount.isCustomFrom) {
+ if (TextUtils.equals(Utils.normalizeEmailAddress(replyFromAccount.address),
+ parsedFromAddress) && replyFromAccount.isCustomFrom) {
return true;
}
}
diff --git a/src/com/android/mail/providers/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index ccc40a6..e4bc961 100644
--- a/src/com/android/mail/providers/UIProvider.java
+++ b/src/com/android/mail/providers/UIProvider.java
@@ -27,6 +27,7 @@
import android.provider.OpenableColumns;
import android.text.TextUtils;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.Map;
@@ -131,6 +132,7 @@
new ImmutableMap.Builder<String, Class<?>>()
.put(AccountColumns._ID, Integer.class)
.put(AccountColumns.NAME, String.class)
+ .put(AccountColumns.SENDER_NAME, String.class)
.put(AccountColumns.ACCOUNT_MANAGER_NAME, String.class)
.put(AccountColumns.TYPE, String.class)
.put(AccountColumns.PROVIDER_VERSION, Integer.class)
@@ -318,6 +320,11 @@
public static final String NAME = "name";
/**
+ * This string column contains the real name associated with the account, e.g. "John Doe"
+ */
+ public static final String SENDER_NAME = "senderName";
+
+ /**
* This string column contains the account manager name of this account.
*/
@@ -697,6 +704,12 @@
FolderColumns.PARENT_URI
};
+ public static final String[] FOLDERS_PROJECTION_WITH_UNREAD_SENDERS =
+ (new ImmutableList.Builder<String>()
+ .addAll(ImmutableList.copyOf(FOLDERS_PROJECTION))
+ .add(FolderColumns.UNREAD_SENDERS)
+ .build().toArray(new String[0]));
+
public static final int FOLDER_ID_COLUMN = 0;
public static final int FOLDER_PERSISTENT_ID_COLUMN = 1;
public static final int FOLDER_URI_COLUMN = 2;
@@ -938,6 +951,12 @@
*/
public static final String PARENT_URI = "parentUri";
+ /**
+ * A string of unread senders sorted by date, so we don't have to fetch this in multiple
+ * queries
+ */
+ public static final String UNREAD_SENDERS = "unreadSenders";
+
public FolderColumns() {}
}
diff --git a/src/com/android/mail/ui/AbstractConversationWebViewClient.java b/src/com/android/mail/ui/AbstractConversationWebViewClient.java
index b372cb0..bf17eaf 100644
--- a/src/com/android/mail/ui/AbstractConversationWebViewClient.java
+++ b/src/com/android/mail/ui/AbstractConversationWebViewClient.java
@@ -69,20 +69,20 @@
return false;
}
- boolean result = false;
- final Intent intent;
final Uri uri = Uri.parse(url);
+ if (Utils.divertMailtoUri(mActivity, uri, mAccount)) {
+ return true;
+ }
+
+ final Intent intent;
if (mAccount != null && !Utils.isEmpty(mAccount.viewIntentProxyUri)) {
intent = generateProxyIntent(uri);
} else {
intent = new Intent(Intent.ACTION_VIEW, uri);
-
- // If this is a mailto: uri, we want to set the account name in the intent so
- // the ComposeActivity can default to the current account
- Utils.addAccountToMailtoIntent(intent, mAccount);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, mActivity.getPackageName());
}
+ boolean result = false;
try {
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
diff --git a/src/com/android/mail/ui/FolderSelectionActivity.java b/src/com/android/mail/ui/FolderSelectionActivity.java
index 2baa472..3573ce6 100644
--- a/src/com/android/mail/ui/FolderSelectionActivity.java
+++ b/src/com/android/mail/ui/FolderSelectionActivity.java
@@ -345,9 +345,11 @@
return null;
}
+ private Folder mNavigatedFolder;
@Override
public void onFolderSelected(Folder folder) {
- if (folder.hasChildren) {
+ if (folder.hasChildren && !folder.equals(mNavigatedFolder)) {
+ mNavigatedFolder = folder;
// Replace this fragment with a new FolderListFragment
// showing this folder's children if we are not already looking
// at the child view for this folder.
diff --git a/src/com/android/mail/ui/FolderSelectionDialog.java b/src/com/android/mail/ui/FolderSelectionDialog.java
index 2c50306..2885599 100644
--- a/src/com/android/mail/ui/FolderSelectionDialog.java
+++ b/src/com/android/mail/ui/FolderSelectionDialog.java
@@ -76,6 +76,8 @@
sDialogShown = false;
}
+ // TODO: use a loader instead
+ @Deprecated
protected abstract void updateAdapterInBackground(Context context);
protected abstract void onListItemClick(int position);
@@ -98,7 +100,8 @@
public void show() {
sDialogShown = true;
- mRunner.execute();
+ // TODO: use a loader instead
+ mRunner.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
protected void showInternal() {
@@ -121,6 +124,8 @@
* Class to query the Folder list database in the background and update the
* adapter with an open cursor.
*/
+ // TODO: use a loader instead
+ @Deprecated
private class QueryRunner extends AsyncTask<Void, Void, Void> {
private final Context mContext;
diff --git a/src/com/android/mail/ui/NestedFolderTeaserView.java b/src/com/android/mail/ui/NestedFolderTeaserView.java
index bf981bf..f8fb44a 100644
--- a/src/com/android/mail/ui/NestedFolderTeaserView.java
+++ b/src/com/android/mail/ui/NestedFolderTeaserView.java
@@ -39,6 +39,7 @@
import com.android.mail.content.ObjectCursor;
import com.android.mail.content.ObjectCursorLoader;
import com.android.mail.providers.Account;
+import com.android.mail.providers.Address;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.Folder;
import com.android.mail.providers.MessageInfo;
@@ -51,11 +52,14 @@
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* The teaser list item in the conversation list that shows nested folders.
@@ -479,6 +483,7 @@
* unread count has changed.
*/
if (oldFolder == null || oldFolder.unreadCount != folder.unreadCount) {
+ populateUnreadSenders(holder, folder.unreadSenders);
updateViews(holder);
}
} else {
@@ -491,6 +496,7 @@
// because it doesn't scale. Disabling it for now, until we can
// optimize it.
// initFolderLoader(getLoaderId(folder.id));
+ populateUnreadSenders(newHolder, folder.unreadSenders);
updateViews(newHolder);
@@ -528,12 +534,51 @@
@Override
public Loader<ObjectCursor<Folder>> onCreateLoader(final int id, final Bundle args) {
final ObjectCursorLoader<Folder> loader = new ObjectCursorLoader<Folder>(getContext(),
- mFolderListUri, UIProvider.FOLDERS_PROJECTION, Folder.FACTORY);
+ mFolderListUri, UIProvider.FOLDERS_PROJECTION_WITH_UNREAD_SENDERS,
+ Folder.FACTORY);
loader.setUpdateThrottle(mFolderItemUpdateDelayMs);
return loader;
}
};
+ /**
+ * This code is intended to roughly duplicate the FolderLoaderCallback's onLoadFinished
+ */
+ private void populateUnreadSenders(final FolderHolder folderHolder,
+ final String unreadSenders) {
+ if (TextUtils.isEmpty(unreadSenders)) {
+ folderHolder.setUnreadSenders(Collections.<String>emptyList());
+ return;
+ }
+ // Use a LinkedHashMap here to maintain ordering
+ final Map<String, String> emailtoNameMap = Maps.newLinkedHashMap();
+
+ final Address[] senderAddresses = Address.parse(unreadSenders);
+
+ for (final Address senderAddress : senderAddresses) {
+ String sender = senderAddress.getName();
+ final String senderEmail = senderAddress.getAddress();
+
+ if (!TextUtils.isEmpty(sender)) {
+ final String existingSender = emailtoNameMap.get(senderEmail);
+ if (!TextUtils.isEmpty(existingSender)) {
+ // Prefer longer names
+ if (existingSender.length() >= sender.length()) {
+ // old name is longer
+ sender = existingSender;
+ }
+ }
+ emailtoNameMap.put(senderEmail, sender);
+ }
+ if (emailtoNameMap.size() >= 20) {
+ break;
+ }
+ }
+
+ final List<String> senders = Lists.newArrayList(emailtoNameMap.values());
+ folderHolder.setUnreadSenders(senders);
+ }
+
private final LoaderCallbacks<ObjectCursor<Conversation>> mFolderLoaderCallbacks =
new LoaderCallbacks<ObjectCursor<Conversation>>() {
@Override
diff --git a/src/com/android/mail/ui/ThumbnailLoadTask.java b/src/com/android/mail/ui/ThumbnailLoadTask.java
index c30864e..f4d2d35 100644
--- a/src/com/android/mail/ui/ThumbnailLoadTask.java
+++ b/src/com/android/mail/ui/ThumbnailLoadTask.java
@@ -144,7 +144,8 @@
}
return originalBitmap;
} catch (Throwable t) {
- LogUtils.e(LOG_TAG, t, "Unable to decode thumbnail %s", thumbnailUri);
+ LogUtils.i(LOG_TAG, "Unable to decode thumbnail %s: %s %s", thumbnailUri,
+ t.getClass(), t.getMessage());
} finally {
if (fd != null) {
try {
@@ -169,7 +170,8 @@
in = resolver.openInputStream(thumbnailUri);
return Exif.getOrientation(in, -1);
} catch (Throwable t) {
- LogUtils.e(LOG_TAG, t, "Unable to get orientation of thumbnail %s", thumbnailUri);
+ LogUtils.i(LOG_TAG, "Unable to get orientation of thumbnail %s: %s %s", thumbnailUri,
+ t.getClass(), t.getMessage());
} finally {
if (in != null) {
try {
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 32c8de7..848f10e 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -1322,12 +1322,22 @@
}
/**
- * Adds the Account extra to mailto intents.
+ * Convenience method for diverting mailto: uris directly to our compose activity. Using this
+ * method ensures that the Account object is not accidentally sent to a different process.
+ *
+ * @param context for sending the intent
+ * @param uri mailto: or other uri
+ * @param account desired account for potential compose activity
+ * @return true if a compose activity was started, false if uri should be sent to a view intent
*/
- public static void addAccountToMailtoIntent(Intent intent, Account account) {
- if (TextUtils.equals(MAILTO_SCHEME, intent.getData().getScheme())) {
- intent.putExtra(Utils.EXTRA_ACCOUNT, account);
+ public static boolean divertMailtoUri(final Context context, final Uri uri,
+ final Account account) {
+ final String scheme = normalizeUri(uri).getScheme();
+ if (TextUtils.equals(MAILTO_SCHEME, scheme)) {
+ ComposeActivity.composeToAddress(context, account, uri.getSchemeSpecificPart());
+ return true;
}
+ return false;
}
/**
@@ -1408,4 +1418,32 @@
}
}
+ /**
+ * Email addresses are supposed to be treated as case-insensitive for the host-part and
+ * case-sensitive for the local-part, but nobody really wants email addresses to match
+ * case-sensitive on the local-part, so just smash everything to lower case.
+ * @param email Hello@Example.COM
+ * @return hello@example.com
+ */
+ public static String normalizeEmailAddress(String email) {
+ /*
+ // The RFC5321 version
+ if (TextUtils.isEmpty(email)) {
+ return email;
+ }
+ String[] parts = email.split("@");
+ if (parts.length != 2) {
+ LogUtils.d(LOG_TAG, "Tried to normalize a malformed email address: ", email);
+ return email;
+ }
+
+ return parts[0] + "@" + parts[1].toLowerCase(Locale.US);
+ */
+ if (TextUtils.isEmpty(email)) {
+ return email;
+ } else {
+ // Doing this for other locales might really screw things up, so do US-version only
+ return email.toLowerCase(Locale.US);
+ }
+ }
}
diff --git a/tests/src/com/android/mail/compose/ComposeActivityTest.java b/tests/src/com/android/mail/compose/ComposeActivityTest.java
index c2c55de..6f97341 100644
--- a/tests/src/com/android/mail/compose/ComposeActivityTest.java
+++ b/tests/src/com/android/mail/compose/ComposeActivityTest.java
@@ -194,7 +194,7 @@
String[] bcc = activity.getBccAddresses();
String toAsString = TextUtils.join(",", to);
assertEquals(1, to.length);
- assertTrue(toAsString.contains(account.name));
+ assertTrue(toAsString.contains(account.getEmailAddress()));
assertEquals(0, cc.length);
assertEquals(0, bcc.length);
}
@@ -686,7 +686,7 @@
Rfc822Tokenizer.tokenize(to[0])[0].getAddress());
assertEquals(0, cc.length);
assertEquals(0, bcc.length);
- assertEquals("account0@mockuiprovider.com", fromAccount.name);
+ assertEquals("account0@mockuiprovider.com", fromAccount.getEmailAddress());
}
});
}
@@ -726,13 +726,13 @@
Rfc822Tokenizer.tokenize(to[0])[0].getAddress());
assertEquals(0, cc.length);
assertEquals(0, bcc.length);
- assertEquals("account2@mockuiprovider.com", fromAccount.name);
+ assertEquals("account2@mockuiprovider.com", fromAccount.getEmailAddress());
}
});
}
- // Test a mailto VIEW Intent, with an account specified
- public void testMailToAccount() throws Throwable {
+ // Test a mailto VIEW Intent, with an account specified in JSON format
+ public void testMailToAccountJSON() throws Throwable {
final Context context = getInstrumentation().getContext();
// Get the test account
final Account currentAccount = getAccountForName(context, "account2@mockuiprovider.com");
@@ -740,7 +740,7 @@
// Create the mailto intent
final Intent mailtoIntent =
new Intent(Intent.ACTION_VIEW, Uri.parse("mailto:test@localhost.com"));
- Utils.addAccountToMailtoIntent(mailtoIntent, currentAccount);
+ mailtoIntent.putExtra(Utils.EXTRA_ACCOUNT, currentAccount.serialize());
setActivityIntent(mailtoIntent);
@@ -759,7 +759,42 @@
Rfc822Tokenizer.tokenize(to[0])[0].getAddress());
assertEquals(0, cc.length);
assertEquals(0, bcc.length);
- assertEquals("account2@mockuiprovider.com", fromAccount.name);
+ assertEquals("account2@mockuiprovider.com", fromAccount.getEmailAddress());
+ }
+ });
+ }
+
+ // Test a COMPOSE Intent, with an account specified in parcel format
+ public void testMailToAccount() throws Throwable {
+ final Context context = getInstrumentation().getContext();
+ // Get the test account
+ final Account currentAccount = getAccountForName(context, "account2@mockuiprovider.com");
+
+ // Create the mailto intent
+ Intent intent = new Intent(context, ComposeActivity.class);
+ intent.putExtra(ComposeActivity.EXTRA_FROM_EMAIL_TASK, true);
+ intent.putExtra(ComposeActivity.EXTRA_ACTION, ComposeActivity.COMPOSE);
+ intent.putExtra(Utils.EXTRA_ACCOUNT, currentAccount);
+ intent.putExtra(ComposeActivity.EXTRA_TO, "test@localhost.com");
+
+ setActivityIntent(intent);
+
+ final ComposeActivity activity = getActivity();
+ Account fromAccount = activity.getFromAccount();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ String[] to = activity.getToAddresses();
+ String[] cc = activity.getCcAddresses();
+ String[] bcc = activity.getBccAddresses();
+ Account fromAccount = activity.getFromAccount();
+ assertEquals( 1, to.length);
+ assertEquals("test@localhost.com",
+ Rfc822Tokenizer.tokenize(to[0])[0].getAddress());
+ assertEquals(0, cc.length);
+ assertEquals(0, bcc.length);
+ assertEquals("account2@mockuiprovider.com", fromAccount.getEmailAddress());
}
});
}
@@ -795,7 +830,7 @@
Rfc822Tokenizer.tokenize(to[0])[0].getAddress());
assertEquals(0, cc.length);
assertEquals(0, bcc.length);
- assertEquals("account1@mockuiprovider.com", fromAccount.name);
+ assertEquals("account1@mockuiprovider.com", fromAccount.getEmailAddress());
}
});
}
diff --git a/tests/src/com/android/mail/utils/NormalizeEmailAddressTest.java b/tests/src/com/android/mail/utils/NormalizeEmailAddressTest.java
new file mode 100644
index 0000000..3778a1f
--- /dev/null
+++ b/tests/src/com/android/mail/utils/NormalizeEmailAddressTest.java
@@ -0,0 +1,27 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+
+package com.android.mail.utils;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.mail.utils.Utils;
+
+@SmallTest
+public class NormalizeEmailAddressTest extends AndroidTestCase {
+ public void testNormalizeEmailAddress() {
+ final String emailAddress = "user@example.com";
+
+ assertEquals(Utils.normalizeEmailAddress("User@EXAMPLE.COM"), emailAddress);
+
+ assertEquals(Utils.normalizeEmailAddress("User@example.com"), emailAddress);
+
+ assertEquals(Utils.normalizeEmailAddress("User@exaMple.com"), emailAddress);
+
+ assertEquals(Utils.normalizeEmailAddress(null), null);
+
+ assertEquals(Utils.normalizeEmailAddress(""), "");
+
+ assertEquals(Utils.normalizeEmailAddress("Not an EMAIL address"), "not an email address");
+ }
+}
\ No newline at end of file