1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-27 11:42:16 -05:00

Merge pull request #468

Extract a TextBodyBuilder class

Conflicts:
	src/com/fsck/k9/activity/MessageCompose.java
This commit is contained in:
cketti 2014-05-04 03:54:10 +02:00
commit 95f33c38fe
6 changed files with 910 additions and 424 deletions

View File

@ -50,3 +50,8 @@ android {
exclude 'META-INF/NOTICE' exclude 'META-INF/NOTICE'
} }
} }
task testsOnJVM(type :GradleBuild, dependsOn: assemble) {
buildFile = 'tests-on-jvm/build.gradle'
tasks = ['test']
}

View File

@ -12,7 +12,7 @@ import java.io.Serializable;
* *
* TODO: This container should also have a text part, along with its insertion point. Or maybe a generic InsertableContent and maintain one each for Html and Text? * TODO: This container should also have a text part, along with its insertion point. Or maybe a generic InsertableContent and maintain one each for Html and Text?
*/ */
class InsertableHtmlContent implements Serializable { public class InsertableHtmlContent implements Serializable {
private static final long serialVersionUID = 2397327034L; private static final long serialVersionUID = 2397327034L;
// Default to a headerInsertionPoint at the beginning of the message. // Default to a headerInsertionPoint at the beginning of the message.
private int headerInsertionPoint = 0; private int headerInsertionPoint = 0;

View File

@ -88,6 +88,7 @@ import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeBodyPart; import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.TextBodyBuilder;
import com.fsck.k9.mail.internet.MimeMultipart; import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody; import com.fsck.k9.mail.internet.TextBody;
@ -111,6 +112,7 @@ import java.io.ByteArrayOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.text.DateFormat; import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -381,36 +383,36 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override @Override
public void handleMessage(android.os.Message msg) { public void handleMessage(android.os.Message msg) {
switch (msg.what) { switch (msg.what) {
case MSG_PROGRESS_ON: case MSG_PROGRESS_ON:
setSupportProgressBarIndeterminateVisibility(true); setSupportProgressBarIndeterminateVisibility(true);
break; break;
case MSG_PROGRESS_OFF: case MSG_PROGRESS_OFF:
setSupportProgressBarIndeterminateVisibility(false); setSupportProgressBarIndeterminateVisibility(false);
break; break;
case MSG_SKIPPED_ATTACHMENTS: case MSG_SKIPPED_ATTACHMENTS:
Toast.makeText( Toast.makeText(
MessageCompose.this, MessageCompose.this,
getString(R.string.message_compose_attachments_skipped_toast), getString(R.string.message_compose_attachments_skipped_toast),
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
break; break;
case MSG_SAVED_DRAFT: case MSG_SAVED_DRAFT:
Toast.makeText( Toast.makeText(
MessageCompose.this, MessageCompose.this,
getString(R.string.message_saved_toast), getString(R.string.message_saved_toast),
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
break; break;
case MSG_DISCARDED_DRAFT: case MSG_DISCARDED_DRAFT:
Toast.makeText( Toast.makeText(
MessageCompose.this, MessageCompose.this,
getString(R.string.message_discarded_toast), getString(R.string.message_discarded_toast),
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
break; break;
case MSG_PERFORM_STALLED_ACTION: case MSG_PERFORM_STALLED_ACTION:
performStalledAction(); performStalledAction();
break; break;
default: default:
super.handleMessage(msg); super.handleMessage(msg);
break; break;
} }
} }
}; };
@ -560,8 +562,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
mMessageReference = intent.getParcelableExtra(EXTRA_MESSAGE_REFERENCE); mMessageReference = intent.getParcelableExtra(EXTRA_MESSAGE_REFERENCE);
mSourceMessageBody = intent.getStringExtra(EXTRA_MESSAGE_BODY); mSourceMessageBody = intent.getStringExtra(EXTRA_MESSAGE_BODY);
if (K9.DEBUG && mSourceMessageBody != null) if (K9.DEBUG && mSourceMessageBody != null) {
Log.d(K9.LOG_TAG, "Composing message with explicitly specified message body."); Log.d(K9.LOG_TAG, "Composing message with explicitly specified message body.");
}
final String accountUuid = (mMessageReference != null) ? final String accountUuid = (mMessageReference != null) ?
mMessageReference.accountUuid : mMessageReference.accountUuid :
@ -1350,136 +1353,56 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* original message. * original message.
*/ */
private TextBody buildText(boolean isDraft, SimpleMessageFormat messageFormat) { private TextBody buildText(boolean isDraft, SimpleMessageFormat messageFormat) {
// The length of the formatted version of the user-supplied text/reply String messageText = mMessageContentView.getCharacters();
int composedMessageLength;
// The offset of the user-supplied text/reply in the final text body TextBodyBuilder textBodyBuilder = new TextBodyBuilder(messageText);
int composedMessageOffset;
/* /*
* Find out if we need to include the original message as quoted text. * Find out if we need to include the original message as quoted text.
* *
* We include the quoted text in the body if the user didn't choose to hide it. We always * We include the quoted text in the body if the user didn't choose to
* include the quoted text when we're saving a draft. That's so the user is able to * hide it. We always include the quoted text when we're saving a draft.
* "un-hide" the quoted text if (s)he opens a saved draft. * That's so the user is able to "un-hide" the quoted text if (s)he
* opens a saved draft.
*/ */
boolean includeQuotedText = (mQuotedTextMode.equals(QuotedTextMode.SHOW) || isDraft); boolean includeQuotedText = (isDraft || mQuotedTextMode == QuotedTextMode.SHOW);
boolean isReplyAfterQuote = (mQuoteStyle == QuoteStyle.PREFIX && mAccount.isReplyAfterQuote());
// Reply after quote makes no sense for HEADER style replies textBodyBuilder.setIncludeQuotedText(false);
boolean replyAfterQuote = (mQuoteStyle == QuoteStyle.HEADER) ? if (includeQuotedText) {
false : mAccount.isReplyAfterQuote(); if (messageFormat == SimpleMessageFormat.HTML && mQuotedHtmlContent != null) {
textBodyBuilder.setIncludeQuotedText(true);
boolean signatureBeforeQuotedText = mAccount.isSignatureBeforeQuotedText(); textBodyBuilder.setQuotedTextHtml(mQuotedHtmlContent);
textBodyBuilder.setReplyAfterQuote(isReplyAfterQuote);
// Get the user-supplied text
String text = mMessageContentView.getCharacters();
// Handle HTML separate from the rest of the text content
if (messageFormat == SimpleMessageFormat.HTML) {
// Do we have to modify an existing message to include our reply?
if (includeQuotedText && mQuotedHtmlContent != null) {
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "insertable: " + mQuotedHtmlContent.toDebugString());
}
if (!isDraft) {
// Append signature to the reply
if (replyAfterQuote || signatureBeforeQuotedText) {
text = appendSignature(text);
}
}
// Convert the text to HTML
text = HtmlConverter.textToHtmlFragment(text);
/*
* Set the insertion location based upon our reply after quote setting.
* Additionally, add some extra separators between the composed message and quoted
* message depending on the quote location. We only add the extra separators when
* we're sending, that way when we load a draft, we don't have to know the length
* of the separators to remove them before editing.
*/
if (replyAfterQuote) {
mQuotedHtmlContent.setInsertionLocation(
InsertableHtmlContent.InsertionLocation.AFTER_QUOTE);
if (!isDraft) {
text = "<br clear=\"all\">" + text;
}
} else {
mQuotedHtmlContent.setInsertionLocation(
InsertableHtmlContent.InsertionLocation.BEFORE_QUOTE);
if (!isDraft) {
text += "<br><br>";
}
}
if (!isDraft) {
// Place signature immediately after the quoted text
if (!(replyAfterQuote || signatureBeforeQuotedText)) {
mQuotedHtmlContent.insertIntoQuotedFooter(getSignatureHtml());
}
}
mQuotedHtmlContent.setUserContent(text);
// Save length of the body and its offset. This is used when thawing drafts.
composedMessageLength = text.length();
composedMessageOffset = mQuotedHtmlContent.getInsertionPoint();
text = mQuotedHtmlContent.toString();
} else {
// There is no text to quote so simply append the signature if available
if (!isDraft) {
text = appendSignature(text);
}
// Convert the text to HTML
text = HtmlConverter.textToHtmlFragment(text);
//TODO: Wrap this in proper HTML tags
composedMessageLength = text.length();
composedMessageOffset = 0;
}
} else {
// Capture composed message length before we start attaching quoted parts and signatures.
composedMessageLength = text.length();
composedMessageOffset = 0;
if (!isDraft) {
// Append signature to the text/reply
if (replyAfterQuote || signatureBeforeQuotedText) {
text = appendSignature(text);
}
} }
String quotedText = mQuotedText.getCharacters(); String quotedText = mQuotedText.getCharacters();
if (includeQuotedText && quotedText.length() > 0) { if (messageFormat == SimpleMessageFormat.TEXT && quotedText.length() > 0) {
if (replyAfterQuote) { textBodyBuilder.setIncludeQuotedText(true);
composedMessageOffset = quotedText.length() + "\r\n".length(); textBodyBuilder.setQuotedText(quotedText);
text = quotedText + "\r\n" + text; textBodyBuilder.setReplyAfterQuote(isReplyAfterQuote);
} else {
text += "\r\n\r\n" + quotedText.toString();
}
}
if (!isDraft) {
// Place signature immediately after the quoted text
if (!(replyAfterQuote || signatureBeforeQuotedText)) {
text = appendSignature(text);
}
} }
} }
TextBody body = new TextBody(text); textBodyBuilder.setInsertSeparator(!isDraft);
body.setComposedMessageLength(composedMessageLength);
body.setComposedMessageOffset(composedMessageOffset);
boolean useSignature = (!isDraft && mIdentity.getSignatureUse());
if (useSignature) {
textBodyBuilder.setAppendSignature(true);
textBodyBuilder.setSignature(mSignatureView.getCharacters());
textBodyBuilder.setSignatureBeforeQuotedText(mAccount.isSignatureBeforeQuotedText());
} else {
textBodyBuilder.setAppendSignature(false);
}
TextBody body;
if (messageFormat == SimpleMessageFormat.HTML) {
body = textBodyBuilder.buildTextHtml();
} else {
body = textBodyBuilder.buildTextPlain();
}
return body; return body;
} }
/** /**
* Build the final message to be sent (or saved). If there is another message quoted in this one, it will be baked * Build the final message to be sent (or saved). If there is another message quoted in this one, it will be baked
* into the final message here. * into the final message here.
@ -1628,8 +1551,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* title*3="isn't it!" * title*3="isn't it!"
*/ */
bp.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, String.format(Locale.US, bp.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, String.format(Locale.US,
"attachment;\r\n filename=\"%s\";\r\n size=%d", "attachment;\r\n filename=\"%s\";\r\n size=%d",
attachment.name, attachment.size)); attachment.name, attachment.size));
mp.addBodyPart(bp); mp.addBodyPart(bp);
} }
@ -1762,8 +1685,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private Map<IdentityField, String> parseIdentityHeader(final String identityString) { private Map<IdentityField, String> parseIdentityHeader(final String identityString) {
Map<IdentityField, String> identity = new HashMap<IdentityField, String>(); Map<IdentityField, String> identity = new HashMap<IdentityField, String>();
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Decoding identity: " + identityString); Log.d(K9.LOG_TAG, "Decoding identity: " + identityString);
}
if (identityString == null || identityString.length() < 1) { if (identityString == null || identityString.length() < 1) {
return identity; return identity;
@ -1781,8 +1705,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
} }
} }
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Decoded identity: " + identity.toString()); Log.d(K9.LOG_TAG, "Decoded identity: " + identity.toString());
}
// Sanity check our Integers so that recipients of this result don't have to. // Sanity check our Integers so that recipients of this result don't have to.
for (IdentityField key : IdentityField.getIntegerFields()) { for (IdentityField key : IdentityField.getIntegerFields()) {
@ -1797,8 +1722,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
} else { } else {
// Legacy identity // Legacy identity
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Got a saved legacy identity: " + identityString); Log.d(K9.LOG_TAG, "Got a saved legacy identity: " + identityString);
}
StringTokenizer tokenizer = new StringTokenizer(identityString, ":", false); StringTokenizer tokenizer = new StringTokenizer(identityString, ":", false);
// First item is the body length. We use this to separate the composed reply from the quoted text. // First item is the body length. We use this to separate the composed reply from the quoted text.
@ -1827,35 +1753,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
return identity; return identity;
} }
private String appendSignature(String originalText) {
String text = originalText;
if (mIdentity.getSignatureUse()) {
String signature = mSignatureView.getCharacters();
if (signature != null && !signature.contentEquals("")) {
text += "\r\n" + signature;
}
}
return text;
}
/**
* Get an HTML version of the signature in the #mSignatureView, if any.
* @return HTML version of signature.
*/
private String getSignatureHtml() {
String signature = "";
if (mIdentity.getSignatureUse()) {
signature = mSignatureView.getCharacters();
if(!StringUtils.isNullOrEmpty(signature)) {
signature = HtmlConverter.textToHtmlFragment("\r\n" + signature);
}
}
return signature;
}
private void sendMessage() { private void sendMessage() {
new SendMessageTask().execute(); new SendMessageTask().execute();
} }
@ -1929,18 +1826,18 @@ public class MessageCompose extends K9Activity implements OnClickListener,
} }
emailsArray = emails.toArray(new String[emails.size()]); emailsArray = emails.toArray(new String[emails.size()]);
} }
if (mEncryptCheckbox.isChecked() && mCryptoSignatureCheckbox.isChecked()) { if (mEncryptCheckbox.isChecked() && mCryptoSignatureCheckbox.isChecked()) {
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT); Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, emailsArray); intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, emailsArray);
executeOpenPgpMethod(intent); executeOpenPgpMethod(intent);
} else if (mCryptoSignatureCheckbox.isChecked()) { } else if (mCryptoSignatureCheckbox.isChecked()) {
Intent intent = new Intent(OpenPgpApi.ACTION_SIGN); Intent intent = new Intent(OpenPgpApi.ACTION_SIGN);
executeOpenPgpMethod(intent); executeOpenPgpMethod(intent);
} else if (mEncryptCheckbox.isChecked()) { } else if (mEncryptCheckbox.isChecked()) {
Intent intent = new Intent(OpenPgpApi.ACTION_ENCRYPT); Intent intent = new Intent(OpenPgpApi.ACTION_ENCRYPT);
intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, emailsArray); intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, emailsArray);
executeOpenPgpMethod(intent); executeOpenPgpMethod(intent);
} }
// onSend() is called again in SignEncryptCallback and with // onSend() is called again in SignEncryptCallback and with
// encryptedData set in pgpData! // encryptedData set in pgpData!
@ -1988,8 +1885,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
sendMessage(); sendMessage();
if (mMessageReference != null && mMessageReference.flag != null) { if (mMessageReference != null && mMessageReference.flag != null) {
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Setting referenced message (" + mMessageReference.folderName + ", " + mMessageReference.uid + ") flag to " + mMessageReference.flag); Log.d(K9.LOG_TAG, "Setting referenced message (" + mMessageReference.folderName + ", " + mMessageReference.uid + ") flag to " + mMessageReference.flag);
}
final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid); final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
final String folderName = mMessageReference.folderName; final String folderName = mMessageReference.folderName;
@ -2049,8 +1947,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
final String output = os.toString("UTF-8"); final String output = os.toString("UTF-8");
if (K9.DEBUG) if (K9.DEBUG)
Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length +
+ " str=" + output); " str=" + output);
mPgpData.setEncryptedData(output); mPgpData.setEncryptedData(output);
onSend(); onSend();
@ -2382,71 +2280,72 @@ public class MessageCompose extends K9Activity implements OnClickListener,
return; return;
} }
if (resultCode != RESULT_OK) if (resultCode != RESULT_OK) {
return; return;
}
if (data == null) { if (data == null) {
return; return;
} }
switch (requestCode) { switch (requestCode) {
case ACTIVITY_REQUEST_PICK_ATTACHMENT: case ACTIVITY_REQUEST_PICK_ATTACHMENT:
addAttachmentsFromResultIntent(data); addAttachmentsFromResultIntent(data);
mDraftNeedsSaving = true; mDraftNeedsSaving = true;
break; break;
case CONTACT_PICKER_TO: case CONTACT_PICKER_TO:
case CONTACT_PICKER_CC: case CONTACT_PICKER_CC:
case CONTACT_PICKER_BCC: case CONTACT_PICKER_BCC:
ContactItem contact = mContacts.extractInfoFromContactPickerIntent(data); ContactItem contact = mContacts.extractInfoFromContactPickerIntent(data);
if (contact == null) { if (contact == null) {
Toast.makeText(this, getString(R.string.error_contact_address_not_found), Toast.LENGTH_LONG).show(); Toast.makeText(this, getString(R.string.error_contact_address_not_found), Toast.LENGTH_LONG).show();
return; return;
} }
if (contact.emailAddresses.size() > 1) { if (contact.emailAddresses.size() > 1) {
Intent i = new Intent(this, EmailAddressList.class); Intent i = new Intent(this, EmailAddressList.class);
i.putExtra(EmailAddressList.EXTRA_CONTACT_ITEM, contact); i.putExtra(EmailAddressList.EXTRA_CONTACT_ITEM, contact);
if (requestCode == CONTACT_PICKER_TO) {
startActivityForResult(i, CONTACT_PICKER_TO2);
} else if (requestCode == CONTACT_PICKER_CC) {
startActivityForResult(i, CONTACT_PICKER_CC2);
} else if (requestCode == CONTACT_PICKER_BCC) {
startActivityForResult(i, CONTACT_PICKER_BCC2);
}
return;
}
if (K9.DEBUG) {
List<String> emails = contact.emailAddresses;
for (int i = 0; i < emails.size(); i++) {
Log.v(K9.LOG_TAG, "email[" + i + "]: " + emails.get(i));
}
}
String email = contact.emailAddresses.get(0);
if (requestCode == CONTACT_PICKER_TO) { if (requestCode == CONTACT_PICKER_TO) {
startActivityForResult(i, CONTACT_PICKER_TO2); addAddress(mToView, new Address(email, ""));
} else if (requestCode == CONTACT_PICKER_CC) { } else if (requestCode == CONTACT_PICKER_CC) {
startActivityForResult(i, CONTACT_PICKER_CC2); addAddress(mCcView, new Address(email, ""));
} else if (requestCode == CONTACT_PICKER_BCC) { } else if (requestCode == CONTACT_PICKER_BCC) {
startActivityForResult(i, CONTACT_PICKER_BCC2); addAddress(mBccView, new Address(email, ""));
} else {
return;
} }
return;
}
if (K9.DEBUG) {
List<String> emails = contact.emailAddresses; break;
for (int i = 0; i < emails.size(); i++) { case CONTACT_PICKER_TO2:
Log.v(K9.LOG_TAG, "email[" + i + "]: " + emails.get(i)); case CONTACT_PICKER_CC2:
case CONTACT_PICKER_BCC2:
String emailAddr = data.getStringExtra(EmailAddressList.EXTRA_EMAIL_ADDRESS);
if (requestCode == CONTACT_PICKER_TO2) {
addAddress(mToView, new Address(emailAddr, ""));
} else if (requestCode == CONTACT_PICKER_CC2) {
addAddress(mCcView, new Address(emailAddr, ""));
} else if (requestCode == CONTACT_PICKER_BCC2) {
addAddress(mBccView, new Address(emailAddr, ""));
} }
} break;
String email = contact.emailAddresses.get(0);
if (requestCode == CONTACT_PICKER_TO) {
addAddress(mToView, new Address(email, ""));
} else if (requestCode == CONTACT_PICKER_CC) {
addAddress(mCcView, new Address(email, ""));
} else if (requestCode == CONTACT_PICKER_BCC) {
addAddress(mBccView, new Address(email, ""));
} else {
return;
}
break;
case CONTACT_PICKER_TO2:
case CONTACT_PICKER_CC2:
case CONTACT_PICKER_BCC2:
String emailAddr = data.getStringExtra(EmailAddressList.EXTRA_EMAIL_ADDRESS);
if (requestCode == CONTACT_PICKER_TO2) {
addAddress(mToView, new Address(emailAddr, ""));
} else if (requestCode == CONTACT_PICKER_CC2) {
addAddress(mCcView, new Address(emailAddr, ""));
} else if (requestCode == CONTACT_PICKER_BCC2) {
addAddress(mBccView, new Address(emailAddr, ""));
}
break;
} }
} }
@ -2563,39 +2462,39 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override @Override
public void onClick(View view) { public void onClick(View view) {
switch (view.getId()) { switch (view.getId()) {
case R.id.attachment_delete: case R.id.attachment_delete:
/* /*
* The view is the delete button, and we have previously set the tag of * The view is the delete button, and we have previously set the tag of
* the delete button to the view that owns it. We don't use parent because the * the delete button to the view that owns it. We don't use parent because the
* view is very complex and could change in the future. * view is very complex and could change in the future.
*/ */
mAttachments.removeView((View) view.getTag()); mAttachments.removeView((View) view.getTag());
mDraftNeedsSaving = true; mDraftNeedsSaving = true;
break; break;
case R.id.quoted_text_show: case R.id.quoted_text_show:
showOrHideQuotedText(QuotedTextMode.SHOW); showOrHideQuotedText(QuotedTextMode.SHOW);
updateMessageFormat(); updateMessageFormat();
mDraftNeedsSaving = true; mDraftNeedsSaving = true;
break; break;
case R.id.quoted_text_delete: case R.id.quoted_text_delete:
showOrHideQuotedText(QuotedTextMode.HIDE); showOrHideQuotedText(QuotedTextMode.HIDE);
updateMessageFormat(); updateMessageFormat();
mDraftNeedsSaving = true; mDraftNeedsSaving = true;
break; break;
case R.id.quoted_text_edit: case R.id.quoted_text_edit:
mForcePlainText = true; mForcePlainText = true;
if (mMessageReference != null) { // shouldn't happen... if (mMessageReference != null) { // shouldn't happen...
// TODO - Should we check if mSourceMessageBody is already present and bypass the MessagingController call? // TODO - Should we check if mSourceMessageBody is already present and bypass the MessagingController call?
MessagingController.getInstance(getApplication()).addListener(mListener); MessagingController.getInstance(getApplication()).addListener(mListener);
final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid); final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
final String folderName = mMessageReference.folderName; final String folderName = mMessageReference.folderName;
final String sourceMessageUid = mMessageReference.uid; final String sourceMessageUid = mMessageReference.uid;
MessagingController.getInstance(getApplication()).loadMessageForView(account, folderName, sourceMessageUid, null); MessagingController.getInstance(getApplication()).loadMessageForView(account, folderName, sourceMessageUid, null);
} }
break; break;
case R.id.identity: case R.id.identity:
showDialog(DIALOG_CHOOSE_IDENTITY); showDialog(DIALOG_CHOOSE_IDENTITY);
break; break;
} }
} }
@ -2642,36 +2541,36 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.send: case R.id.send:
mPgpData.setEncryptionKeys(null); mPgpData.setEncryptionKeys(null);
onSend(); onSend();
break; break;
case R.id.save: case R.id.save:
if (mEncryptCheckbox.isChecked()) { if (mEncryptCheckbox.isChecked()) {
showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED); showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
} else { } else {
onSave(); onSave();
} }
break; break;
case R.id.discard: case R.id.discard:
onDiscard(); onDiscard();
break; break;
case R.id.add_cc_bcc: case R.id.add_cc_bcc:
onAddCcBcc(); onAddCcBcc();
break; break;
case R.id.add_attachment: case R.id.add_attachment:
onAddAttachment(); onAddAttachment();
break; break;
case R.id.add_attachment_image: case R.id.add_attachment_image:
onAddAttachment2("image/*"); onAddAttachment2("image/*");
break; break;
case R.id.add_attachment_video: case R.id.add_attachment_video:
onAddAttachment2("video/*"); onAddAttachment2("video/*");
break; break;
case R.id.read_receipt: case R.id.read_receipt:
onReadReceipt(); onReadReceipt();
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
return true; return true;
} }
@ -2769,94 +2668,94 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override @Override
public Dialog onCreateDialog(int id) { public Dialog onCreateDialog(int id) {
switch (id) { switch (id) {
case DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE: case DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE:
return new AlertDialog.Builder(this) return new AlertDialog.Builder(this)
.setTitle(R.string.save_or_discard_draft_message_dlg_title) .setTitle(R.string.save_or_discard_draft_message_dlg_title)
.setMessage(R.string.save_or_discard_draft_message_instructions_fmt) .setMessage(R.string.save_or_discard_draft_message_instructions_fmt)
.setPositiveButton(R.string.save_draft_action, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.save_draft_action, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE); dismissDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
onSave(); onSave();
} }
}) })
.setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE); dismissDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
onDiscard(); onDiscard();
} }
}) })
.create(); .create();
case DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED: case DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED:
return new AlertDialog.Builder(this) return new AlertDialog.Builder(this)
.setTitle(R.string.refuse_to_save_draft_marked_encrypted_dlg_title) .setTitle(R.string.refuse_to_save_draft_marked_encrypted_dlg_title)
.setMessage(R.string.refuse_to_save_draft_marked_encrypted_instructions_fmt) .setMessage(R.string.refuse_to_save_draft_marked_encrypted_instructions_fmt)
.setNeutralButton(R.string.okay_action, new DialogInterface.OnClickListener() { .setNeutralButton(R.string.okay_action, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED); dismissDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
} }
}) })
.create(); .create();
case DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY: case DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY:
return new AlertDialog.Builder(this) return new AlertDialog.Builder(this)
.setTitle(R.string.continue_without_public_key_dlg_title) .setTitle(R.string.continue_without_public_key_dlg_title)
.setMessage(R.string.continue_without_public_key_instructions_fmt) .setMessage(R.string.continue_without_public_key_instructions_fmt)
.setPositiveButton(R.string.continue_action, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.continue_action, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY); dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
mContinueWithoutPublicKey = true; mContinueWithoutPublicKey = true;
onSend(); onSend();
} }
}) })
.setNegativeButton(R.string.back_action, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.back_action, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY); dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
mContinueWithoutPublicKey = false; mContinueWithoutPublicKey = false;
} }
}) })
.create(); .create();
case DIALOG_CONFIRM_DISCARD_ON_BACK: case DIALOG_CONFIRM_DISCARD_ON_BACK:
return new AlertDialog.Builder(this) return new AlertDialog.Builder(this)
.setTitle(R.string.confirm_discard_draft_message_title) .setTitle(R.string.confirm_discard_draft_message_title)
.setMessage(R.string.confirm_discard_draft_message) .setMessage(R.string.confirm_discard_draft_message)
.setPositiveButton(R.string.cancel_action, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK); dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
} }
}) })
.setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int whichButton) { public void onClick(DialogInterface dialog, int whichButton) {
dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK); dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
Toast.makeText(MessageCompose.this, Toast.makeText(MessageCompose.this,
getString(R.string.message_discarded_toast), getString(R.string.message_discarded_toast),
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
onDiscard(); onDiscard();
} }
}) })
.create(); .create();
case DIALOG_CHOOSE_IDENTITY: case DIALOG_CHOOSE_IDENTITY:
Context context = new ContextThemeWrapper(this, Context context = new ContextThemeWrapper(this,
(K9.getK9Theme() == K9.Theme.LIGHT) ? (K9.getK9Theme() == K9.Theme.LIGHT) ?
R.style.Theme_K9_Dialog_Light : R.style.Theme_K9_Dialog_Light :
R.style.Theme_K9_Dialog_Dark); R.style.Theme_K9_Dialog_Dark);
Builder builder = new AlertDialog.Builder(context); Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.send_as); builder.setTitle(R.string.send_as);
final IdentityAdapter adapter = new IdentityAdapter(context); final IdentityAdapter adapter = new IdentityAdapter(context);
builder.setAdapter(adapter, new DialogInterface.OnClickListener() { builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
IdentityContainer container = (IdentityContainer) adapter.getItem(which); IdentityContainer container = (IdentityContainer) adapter.getItem(which);
onAccountChosen(container.account, container.identity); onAccountChosen(container.account, container.identity);
} }
}); });
return builder.create(); return builder.create();
} }
return super.onCreateDialog(id); return super.onCreateDialog(id);
} }
@ -2993,8 +2892,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
} }
} else { } else {
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "could not get Message-ID."); Log.d(K9.LOG_TAG, "could not get Message-ID.");
}
} }
// Quote the message and setup the UI. // Quote the message and setup the UI.
@ -3387,10 +3287,10 @@ public class MessageCompose extends K9Activity implements OnClickListener,
List<Integer> start = new ArrayList<Integer>(); List<Integer> start = new ArrayList<Integer>();
List<Integer> end = new ArrayList<Integer>(); List<Integer> end = new ArrayList<Integer>();
while(blockquoteStart.find()) { while (blockquoteStart.find()) {
start.add(blockquoteStart.start()); start.add(blockquoteStart.start());
} }
while(blockquoteEnd.find()) { while (blockquoteEnd.find()) {
end.add(blockquoteEnd.start()); end.add(blockquoteEnd.start());
} }
if (start.size() != end.size()) { if (start.size() != end.size()) {
@ -3405,8 +3305,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
} else { } else {
for (int i = 0; i < start.size() - 1; i++) { for (int i = 0; i < start.size() - 1; i++) {
// within blockquotes. // within blockquotes.
if (end.get(i) < start.get(i+1)) { if (end.get(i) < start.get(i + 1)) {
dashSignatureHtml.region(end.get(i), start.get(i+1)); dashSignatureHtml.region(end.get(i), start.get(i + 1));
if (dashSignatureHtml.find()) { if (dashSignatureHtml.find()) {
content = content.substring(0, dashSignatureHtml.start()); content = content.substring(0, dashSignatureHtml.start());
break; break;
@ -3493,30 +3393,34 @@ public class MessageCompose extends K9Activity implements OnClickListener,
// HTML takes precedence, then text. // HTML takes precedence, then text.
part = MimeUtility.findFirstPartByMimeType(message, "text/html"); part = MimeUtility.findFirstPartByMimeType(message, "text/html");
if (part != null) { if (part != null) {
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, HTML found."); Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, HTML found.");
}
return MimeUtility.getTextFromPart(part); return MimeUtility.getTextFromPart(part);
} }
part = MimeUtility.findFirstPartByMimeType(message, "text/plain"); part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part != null) { if (part != null) {
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, text found."); Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, text found.");
}
return HtmlConverter.textToHtml(MimeUtility.getTextFromPart(part)); return HtmlConverter.textToHtml(MimeUtility.getTextFromPart(part));
} }
} else if (format == SimpleMessageFormat.TEXT) { } else if (format == SimpleMessageFormat.TEXT) {
// Text takes precedence, then html. // Text takes precedence, then html.
part = MimeUtility.findFirstPartByMimeType(message, "text/plain"); part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part != null) { if (part != null) {
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, text found."); Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, text found.");
}
return MimeUtility.getTextFromPart(part); return MimeUtility.getTextFromPart(part);
} }
part = MimeUtility.findFirstPartByMimeType(message, "text/html"); part = MimeUtility.findFirstPartByMimeType(message, "text/html");
if (part != null) { if (part != null) {
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, HTML found."); Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, HTML found.");
}
return HtmlConverter.htmlToText(MimeUtility.getTextFromPart(part)); return HtmlConverter.htmlToText(MimeUtility.getTextFromPart(part));
} }
} }
@ -3583,8 +3487,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
hasBodyTag = true; hasBodyTag = true;
} }
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Open: hasHtmlTag:" + hasHtmlTag + " hasHeadTag:" + hasHeadTag + " hasBodyTag:" + hasBodyTag); Log.d(K9.LOG_TAG, "Open: hasHtmlTag:" + hasHtmlTag + " hasHeadTag:" + hasHeadTag + " hasBodyTag:" + hasBodyTag);
}
// Given our inspections, let's figure out where to start our content. // Given our inspections, let's figure out where to start our content.
// This is the ideal case -- there's a BODY tag and we insert ourselves just after it. // This is the ideal case -- there's a BODY tag and we insert ourselves just after it.
@ -3635,8 +3540,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
hasBodyEndTag = true; hasBodyEndTag = true;
} }
if (K9.DEBUG) if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Close: hasHtmlEndTag:" + hasHtmlEndTag + " hasBodyEndTag:" + hasBodyEndTag); Log.d(K9.LOG_TAG, "Close: hasHtmlEndTag:" + hasHtmlEndTag + " hasBodyEndTag:" + hasBodyEndTag);
}
// Now figure out where to put our footer. // Now figure out where to put our footer.
// This is the ideal case -- there's a BODY tag and we insert ourselves just before it. // This is the ideal case -- there's a BODY tag and we insert ourselves just before it.

