Restore sender snippet for nested folder teaser.

It's a bit ugly, but it's much better than firing up a new loader for each subfolder.

b/11288044

Change-Id: I509503e95ccbcfafbd89b755f2197d8a5f1ed1b8
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/UIProvider.java b/src/com/android/mail/providers/UIProvider.java
index b558464..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;
@@ -703,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;
@@ -944,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/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