From f2dded3a2fba83dd3f0d14cce6abe467a4ab66eb Mon Sep 17 00:00:00 2001 From: Marc Blank Date: Sat, 12 Sep 2009 14:45:08 -0700 Subject: [PATCH] Reimplement reply/forward to use mTextReply/mHtmlReply * Move creation of final reply/forward text (i.e. new text plus the original) to Rfc822Output, where it belongs. * Prepares the way for use of SmartForward/SmartReply in Exchange and replying w/ multipart/alternative in SMTP * Moved test from MessageCompose to new Rfc822OutputTests, and note that new tests should be added (this is not a regression; there were never enough tests here) Change-Id: Ibefb842f47cc9223714856d99b8d4f55b55f49e3 --- .../email/activity/MessageCompose.java | 69 +++++-------------- .../email/mail/transport/Rfc822Output.java | 39 ++++++++++- .../MessageComposeInstrumentationTests.java | 31 +-------- .../mail/transport/Rfc822OutputTests.java | 67 ++++++++++++++++++ 4 files changed, 122 insertions(+), 84 deletions(-) create mode 100644 tests/src/com/android/email/mail/transport/Rfc822OutputTests.java diff --git a/src/com/android/email/activity/MessageCompose.java b/src/com/android/email/activity/MessageCompose.java index 4ee46763c..8d46b844b 100644 --- a/src/com/android/email/activity/MessageCompose.java +++ b/src/com/android/email/activity/MessageCompose.java @@ -72,8 +72,6 @@ import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class MessageCompose extends Activity implements OnClickListener, OnFocusChangeListener { @@ -101,9 +99,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus private static final int ACTIVITY_REQUEST_PICK_ATTACHMENT = 1; - private static final Pattern PATTERN_START_OF_LINE = Pattern.compile("(?m)^"); - private static final Pattern PATTERN_ENDLINE_CRLF = Pattern.compile("\r\n"); - private static final String[] ATTACHMENT_META_COLUMNS = { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE @@ -572,45 +567,6 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus return addresses; } - /* - * Takes care to append source info and text in a REPLY or FORWARD situation. - */ - /* package */ String buildBodyText(Message sourceMessage) { - /* - * Build the Body that will contain the text of the message. We'll decide where to - * include it later. - */ - final String action = getIntent().getAction(); - String text = mMessageContentView.getText().toString(); - - if (mQuotedTextBar.getVisibility() == View.VISIBLE && sourceMessage != null) { - String quotedText = sourceMessage.mText; - 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"); - } - String fromAsString = Address.unpackToString(sourceMessage.mFrom); - if (ACTION_REPLY.equals(action) || ACTION_REPLY_ALL.equals(action)) { - text += getString(R.string.message_compose_reply_header_fmt, fromAsString); - if (quotedText != null) { - Matcher matcher = PATTERN_START_OF_LINE.matcher(quotedText); - text += matcher.replaceAll(">"); - } - } else if (ACTION_FORWARD.equals(action)) { - String subject = sourceMessage.mSubject; - String to = Address.unpackToString(sourceMessage.mTo); - String cc = Address.unpackToString(sourceMessage.mCc); - text += getString(R.string.message_compose_fwd_header_fmt, subject, fromAsString, - to != null ? to : "", cc != null ? cc : ""); - if (quotedText != null) { - text += quotedText; - } - } - } - return text; - } - private ContentValues getUpdateContentValues(Message message) { ContentValues values = new ContentValues(); values.put(MessageColumns.TIMESTAMP, message.mTimeStamp); @@ -656,15 +612,14 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus * @param account the account (used to obtain From: address). * @param bodyText the body text. */ - private void updateMessage(Message message, Account account, String bodyText, - boolean hasAttachments) { + private void updateMessage(Message message, Account account, boolean hasAttachments) { message.mTimeStamp = System.currentTimeMillis(); message.mFrom = new Address(account.getEmailAddress(), account.getSenderName()).pack(); message.mTo = getPackedAddresses(mToView); message.mCc = getPackedAddresses(mCcView); message.mBcc = getPackedAddresses(mBccView); message.mSubject = mSubjectView.getText().toString(); - message.mText = bodyText; + message.mText = mMessageContentView.getText().toString(); message.mAccountKey = account.mId; message.mDisplayName = makeDisplayName(message.mTo, message.mCc, message.mBcc); message.mFlagLoaded = Message.FLAG_LOADED_COMPLETE; @@ -672,13 +627,19 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus String action = getIntent().getAction(); // 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) { - if (ACTION_REPLY.equals(action) || ACTION_REPLY_ALL.equals(action)) { - message.mFlags |= Message.FLAG_TYPE_REPLY; + if (mSource != null && mQuotedTextBar.getVisibility() == View.VISIBLE) { + if (ACTION_REPLY.equals(action) || ACTION_REPLY_ALL.equals(action) + || ACTION_FORWARD.equals(action)) { message.mSourceKey = mSource.mId; - } else if (ACTION_FORWARD.equals(action)) { + // Get the body of the source message here + // Note that the following commented line will be useful when we use HTML in replies + //message.mHtmlReply = mSource.mHtml; + message.mTextReply = mSource.mText; + } + if (ACTION_FORWARD.equals(action)) { message.mFlags |= Message.FLAG_TYPE_FORWARD; - message.mSourceKey = mSource.mId; + } else { + message.mFlags |= Message.FLAG_TYPE_REPLY; } } } @@ -704,7 +665,7 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus mDraft = new Message(); } final Attachment[] attachments = getAttachmentsFromUI(); - updateMessage(mDraft, mAccount, buildBodyText(mSource), attachments.length > 0); + updateMessage(mDraft, mAccount, attachments.length > 0); mSaveMessageTask = new AsyncTask() { @Override @@ -714,6 +675,8 @@ public class MessageCompose extends Activity implements OnClickListener, OnFocus mDraft.update(MessageCompose.this, getUpdateContentValues(mDraft)); ContentValues values = new ContentValues(); values.put(BodyColumns.TEXT_CONTENT, mDraft.mText); + values.put(BodyColumns.TEXT_REPLY, mDraft.mTextReply); + values.put(BodyColumns.HTML_REPLY, mDraft.mHtmlReply); Body.updateBodyWithMessageId(MessageCompose.this, mDraft.mId, values); } else { // mDraft.mId is set upon return of saveToMailbox() diff --git a/src/com/android/email/mail/transport/Rfc822Output.java b/src/com/android/email/mail/transport/Rfc822Output.java index b85fbb109..6f0616441 100644 --- a/src/com/android/email/mail/transport/Rfc822Output.java +++ b/src/com/android/email/mail/transport/Rfc822Output.java @@ -16,6 +16,7 @@ package com.android.email.mail.transport; +import com.android.email.R; import com.android.email.codec.binary.Base64; import com.android.email.codec.binary.Base64OutputStream; import com.android.email.mail.Address; @@ -42,17 +43,53 @@ import java.io.Writer; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * Utility class to output RFC 822 messages from provider email messages */ public class Rfc822Output { + private static final Pattern PATTERN_START_OF_LINE = Pattern.compile("(?m)^"); + private static final Pattern PATTERN_ENDLINE_CRLF = Pattern.compile("\r\n"); + // In MIME, en_US-like date format should be used. In other words "MMM" should be encoded to // "Jan", not the other localized format like "Ene" (meaning January in locale es). static final SimpleDateFormat mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US); + /*package*/ static String buildBodyText(Context context, Message message) { + int flags = message.mFlags; + Body body = Body.restoreBodyWithMessageId(context, message.mId); + String text = body.mTextContent; + + 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"); + } + String fromAsString = Address.unpackToString(message.mFrom); + if ((flags & Message.FLAG_TYPE_REPLY) != 0) { + text += context.getString(R.string.message_compose_reply_header_fmt, fromAsString); + if (quotedText != null) { + Matcher matcher = PATTERN_START_OF_LINE.matcher(quotedText); + text += matcher.replaceAll(">"); + } + } else if ((flags & Message.FLAG_TYPE_FORWARD) != 0) { + String subject = message.mSubject; + String to = Address.unpackToString(message.mTo); + String cc = Address.unpackToString(message.mCc); + text += context.getString(R.string.message_compose_fwd_header_fmt, subject, + fromAsString, to != null ? to : "", cc != null ? cc : ""); + if (quotedText != null) { + text += quotedText; + } + } + return text; + } + /** * Write the entire message to an output stream. This method provides buffering, so it is * not necessary to pass in a buffered output stream here. @@ -92,7 +129,7 @@ public class Rfc822Output { writeAddressHeader(writer, "Reply-To", message.mReplyTo); // Analyze message and determine if we have multiparts - String text = Body.restoreBodyTextWithMessageId(context, messageId); + String text = buildBodyText(context, message); Uri uri = ContentUris.withAppendedId(Attachment.MESSAGE_ID_URI, messageId); Cursor attachmentsCursor = context.getContentResolver().query(uri, diff --git a/tests/src/com/android/email/activity/MessageComposeInstrumentationTests.java b/tests/src/com/android/email/activity/MessageComposeInstrumentationTests.java index 6ac80cfe4..7e97cfb50 100644 --- a/tests/src/com/android/email/activity/MessageComposeInstrumentationTests.java +++ b/tests/src/com/android/email/activity/MessageComposeInstrumentationTests.java @@ -164,36 +164,7 @@ public class MessageComposeInstrumentationTests assertEquals(0, mMessageView.length()); } - /** - * Test for buildBodyText(). - * Compare with expected values. - * Also test the situation where the message has no body. - */ - public void testBuildBodyText() throws MessagingException, Throwable { - final Message message = buildTestMessage(RECIPIENT_TO, SENDER, SUBJECT, BODY); - Intent intent = new Intent(ACTION_REPLY); - final MessageCompose a = getActivity(); - a.setIntent(intent); - - runTestOnUiThread(new Runnable() { - public void run() { - a.processSourceMessage(message, null); - String body = a.buildBodyText(message); - assertEquals(REPLY_BODY, body); - } - }); - - message.mText = null; - runTestOnUiThread(new Runnable() { - public void run() { - a.processSourceMessage(message, null); - String body = a.buildBodyText(message); - assertEquals(REPLY_BODY_SHORT, body); - } - }); - } - - /** + /** * Test a couple of variations of processSourceMessage() for REPLY * To = Reply-To or From: (if REPLY) * To = (Reply-To or From:) + To: + Cc: (if REPLY_ALL) diff --git a/tests/src/com/android/email/mail/transport/Rfc822OutputTests.java b/tests/src/com/android/email/mail/transport/Rfc822OutputTests.java new file mode 100644 index 000000000..3512855be --- /dev/null +++ b/tests/src/com/android/email/mail/transport/Rfc822OutputTests.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.email.mail.transport; + +import com.android.email.provider.EmailContent.Message; + +import android.test.AndroidTestCase; + +/** + * Tests of the Rfc822Output (used for sending mail) + * + * You can run this entire test case with: + * runtest -c com.android.email.mail.transport.Rfc822OutputTests email + */ +public class Rfc822OutputTests extends AndroidTestCase { + private static final String SENDER = "sender@android.com"; + private static final String REPLYTO = "replyto@android.com"; + private static final String RECIPIENT_TO = "recipient-to@android.com"; + private static final String RECIPIENT_CC = "recipient-cc@android.com"; + private static final String RECIPIENT_BCC = "recipient-bcc@android.com"; + private static final String SUBJECT = "This is the subject"; + private static final String BODY = "This is the body. This is also the body."; + private static final String REPLY_BODY_SHORT = "\n\n" + SENDER + " wrote:\n\n"; + private static final String REPLY_BODY = REPLY_BODY_SHORT + ">" + BODY; + + // TODO Create more tests here. Specifically, we should test to make sure that forward works + // properly instead of just reply + + /** + * Test for buildBodyText(). + * Compare with expected values. + * Also test the situation where the message has no body. + */ + public void testBuildBodyText() { + // Create the least necessary; sender, flags, and the body of the reply + Message msg = new Message(); + msg.mText = ""; + msg.mFrom = SENDER; + msg.mFlags = Message.FLAG_TYPE_REPLY; + msg.mTextReply = BODY; + msg.save(getContext()); + + String body = Rfc822Output.buildBodyText(getContext(), msg); + assertEquals(REPLY_BODY, body); + + // Save a different message with no reply body (so we reset the id) + msg.mId = -1; + msg.mTextReply = null; + msg.save(getContext()); + body = Rfc822Output.buildBodyText(getContext(), msg); + assertEquals(REPLY_BODY_SHORT, body); + } + }