View File

@ -0,0 +1,242 @@
package com.fsck.k9.mail.internet;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.activity.InsertableHtmlContent;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.mail.Body;
public class TextBodyBuilder {
private boolean mIncludeQuotedText = true;
private boolean mReplyAfterQuote = false;
private boolean mSignatureBeforeQuotedText = false;
private boolean mInsertSeparator = false;
private boolean mAppendSignature = true;
private String mMessageContent;
private String mSignature;
private String mQuotedText;
private InsertableHtmlContent mQuotedTextHtml;
public TextBodyBuilder(String messageContent) {
mMessageContent = messageContent;
}
/**
* Build the {@link Body} that will contain the text of the message.
*
* @return {@link TextBody} instance that contains the entered text and
* possibly the quoted original message.
*/
public TextBody buildTextHtml() {
// The length of the formatted version of the user-supplied text/reply
int composedMessageLength;
// The offset of the user-supplied text/reply in the final text body
int composedMessageOffset;
// Get the user-supplied text
String text = mMessageContent;
// Do we have to modify an existing message to include our reply?
if (mIncludeQuotedText) {
InsertableHtmlContent quotedHtmlContent = getQuotedTextHtml();
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "insertable: " + quotedHtmlContent.toDebugString());
}
if (mAppendSignature) {
// Append signature to the reply
if (mReplyAfterQuote || mSignatureBeforeQuotedText) {
text += getSignature();
}
}
// Convert the text to HTML
text = textToHtmlFragment(text);
/*
* Set the insertion location based upon our reply after quote
* setting. Additionally, add some extra separators between the
* composed message and quoted message depending on the quote
* location. We only add the extra separators when we're
* sending, that way when we load a draft, we don't have to know
* the length of the separators to remove them before editing.
*/
if (mReplyAfterQuote) {
quotedHtmlContent.setInsertionLocation(
InsertableHtmlContent.InsertionLocation.AFTER_QUOTE);
if (mInsertSeparator) {
text = "<br clear=\"all\">" + text;
}
} else {
quotedHtmlContent.setInsertionLocation(
InsertableHtmlContent.InsertionLocation.BEFORE_QUOTE);
if (mInsertSeparator) {
text += "<br><br>";
}
}
if (mAppendSignature) {
// Place signature immediately after the quoted text
if (!(mReplyAfterQuote || mSignatureBeforeQuotedText)) {
quotedHtmlContent.insertIntoQuotedFooter(getSignatureHtml());
}
}
quotedHtmlContent.setUserContent(text);
// Save length of the body and its offset. This is used when thawing drafts.
composedMessageLength = text.length();
composedMessageOffset = quotedHtmlContent.getInsertionPoint();
text = quotedHtmlContent.toString();
} else {
// There is no text to quote so simply append the signature if available
if (mAppendSignature) {
text += getSignature();
}
// Convert the text to HTML
text = textToHtmlFragment(text);
//TODO: Wrap this in proper HTML tags
composedMessageLength = text.length();
composedMessageOffset = 0;
}
TextBody body = new TextBody(text);
body.setComposedMessageLength(composedMessageLength);
body.setComposedMessageOffset(composedMessageOffset);
return body;
}
/**
* Build the {@link Body} that will contain the text of the message.
*
* @return {@link TextBody} instance that contains the entered text and
* possibly the quoted original message.
*/
public TextBody buildTextPlain() {
// The length of the formatted version of the user-supplied text/reply
int composedMessageLength;
// The offset of the user-supplied text/reply in the final text body
int composedMessageOffset;
// Get the user-supplied text
String text = mMessageContent;
// Capture composed message length before we start attaching quoted parts and signatures.
composedMessageLength = text.length();
composedMessageOffset = 0;
// Do we have to modify an existing message to include our reply?
if (mIncludeQuotedText) {
String quotedText = getQuotedText();
if (mAppendSignature) {
// Append signature to the text/reply
if (mReplyAfterQuote || mSignatureBeforeQuotedText) {
text += getSignature();
}
}
if (mReplyAfterQuote) {
composedMessageOffset = quotedText.length() + "\r\n".length();
text = quotedText + "\r\n" + text;
} else {
text += "\r\n\r\n" + quotedText;
}
if (mAppendSignature) {
// Place signature immediately after the quoted text
if (!(mReplyAfterQuote || mSignatureBeforeQuotedText)) {
text += getSignature();
}
}
} else {
// There is no text to quote so simply append the signature if available
if (mAppendSignature) {
// Append signature to the text/reply
text += getSignature();
}
}
TextBody body = new TextBody(text);
body.setComposedMessageLength(composedMessageLength);
body.setComposedMessageOffset(composedMessageOffset);
return body;
}
private String getSignature() {
String signature = "";
if (!StringUtils.isNullOrEmpty(mSignature)) {
signature = "\r\n" + mSignature;
}
return signature;
}
private String getSignatureHtml() {
String signature = "";
if (!StringUtils.isNullOrEmpty(mSignature)) {
signature = textToHtmlFragment("\r\n" + mSignature);
}
return signature;
}
private String getQuotedText() {
String quotedText = "";
if (!StringUtils.isNullOrEmpty(mQuotedText)) {
quotedText = mQuotedText;
}
return quotedText;
}
private InsertableHtmlContent getQuotedTextHtml() {
return mQuotedTextHtml;
}
public String textToHtmlFragment(String text) {
return HtmlConverter.textToHtmlFragment(text);
}
public void setSignature(String signature) {
mSignature = signature;
}
public void setIncludeQuotedText(boolean includeQuotedText) {
mIncludeQuotedText = includeQuotedText;
}
public void setQuotedText(String quotedText) {
mQuotedText = quotedText;
}
public void setQuotedTextHtml(InsertableHtmlContent quotedTextHtml) {
mQuotedTextHtml = quotedTextHtml;
}
public void setInsertSeparator(boolean insertSeparator) {
mInsertSeparator = insertSeparator;
}
public void setSignatureBeforeQuotedText(boolean signatureBeforeQuotedText) {
mSignatureBeforeQuotedText = signatureBeforeQuotedText;
}
public void setReplyAfterQuote(boolean replyAfterQuote) {
mReplyAfterQuote = replyAfterQuote;
}
public void setAppendSignature(boolean appendSignature) {
mAppendSignature = appendSignature;
}
}

