Merge "Change the fonts / sizes of the chips labels to match the rest of the compose activity."
diff --git a/res/drawable-hdpi/ic_notification_multiple_mail_holo_dark.png b/res/drawable-hdpi/ic_notification_multiple_mail_holo_dark.png
new file mode 100644
index 0000000..352b688
--- /dev/null
+++ b/res/drawable-hdpi/ic_notification_multiple_mail_holo_dark.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_notification_multiple_mail_holo_dark.png b/res/drawable-mdpi/ic_notification_multiple_mail_holo_dark.png
new file mode 100644
index 0000000..529cb01
--- /dev/null
+++ b/res/drawable-mdpi/ic_notification_multiple_mail_holo_dark.png
Binary files differ
diff --git a/res/drawable-xhdpi/ic_notification_multiple_mail_holo_dark.png b/res/drawable-xhdpi/ic_notification_multiple_mail_holo_dark.png
new file mode 100644
index 0000000..c018d73
--- /dev/null
+++ b/res/drawable-xhdpi/ic_notification_multiple_mail_holo_dark.png
Binary files differ
diff --git a/res/layout/message_list_item_normal.xml b/res/layout/message_list_item_normal.xml
index 5ebecce..fb4e3fc 100644
--- a/res/layout/message_list_item_normal.xml
+++ b/res/layout/message_list_item_normal.xml
@@ -57,7 +57,6 @@
         android:layout_weight="1"
         android:orientation="vertical"
         android:paddingRight="16dip"
-        android:paddingBottom="15dip"
         >
         <View
             android:id="@+id/color_chip"
@@ -71,8 +70,7 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignParentRight="true"
-            android:layout_alignParentBottom="true"
-            android:layout_marginBottom="16dip"
+            android:layout_centerVertical="true"
             android:src="@drawable/btn_star_off_normal_holo_light" />
 
         <LinearLayout
@@ -118,6 +116,7 @@
             android:layout_alignParentLeft="true"
             android:layout_alignParentBottom="true"
             android:layout_toLeftOf="@+id/star"
+            android:layout_marginBottom="8dip"
             android:text="@string/long_string"
             android:textSize="@dimen/subject_font_size"
             android:lines="2" />
diff --git a/res/layout/message_list_item_wide.xml b/res/layout/message_list_item_wide.xml
index 8354b93..a645880 100644
--- a/res/layout/message_list_item_wide.xml
+++ b/res/layout/message_list_item_wide.xml
@@ -75,8 +75,8 @@
 
         <ImageView
             android:id="@+id/paperclip"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
+            android:layout_width="36dip"
+            android:layout_height="16dip"
             android:layout_centerVertical="true"
             android:src="@drawable/ic_attachment_holo_light" />
 
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index c375849..1cdcc66 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -219,21 +219,10 @@
         <item>@color/combined_view_account_color_9</item>
     </array>
 
-    <!-- TODO: rename -->
-    <!-- Values in dip of the message list item heights in the various modes:
-         wide, normal, and narrow -->
-    <integer-array name="conversation_heights">
-        <item>61</item>
-        <item>77</item>
-        <item>77</item>
-    </integer-array>
-
-    <!-- TODO: rename to subject_widths -->
     <!-- Length of the subject text in the message list items in dips for the
-         various modes: wide, normal, and narrow -->
+         various modes: wide, and narrow -->
     <integer-array name="subject_lengths">
         <item>50</item>
-        <item>50</item>
         <item>25</item>
     </integer-array>
 </resources>
diff --git a/src/com/android/email/NotificationController.java b/src/com/android/email/NotificationController.java
index 737580d..5ed3e01 100644
--- a/src/com/android/email/NotificationController.java
+++ b/src/com/android/email/NotificationController.java
@@ -84,6 +84,7 @@
     private final NotificationManager mNotificationManager;
     private final AudioManager mAudioManager;
     private final Bitmap mGenericSenderIcon;
+    private final Bitmap mGenericMultipleSenderIcon;
     private final Clock mClock;
     // TODO We're maintaining all of our structures based upon the account ID. This is fine
     // for now since the assumption is that we only ever look for changes in an account's
@@ -121,6 +122,8 @@
         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         mGenericSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
                 R.drawable.ic_contact_picture);
+        mGenericMultipleSenderIcon = BitmapFactory.decodeResource(mContext.getResources(),
+                R.drawable.ic_notification_multiple_mail_holo_dark);
         mClock = clock;
         mNotificationMap = new HashMap<Long, ContentObserver>();
     }
@@ -423,7 +426,9 @@
             senderName = ""; // Happens when a message has no from.
         }
         final boolean multipleUnseen = unseenMessageCount > 1;
