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