View File

@ -0,0 +1,5 @@
package com.fsck.k9;
public class K9 {
public static boolean DEBUG = false;
}

View File

@ -0,0 +1,328 @@
package com.fsck.k9.mail.internet;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.experimental.theories.*;
import org.junit.runner.RunWith;
import com.fsck.k9.Account.QuoteStyle;
import com.fsck.k9.activity.InsertableHtmlContent;
class TestingTextBodyBuilder extends TextBodyBuilder {
public TestingTextBodyBuilder(boolean includeQuotedText,
boolean isDraft,
QuoteStyle quoteStyle,
boolean replyAfterQuote,
boolean signatureBeforeQuotedText,
boolean useSignature,
String messageText,
String signatureText) {
super(messageText);
includeQuotedText = (isDraft || includeQuotedText);
if (includeQuotedText) {
this.setIncludeQuotedText(true);
this.setReplyAfterQuote(quoteStyle == QuoteStyle.PREFIX && replyAfterQuote);
} else {
this.setIncludeQuotedText(false);
}
this.setInsertSeparator(!isDraft);
useSignature = (!isDraft && useSignature);
if (useSignature) {
this.setAppendSignature(true);
this.setSignature(signatureText);
this.setSignatureBeforeQuotedText(signatureBeforeQuotedText);
} else {
this.setAppendSignature(false);
}
}
// HtmlConverter depends on Android.
// So we use dummy method for tests.
@Override
public String textToHtmlFragment(String text) {
return "<html>" + text + "</html>";
}
}
@RunWith(Theories.class)
public class TextBodyBuilderTest {
@DataPoints
public static boolean[] BOOLEANS = { true, false };
@DataPoints
public static QuoteStyle[] QUOTESTYLES = { QuoteStyle.PREFIX, QuoteStyle.HEADER };
@Theory
public void testBuildTextPlain(boolean includeQuotedText,
QuoteStyle quoteStyle,
boolean isReplyAfterQuote,
boolean isSignatureUse,
boolean isSignatureBeforeQuotedText,
boolean isDraft) {
String expectedText;
int expectedMessageLength;
int expectedMessagePosition;
// 1.quoted text
// 2.message content
// 3.signature
if (quoteStyle == QuoteStyle.PREFIX && isReplyAfterQuote) {
String expectedQuotedText = "";
if (isDraft || includeQuotedText) {
expectedQuotedText = "quoted text" + "\r\n";
}
expectedText = expectedQuotedText;
expectedText += "message content";
if (!isDraft && isSignatureUse) {
expectedText += "\r\n" + "signature";
}
expectedMessageLength = "message content".length();
expectedMessagePosition = expectedQuotedText.length();
}
// 1.message content
// 2.signature
// 3.quoted text
else if (isSignatureBeforeQuotedText) {
expectedText = "message content";
if (!isDraft && isSignatureUse) {
expectedText += "\r\n" + "signature";
}
if (isDraft || includeQuotedText) {
expectedText += "\r\n\r\nquoted text";
}
expectedMessageLength = "message content".length();
expectedMessagePosition = 0;
}
// 1.message content
// 2.quoted text
// 3.signature
else {
expectedText = "message content";
if (isDraft || includeQuotedText) {
expectedText += "\r\n\r\nquoted text";
}
if (!isDraft && isSignatureUse) {
expectedText += "\r\n" + "signature";
}
expectedMessageLength = "message content".length();
expectedMessagePosition = 0;
}
String quotedText = "quoted text";
String messageText = "message content";
String signatureText = "signature";
TestingTextBodyBuilder textBodyBuilder = new TestingTextBodyBuilder(
includeQuotedText,
isDraft,
quoteStyle,
isReplyAfterQuote,
isSignatureBeforeQuotedText,
isSignatureUse,
messageText,
signatureText
);
textBodyBuilder.setQuotedText(quotedText);
TextBody textBody = textBodyBuilder.buildTextPlain();
assertThat(textBody, instanceOf(TextBody.class));
assertThat(textBody.getText(), is(expectedText));
assertThat(textBody.getComposedMessageLength(), is(expectedMessageLength));
assertThat(textBody.getComposedMessageOffset(), is(expectedMessagePosition));
assertThat(textBody.getText().substring(expectedMessagePosition, expectedMessagePosition + expectedMessageLength),
is("message content"));
}
/**
* generate expected HtmlContent debug string
*
* @param expectedText
* @param quotedContent
* @param footerInsertionPoint
* @param isBefore
* @param userContent
* @param compiledResult
* @return expected string
*
* @see InsertableHtmlContent#toDebugString()
*/
public String makeExpectedHtmlContent(String expectedText, String quotedContent,
int footerInsertionPoint, boolean isBefore,
String userContent, String compiledResult) {
String expectedHtmlContent = "InsertableHtmlContent{"
+ "headerInsertionPoint=0,"
+ " footerInsertionPoint=" + footerInsertionPoint + ","
+ " insertionLocation=" + (isBefore ? "BEFORE_QUOTE" : "AFTER_QUOTE") + ","
+ " quotedContent=" + quotedContent + ","
+ " userContent=" + userContent + ","
+ " compiledResult=" + compiledResult
+ "}";
return expectedHtmlContent;
}
@Theory
public void testBuildTextHtml(boolean includeQuotedText,
QuoteStyle quoteStyle,
boolean isReplyAfterQuote,
boolean isSignatureUse,
boolean isSignatureBeforeQuotedText,
boolean isDraft) {
String expectedText;
int expectedMessageLength;
int expectedMessagePosition = 0;
String expectedHtmlContent;
String expectedPrefix = "";
if (includeQuotedText && quoteStyle == QuoteStyle.PREFIX && isReplyAfterQuote && !isDraft) {
expectedPrefix = "<br clear=\"all\">";
}
String expectedPostfix = "";
if (!isDraft && includeQuotedText) {
expectedPostfix = "<br><br>";
}
// 1.quoted text
// 2.message content
// 3.signature
if (quoteStyle == QuoteStyle.PREFIX && isReplyAfterQuote) {
expectedText = expectedPrefix
+ "<html>message content";
if (!isDraft && isSignatureUse) {
expectedText += "\r\n" + "signature";
}
expectedText += "</html>";
expectedMessageLength = expectedText.length();
String quotedContent = "quoted text";
if (isDraft || includeQuotedText) {
expectedHtmlContent = makeExpectedHtmlContent(expectedText, quotedContent,
0,
false,
expectedText,
expectedText + quotedContent);
expectedText += quotedContent;
} else {
expectedHtmlContent = makeExpectedHtmlContent(expectedText, quotedContent,
0,
true,
"",
quotedContent);
// expectedText += quotedContent;
}
}
// 1.message content
// 2.signature
// 3.quoted text
else if (isSignatureBeforeQuotedText) {
expectedText = expectedPrefix
+ "<html>message content";
if (!isDraft && isSignatureUse) {
expectedText += "\r\n" + "signature";
}
expectedText += "</html>";
expectedText += expectedPostfix;
expectedMessageLength = expectedText.length();
String quotedContent = "quoted text";
if (isDraft || includeQuotedText) {
expectedHtmlContent = makeExpectedHtmlContent(expectedText, quotedContent,
0,
true,
expectedText,
expectedText + quotedContent);
expectedText += quotedContent;
} else {
expectedHtmlContent = makeExpectedHtmlContent(expectedText, quotedContent,
0,
true,
"",
quotedContent);
// expectedText += quotedContent;
}
}
// 1.message content
// 2.quoted text
// 3.signature
else {
String expectedSignature = "";
expectedText = expectedPrefix
+ "<html>message content";
if (!isDraft && isSignatureUse) {
if (!includeQuotedText) {
expectedText += "\r\n" + "signature";
} else {
expectedSignature = "<html>\r\nsignature</html>";
}
}
expectedText += "</html>";
expectedText += expectedPostfix;
expectedMessageLength = expectedText.length();
String quotedContent = "quoted text";
if (isDraft || includeQuotedText) {
expectedHtmlContent = makeExpectedHtmlContent(expectedText, expectedSignature + quotedContent,
expectedSignature.length(),
true,
expectedText,
expectedText + expectedSignature + quotedContent);
expectedText += expectedSignature + quotedContent;
} else {
expectedHtmlContent = makeExpectedHtmlContent(expectedText, quotedContent,
0,
true,
"",
quotedContent);
// expectedText += quotedContent;
}
}
InsertableHtmlContent insertableHtmlContent = new InsertableHtmlContent();
String quotedText = "quoted text";
insertableHtmlContent.setQuotedContent(new StringBuilder(quotedText));
String messageText = "message content";
String signatureText = "signature";
TestingTextBodyBuilder textBodyBuilder = new TestingTextBodyBuilder(
includeQuotedText,
isDraft,
quoteStyle,
isReplyAfterQuote,
isSignatureBeforeQuotedText,
isSignatureUse,
messageText,
signatureText
);
textBodyBuilder.setQuotedTextHtml(insertableHtmlContent);
TextBody textBody = textBodyBuilder.buildTextHtml();
assertThat(textBody, instanceOf(TextBody.class));
assertThat(textBody.getText(), is(expectedText));
assertThat(textBody.getComposedMessageLength(), is(expectedMessageLength));
assertThat(textBody.getComposedMessageOffset(), is(expectedMessagePosition));
assertThat(insertableHtmlContent.toDebugString(), is(expectedHtmlContent));
}
}