-        final Bitmap senderPhoto = multipleUnseen ? null : getSenderPhoto(message);
+        final Bitmap senderPhoto = multipleUnseen
+                ? mGenericMultipleSenderIcon
+                : getSenderPhoto(message);
         final SpannableString title = getNewMessageTitle(senderName, unseenMessageCount);
         // TODO: add in display name on the second line for the text, once framework supports
         // multiline texts.
diff --git a/src/com/android/email/activity/AccountSelectorAdapter.java b/src/com/android/email/activity/AccountSelectorAdapter.java
index a1c3335..177a5a1 100644
--- a/src/com/android/email/activity/AccountSelectorAdapter.java
+++ b/src/com/android/email/activity/AccountSelectorAdapter.java
@@ -156,14 +156,14 @@
             displayNameView.setText(displayName);
 
             // Show the email address only when it's different from the display name.
-            if (displayName.equals(emailAddress)) {
+            boolean isAccount = isAccountItem(c);
+            if (displayName.equals(emailAddress) || !isAccount) {
                 emailAddressView.setVisibility(View.GONE);
             } else {
                 emailAddressView.setVisibility(View.VISIBLE);
                 emailAddressView.setText(emailAddress);
             }
 
-            boolean isAccount = isAccountItem(c);
             long id = getId(c);
             if (isAccount || id != Mailbox.NO_MAILBOX) {
                 unreadCountView.setVisibility(View.VISIBLE);
diff --git a/src/com/android/email/activity/ActionBarController.java b/src/com/android/email/activity/ActionBarController.java
index 2674fb6..8f47472 100644
--- a/src/com/android/email/activity/ActionBarController.java
+++ b/src/com/android/email/activity/ActionBarController.java
@@ -444,6 +444,7 @@
 
         if (mTitleMode == Callback.TITLE_MODE_MESSAGE_SUBJECT) {
             mAccountSpinnerLine1View.setSingleLine(false);
+            mAccountSpinnerLine1View.setMaxLines(2);
             mAccountSpinnerLine1View.setText(mCallback.getMessageSubject());
             mAccountSpinnerLine2View.setVisibility(View.GONE);
 
@@ -460,7 +461,10 @@
                 mailboxName = null;
             }
 
+            // Note - setSingleLine is needed as well as setMaxLines since they set different
+            // flags on the view.
             mAccountSpinnerLine1View.setSingleLine();
+            mAccountSpinnerLine1View.setMaxLines(1);
             if (TextUtils.isEmpty(mailboxName)) {
                 mAccountSpinnerLine1View.setText(mCursor.getAccountDisplayName());
 
diff --git a/src/com/android/email/activity/MessageListItemCoordinates.java b/src/com/android/email/activity/MessageListItemCoordinates.java
index b10953e..b453193 100644
--- a/src/com/android/email/activity/MessageListItemCoordinates.java
+++ b/src/com/android/email/activity/MessageListItemCoordinates.java
@@ -45,7 +45,6 @@
 
     // Static threshold.
     private static int MINIMUM_WIDTH_WIDE_MODE = -1;
-    private static int[] CONVERSATION_HEIGHTS;
     private static int[] SUBJECT_LENGTHS;
 
     // Checkmark.
@@ -153,13 +152,10 @@
      * Returns the height of the view in this mode.
      */
     public static int getHeight(Context context, int mode) {
-        Resources res = context.getResources();
-        float density = res.getDisplayMetrics().scaledDensity;
-        if (CONVERSATION_HEIGHTS == null) {
-            CONVERSATION_HEIGHTS = getDensityDependentArray(
-                    res.getIntArray(R.array.conversation_heights), density);
-        }
-        return CONVERSATION_HEIGHTS[mode];
+        return context.getResources().getDimensionPixelSize(
+                (mode == WIDE_MODE)
+                        ? R.dimen.message_list_item_height_wide
+                        : R.dimen.message_list_item_height_normal);
     }
 
     /**
diff --git a/src/com/android/email/activity/MessageViewFragment.java b/src/com/android/email/activity/MessageViewFragment.java
index 88f1f22..26c695a 100644
--- a/src/com/android/email/activity/MessageViewFragment.java
+++ b/src/com/android/email/activity/MessageViewFragment.java
@@ -383,6 +383,7 @@
             case R.id.decline:
                 onRespondToInvite(EmailServiceConstants.MEETING_REQUEST_DECLINED,
                         R.string.message_view_invite_toast_no);
+                return;
 
             case R.id.more: {
                 PopupMenu popup = new PopupMenu(getActivity(), mMoreButton);
diff --git a/src/com/android/email/mail/store/ImapConnection.java b/src/com/android/email/mail/store/ImapConnection.java
index 4d4d16f..0fbf603 100644
--- a/src/com/android/email/mail/store/ImapConnection.java
+++ b/src/com/android/email/mail/store/ImapConnection.java
@@ -16,6 +16,9 @@
 
 package com.android.email.mail.store;
 
+import android.text.TextUtils;
+import android.util.Log;
+
 import com.android.email.Email;
 import com.android.email.mail.Transport;
 import com.android.email.mail.store.ImapStore.ImapException;
@@ -31,9 +34,6 @@
 import com.android.emailcommon.mail.CertificateValidationException;
 import com.android.emailcommon.mail.MessagingException;
 
-import android.text.TextUtils;
-import android.util.Log;
-
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -248,14 +248,53 @@
         return tag;
     }
 
+
+    /**
+     * Send a single, complex command to the server.  The command will be preceded by an IMAP
+     * command tag and followed by \r\n (caller need not supply them).  After each piece of the
+     * command, a response will be read which MUST be a continuation request.
+     *
+     * @param commands An array of Strings comprising the command to be sent to the server
+     * @return Returns the command tag that was sent
+     */
+    String sendComplexCommand(List<String> commands, boolean sensitive) throws MessagingException,
+            IOException {
+        open();
+        String tag = Integer.toString(mNextCommandTag.incrementAndGet());
+        int len = commands.size();
+        for (int i = 0; i < len; i++) {
+            String commandToSend = commands.get(i);
+            // The first part of the command gets the tag
+            if (i == 0) {
+                commandToSend = tag + " " + commandToSend;
+            } else {
+                // Otherwise, read the response from the previous part of the command
+                ImapResponse response = readResponse();
+                // If it isn't a continuation request, that's an error
+                if (!response.isContinuationRequest()) {
+                    throw new MessagingException("Expected continuation request");
+                }
+            }
+            // Send the command
+            mTransport.writeLine(commandToSend, null);
+            mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
+        }
+        return tag;
+    }
+
     List<ImapResponse> executeSimpleCommand(String command) throws IOException,
             MessagingException {
         return executeSimpleCommand(command, false);
     }
 
-    List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
-            throws IOException, MessagingException {
-        String tag = sendCommand(command, sensitive);
+    /**
+     * Read and return all of the responses from the most recent command sent to the server
+     *
+     * @return a list of ImapResponses
+     * @throws IOException
+     * @throws MessagingException
+     */
+    List<ImapResponse> getCommandResponses() throws IOException, MessagingException {
         ArrayList<ImapResponse> responses = new ArrayList<ImapResponse>();
         ImapResponse response;
         do {
@@ -272,6 +311,38 @@
     }
 
     /**
+     * Execute a simple command at the server, a simple command being one that is sent in a single
+     * line of text
+     *
+     * @param command the command to send to the server
+     * @param sensitive whether the command should be redacted in logs (used for login)
+     * @return a list of ImapResponses
+     * @throws IOException
+     * @throws MessagingException
+     */
+     List<ImapResponse> executeSimpleCommand(String command, boolean sensitive)
+            throws IOException, MessagingException {
+        sendCommand(command, sensitive);
+        return getCommandResponses();
+    }
+
+     /**
+      * Execute a complex command at the server, a complex command being one that must be sent in
+      * multiple lines due to the use of string literals
+      *
+      * @param commands a list of strings that comprise the command to be sent to the server
+      * @param sensitive whether the command should be redacted in logs (used for login)
+      * @return a list of ImapResponses
+      * @throws IOException
+      * @throws MessagingException
+      */
+      List<ImapResponse> executeComplexCommand(List<String> commands, boolean sensitive)
+            throws IOException, MessagingException {
+        sendComplexCommand(commands, sensitive);
+        return getCommandResponses();
+    }
+
+    /**
      * Query server for capabilities.
      */
     private ImapResponse queryCapabilities() throws IOException, MessagingException {
diff --git a/src/com/android/email/mail/store/ImapFolder.java b/src/com/android/email/mail/store/ImapFolder.java
index aa6722e..98d25c9 100644
--- a/src/com/android/email/mail/store/ImapFolder.java
+++ b/src/com/android/email/mail/store/ImapFolder.java
@@ -366,34 +366,37 @@
         throw new Error("ImapStore.delete() not yet implemented");
     }
 
-    /* package */ String[] searchForUids(String searchCriteria)
-            throws MessagingException {
+    String[] getSearchUids(List<ImapResponse> responses) {
+        // S: * SEARCH 2 3 6
+        final ArrayList<String> uids = new ArrayList<String>();
+        for (ImapResponse response : responses) {
+            if (!response.isDataResponse(0, ImapConstants.SEARCH)) {
+                continue;
+            }
+            // Found SEARCH response data
+            for (int i = 1; i < response.size(); i++) {
+                ImapString s = response.getStringOrEmpty(i);
+                if (s.isString()) {
+                    uids.add(s.getString());
+                }
+            }
+        }
+        return uids.toArray(Utility.EMPTY_STRINGS);
+    }
+
+    @VisibleForTesting
+    String[] searchForUids(String searchCriteria) throws MessagingException {
         checkOpen();
-        List<ImapResponse> responses;
         try {
             try {
-                responses = mConnection.executeSimpleCommand(
-                        ImapConstants.UID_SEARCH + " " + searchCriteria);
+                String command = ImapConstants.UID_SEARCH + " " + searchCriteria;
+                return getSearchUids(mConnection.executeSimpleCommand(command));
             } catch (ImapException e) {
+                Log.d(Logging.LOG_TAG, "ImapException in search: " + searchCriteria);
                 return Utility.EMPTY_STRINGS; // not found;
             } catch (IOException ioe) {
                 throw ioExceptionHandler(mConnection, ioe);
             }
-            // S: * SEARCH 2 3 6
-            final ArrayList<String> uids = new ArrayList<String>();
-            for (ImapResponse response : responses) {
-                if (!response.isDataResponse(0, ImapConstants.SEARCH)) {
-                    continue;
-                }
-                // Found SEARCH response data
-                for (int i = 1; i < response.size(); i++) {
-                    ImapString s = response.getStringOrEmpty(i);
-                    if (s.isString()) {
-                        uids.add(s.getString());
-                    }
-                }
-            }
-            return uids.toArray(Utility.EMPTY_STRINGS);
         } finally {
             destroyResponses();
         }
@@ -413,29 +416,58 @@
         return null;
     }
 
+    @VisibleForTesting
+    protected static boolean isAsciiString(String str) {
+        int len = str.length();
+        for (int i = 0; i < len; i++) {
+            char c = str.charAt(i);
+            if (c >= 128) return false;
+        }
+        return true;
+    }
+
     /**
      * Retrieve messages based on search parameters.  We search FROM, TO, CC, SUBJECT, and BODY
-     * We send: SEARCH OR FROM "foo" (OR TO "foo" (OR CC "foo" (OR SUBJECT "foo" BODY "foo")))
-     * TODO: Properly quote the filter
+     * We send: SEARCH OR FROM "foo" (OR TO "foo" (OR CC "foo" (OR SUBJECT "foo" BODY "foo"))), but
+     * with the additional CHARSET argument and sending "foo" as a literal (e.g. {3}<CRLF>foo}
      */
     @Override
     @VisibleForTesting
     public Message[] getMessages(SearchParams params, MessageRetrievalListener listener)
             throws MessagingException {
+        List<String> commands = new ArrayList<String>();
         String filter = params.mFilter;
-        StringBuilder sb = new StringBuilder();
-        sb.append("OR FROM \"");
-        sb.append(filter);
-        sb.append("\" (OR TO \"");
-        sb.append(filter);
-        sb.append("\" (OR CC \"");
-        sb.append(filter);
-        sb.append("\" (OR SUBJECT \"");
-        sb.append(filter);
-        sb.append("\" BODY \"");
-        sb.append(filter);
-        sb.append("\")))");
-        return getMessagesInternal(searchForUids(sb.toString()), listener);
+        // All servers MUST accept US-ASCII, so we'll send this as the CHARSET unless we're really
+        // dealing with a string that contains non-ascii characters
+        String charset = "US-ASCII";
+        if (!isAsciiString(filter)) {
+            charset = "UTF-8";
+        }
+        // This is the length of the string in octets (bytes), formatted as a string literal {n}
+        String octetLength = "{" + filter.getBytes().length + "}";
+        // Break the command up into pieces ending with the string literal length
+        commands.add(ImapConstants.UID_SEARCH + " CHARSET " + charset + " OR FROM " + octetLength);
+        commands.add(filter + " (OR TO " + octetLength);
+        commands.add(filter + " (OR CC " + octetLength);
+        commands.add(filter + " (OR SUBJECT " + octetLength);
+        commands.add(filter + " BODY " + octetLength);
+        commands.add(filter + ")))");
+        return getMessagesInternal(complexSearchForUids(commands), listener);
+    }
+
+    /* package */ String[] complexSearchForUids(List<String> commands) throws MessagingException {
+        checkOpen();
+        try {
+            try {
+                return getSearchUids(mConnection.executeComplexCommand(commands, false));
+            } catch (ImapException e) {
+                return Utility.EMPTY_STRINGS; // not found;
+            } catch (IOException ioe) {
+                throw ioExceptionHandler(mConnection, ioe);
+            }
+        } finally {
+            destroyResponses();
+        }
     }
 
     @Override