diff --git a/emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java b/emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java
index 7382d30b2..6aa70d334 100644
--- a/emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java
+++ b/emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java
@@ -28,6 +28,7 @@ import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
+import android.text.Html;
import android.util.Base64;
import android.util.Base64OutputStream;
@@ -61,15 +62,64 @@ public class Rfc822Output {
private static final String WHERE_NOT_SMART_FORWARD = "(" + Attachment.FLAGS + "&" +
Attachment.FLAG_SMART_FORWARD + ")=0";
+ /** A less-than-perfect pattern to pull out
content */
+ private static final String BODY_PATTERN = "(?:<\\s*body[^>]*>)(.*)(?:<\\s*/\\s*body\\s*>)";
+ /** Index of the plain text version of the message body */
+ private final static int TEXT_BODY_IDX = 0;
+ /** Index of the HTML version of the message body */
+ private final static int HTML_BODY_IDX = 1;
+
+ /**
+ * Returns just the content between the tags. This is not perfect and breaks
+ * with malformed HTML or if there happens to be special characters in the attributes of
+ * the tag (e.g. a '>' in a java script block).
+ */
+ /*package*/ static String getHtmlBody(String html) {
+ Pattern bodyPattern =
+ Pattern.compile(BODY_PATTERN,Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
+ Matcher match = bodyPattern.matcher(html);
+ if (match.find()) {
+ return match.group(1); // Found body; return
+ } else {
+ return html; // Body not found; return the full HTML and hope for the best
+ }
+ }
+
+ /**
+ * Returns an HTML encoded message alternate
+ */
+ private static String getHtmlAlternate(Body body) {
+ if (body.mHtmlReply == null) {
+ return null;
+ }
+ StringBuffer altMessage = new StringBuffer();
+ altMessage.append(body.mTextContent.replaceAll("\\r?\\n", "
"));
+ if (body.mIntroText != null) {
+ altMessage.append(body.mIntroText.replaceAll("\\r?\\n", "
"));
+ }
+ String htmlBody = getHtmlBody(body.mHtmlReply);
+ altMessage.append(htmlBody);
+ return altMessage.toString();
+ }
+
+ /**
+ * Gets the plain text version of the message body.
+ */
/*package*/ static String buildBodyText(Context context, Message message,
boolean useSmartReply) {
Body body = Body.restoreBodyWithMessageId(context, message.mId);
- if (body == null) {
- return null;
- }
+ return buildBodyText(body, message.mFlags, useSmartReply)[TEXT_BODY_IDX];
+ }
+ /**
+ * Gets both the plain text and HTML versions of the message body.
+ */
+ private static String[] buildBodyText(Body body, int flags, boolean useSmartReply) {
+ String[] messageBody = new String[] { null, null };
+ if (body == null) {
+ return messageBody;
+ }
String text = body.mTextContent;
- int flags = message.mFlags;
boolean isReply = (flags & Message.FLAG_TYPE_REPLY) != 0;
boolean isForward = (flags & Message.FLAG_TYPE_FORWARD) != 0;
// For all forwards/replies, we add the intro text
@@ -83,26 +133,31 @@ public class Rfc822Output {
if (isForward) {
text += "\n";
}
- return text;
- }
-
- String quotedText = body.mTextReply;
- if (quotedText != null) {
- // fix CR-LF line endings to LF-only needed by EditText.
- Matcher matcher = PATTERN_ENDLINE_CRLF.matcher(quotedText);
- quotedText = matcher.replaceAll("\n");
- }
- if (isReply) {
- if (quotedText != null) {
- Matcher matcher = PATTERN_START_OF_LINE.matcher(quotedText);
- text += matcher.replaceAll(">");
+ } else {
+ String quotedText = body.mTextReply;
+ // If there is no plain-text body, use de-tagified HTML as the text body
+ if (quotedText == null && body.mHtmlReply != null) {
+ quotedText = Html.fromHtml(body.mHtmlReply).toString();
}
- } else if (isForward) {
if (quotedText != null) {
- text += quotedText;
+ // fix CR-LF line endings to LF-only needed by EditText.
+ Matcher matcher = PATTERN_ENDLINE_CRLF.matcher(quotedText);
+ quotedText = matcher.replaceAll("\n");
+ }
+ if (isReply) {
+ if (quotedText != null) {
+ Matcher matcher = PATTERN_START_OF_LINE.matcher(quotedText);
+ text += matcher.replaceAll(">");
+ }
+ } else if (isForward) {
+ if (quotedText != null) {
+ text += quotedText;
+ }
}
}
- return text;
+ messageBody[TEXT_BODY_IDX] = text;
+ messageBody[HTML_BODY_IDX] = getHtmlAlternate(body);
+ return messageBody;
}
/**
@@ -112,9 +167,13 @@ public class Rfc822Output {
* @param context system context for accessing the provider
* @param messageId the message to write out
* @param out the output stream to write the message to
+<<<<<<< HEAD:emailcommon/src/com/android/emailcommon/internet/Rfc822Output.java
* @param useSmartReply whether or not quoted text is appended to a reply/forward
*
* TODO alternative parts (e.g. text+html) are not supported here.
+=======
+ * @param useSmartReply whether or not to append quoted text if this is a reply/forward
+>>>>>>> 5912e7c... Attach original HTML message on forward/reply:src/com/android/emailcommon/internet/Rfc822Output.java
*/
public static void writeTo(Context context, long messageId, OutputStream out,
boolean useSmartReply, boolean sendBcc) throws IOException, MessagingException {
@@ -149,7 +208,8 @@ public class Rfc822Output {
writeHeader(writer, "MIME-Version", "1.0");
// Analyze message and determine if we have multiparts
- String text = buildBodyText(context, message, useSmartReply);
+ Body body = Body.restoreBodyWithMessageId(context, message.mId);
+ String[] bodyText = buildBodyText(body, message.mFlags, useSmartReply);
Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, messageId);
Cursor attachmentsCursor = context.getContentResolver().query(uri,
@@ -163,11 +223,7 @@ public class Rfc822Output {
// Simplified case for no multipart - just emit text and be done.
if (!multipart) {
- if (text != null) {
- writeTextWithHeaders(writer, stream, text);
- } else {
- writer.write("\r\n"); // a truly empty message
- }
+ writeTextWithHeaders(writer, stream, bodyText);
} else {
// continue with multipart headers, then into multipart body
multipartBoundary = "--_com.android.email_" + System.nanoTime();
@@ -189,9 +245,9 @@ public class Rfc822Output {
writer.write("\r\n");
// first multipart element is the body
- if (text != null) {
+ if (bodyText[TEXT_BODY_IDX] != null) {
writeBoundary(writer, multipartBoundary, false);
- writeTextWithHeaders(writer, stream, text);
+ writeTextWithHeaders(writer, stream, bodyText);
}
// Write out the attachments until we run out
@@ -230,7 +286,9 @@ public class Rfc822Output {
+ "\n filename=\"" + attachment.mFileName + "\";"
+ "\n size=" + Long.toString(attachment.mSize));
}
- writeHeader(writer, "Content-ID", attachment.mContentId);
+ if (attachment.mContentId != null) {
+ writeHeader(writer, "Content-ID", attachment.mContentId);
+ }
writer.append("\r\n");
// Set up input stream and write it out via base64
@@ -335,7 +393,9 @@ public class Rfc822Output {
}
/**
- * Write text (either as main body or inside a multipart), preceded by appropriate headers.
+ * Write the body text. If only one version of the body is specified (either plain text
+ * or HTML), the text is written directly. Otherwise, the plain text and HTML bodies
+ * are both written with the appropriate headers.
*
* Note this always uses base64, even when not required. Slightly less efficient for
* US-ASCII text, but handles all formats even when non-ascii chars are involved. A small
@@ -343,15 +403,53 @@ public class Rfc822Output {
*
* @param writer the output writer
* @param out the output stream inside the writer (used for byte[] access)
- * @param text The original text of the message
+ * @param bodyText Plain text and HTML versions of the original text of the message
*/
- private static void writeTextWithHeaders(Writer writer, OutputStream out, String text)
+ private static void writeTextWithHeaders(Writer writer, OutputStream out, String[] bodyText)
throws IOException {
- writeHeader(writer, "Content-Type", "text/plain; charset=utf-8");
- writeHeader(writer, "Content-Transfer-Encoding", "base64");
- writer.write("\r\n");
- byte[] bytes = text.getBytes("UTF-8");
- writer.flush();
- out.write(Base64.encode(bytes, Base64.CRLF));
+ String text = bodyText[TEXT_BODY_IDX];
+ String html = bodyText[HTML_BODY_IDX];
+
+ if (text == null) {
+ writer.write("\r\n"); // a truly empty message
+ } else {
+ String multipartBoundary = null;
+ boolean multipart = html != null;
+
+ // Simplified case for no multipart - just emit text and be done.
+ if (multipart) {
+ // continue with multipart headers, then into multipart body
+ multipartBoundary = "--_com.android.email_" + System.nanoTime();
+
+ writeHeader(writer, "Content-Type",
+ "multipart/alternative; boundary=\"" + multipartBoundary + "\"");
+ // Finish headers and prepare for body section(s)
+ writer.write("\r\n");
+ writeBoundary(writer, multipartBoundary, false);
+ }
+
+ // first multipart element is the body
+ writeHeader(writer, "Content-Type", "text/plain; charset=utf-8");
+ writeHeader(writer, "Content-Transfer-Encoding", "base64");
+ writer.write("\r\n");
+ byte[] textBytes = text.getBytes("UTF-8");
+ writer.flush();
+ out.write(Base64.encode(textBytes, Base64.CRLF));
+
+ if (multipart) {
+ // next multipart section
+ writeBoundary(writer, multipartBoundary, false);
+
+ writeHeader(writer, "Content-Type", "text/html; charset=utf-8");
+ writeHeader(writer, "Content-Transfer-Encoding", "base64");
+ writer.write("\r\n");
+ byte[] htmlBytes = html.getBytes("UTF-8");
+ writer.flush();
+ out.write(Base64.encode(htmlBytes, Base64.CRLF));
+
+ // end of multipart section
+ writeBoundary(writer, multipartBoundary, true);
+ }
+ }
}
}
diff --git a/proguard.flags b/proguard.flags
index f6355d7c0..494c26c46 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -46,6 +46,11 @@
*** setProviderContext(android.content.Context);
}
+-keepclasseswithmembers class com.android.emailcommon.internet.Rfc822Output {
+ *** getHtmlBody(java.lang.String);
+ *** buildBodyText(android.content.Context, com.android.emailcommon.provider.EmailContent$Message, boolean);
+}
+
-keepclasseswithmembers class com.android.emailcommon.mail.Address {
(java.lang.String);
(java.lang.String,java.lang.String);
diff --git a/src/com/android/email/activity/MessageCompose.java b/src/com/android/email/activity/MessageCompose.java
index 2ca634c80..4b279a182 100644
--- a/src/com/android/email/activity/MessageCompose.java
+++ b/src/com/android/email/activity/MessageCompose.java
@@ -141,7 +141,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
*/
private boolean mSourceMessageProcessed = false;
- private ActionBar mActionBar;
private TextView mFromView;
private MultiAutoCompleteTextView mToView;
private MultiAutoCompleteTextView mCcView;
@@ -472,7 +471,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
}
private void initViews() {
- mActionBar = getActionBar();
mFromView = (TextView)findViewById(R.id.from);
mToView = (MultiAutoCompleteTextView)findViewById(R.id.to);
mCcView = (MultiAutoCompleteTextView)findViewById(R.id.cc);
@@ -481,7 +479,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
mSubjectView = (EditText)findViewById(R.id.subject);
mMessageContentView = (EditText)findViewById(R.id.message_content);
mAttachments = (LinearLayout)findViewById(R.id.attachments);
- mAttachmentContainer = (LinearLayout)findViewById(R.id.attachment_container);
+ mAttachmentContainer = findViewById(R.id.attachment_container);
mQuotedTextBar = findViewById(R.id.quoted_text_bar);
mIncludeQuotedTextCheckBox = (CheckBox) findViewById(R.id.include_quoted_text);
mQuotedText = (WebView)findViewById(R.id.quoted_text);
@@ -814,6 +812,8 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
}
/**
+ * Updates the given message using values from the compose UI.
+ *
* @param message The message to be updated.
* @param account the account (used to obtain From: address).
* @param hasAttachments true if it has one or more attachment.
@@ -840,14 +840,11 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
// Use the Intent to set flags saying this message is a reply or a forward and save the
// unique id of the source message
if (mSource != null && mQuotedTextBar.getVisibility() == View.VISIBLE) {
- if (ACTION_REPLY.equals(mAction) || ACTION_REPLY_ALL.equals(mAction)
- || ACTION_FORWARD.equals(mAction)) {
- message.mSourceKey = mSource.mId;
- // Get the body of the source message here
- message.mHtmlReply = mSource.mHtml;
- message.mTextReply = mSource.mText;
- }
-
+ // If the quote bar is visible; this must either be a reply or forward
+ message.mSourceKey = mSource.mId;
+ // Get the body of the source message here
+ message.mHtmlReply = mSource.mHtml;
+ message.mTextReply = mSource.mText;
String fromAsString = Address.unpackToString(mSource.mFrom);
if (ACTION_FORWARD.equals(mAction)) {
message.mFlags |= Message.FLAG_TYPE_FORWARD;
@@ -949,7 +946,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
// For any unloaded attachment, set the flag saying we need it loaded
boolean hasUnloadedAttachments = false;
for (Attachment attachment : attachments) {
-
if (attachment.mContentUri == null &&
((attachment.mFlags & Attachment.FLAG_SMART_FORWARD) != 0)) {
attachment.mFlags |= Attachment.FLAG_DOWNLOAD_FORWARD;
@@ -1481,13 +1477,12 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus
return URLDecoder.decode(s, "UTF-8");
}
- // used by processSourceMessage()
+ /**
+ * Displays quoted text from the original email
+ */
private void displayQuotedText(String textBody, String htmlBody) {
- /* Use plain-text body if available, otherwise use HTML body.
- * This matches the desired behavior for IMAP/POP where we only send plain-text,
- * and for EAS which sends HTML and has no plain-text body.
- */
- boolean plainTextFlag = textBody != null;
+ // Only use plain text if there is no HTML body
+ boolean plainTextFlag = htmlBody == null;
String text = plainTextFlag ? textBody : htmlBody;
if (text != null) {
text = plainTextFlag ? EmailHtmlUtil.escapeCharacterToDisplay(text) : text;
diff --git a/tests/src/com/android/emailcommon/internet/Rfc822OutputTests.java b/tests/src/com/android/emailcommon/internet/Rfc822OutputTests.java
index 1d9253f95..dc5999292 100644
--- a/tests/src/com/android/emailcommon/internet/Rfc822OutputTests.java
+++ b/tests/src/com/android/emailcommon/internet/Rfc822OutputTests.java
@@ -269,6 +269,25 @@ public class Rfc822OutputTests extends ProviderTestCase2 {
assertNotNull(header.getField("content-disposition"));
}
+ private static String BODY_TEST1 = "MyTitle"
+ + "test1";
+ private static String BODY_RESULT1 = "test1";
+ private static String BODY_TEST2 = "test2
test2";
+ private static String BODY_RESULT2 = "test2
test2";
+ private static String BODY_TEST3 = "test3";
+ private static String BODY_RESULT3 = "test3";
+
+ public void testGetHtmlBody() {
+ String actual;
+
+ actual = Rfc822Output.getHtmlBody(BODY_TEST1);
+ assertEquals(BODY_RESULT1, actual);
+ actual = Rfc822Output.getHtmlBody(BODY_TEST2);
+ assertEquals(BODY_RESULT2, actual);
+ actual = Rfc822Output.getHtmlBody(BODY_TEST3);
+ assertEquals(BODY_RESULT3, actual);
+ }
+
/**
* Confirm that the constructed message includes "MIME-VERSION: 1.0"
*/