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