diff --git a/build.gradle b/build.gradle
index 02a139d31..2fae19a89 100644
--- a/build.gradle
+++ b/build.gradle
@@ -50,3 +50,8 @@ android {
exclude 'META-INF/NOTICE'
}
}
+
+task testsOnJVM(type :GradleBuild, dependsOn: assemble) {
+ buildFile = 'tests-on-jvm/build.gradle'
+ tasks = ['test']
+}
diff --git a/src/com/fsck/k9/activity/InsertableHtmlContent.java b/src/com/fsck/k9/activity/InsertableHtmlContent.java
index 851f0ca9a..9821ad784 100644
--- a/src/com/fsck/k9/activity/InsertableHtmlContent.java
+++ b/src/com/fsck/k9/activity/InsertableHtmlContent.java
@@ -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?
*/
-class InsertableHtmlContent implements Serializable {
+public class InsertableHtmlContent implements Serializable {
private static final long serialVersionUID = 2397327034L;
// Default to a headerInsertionPoint at the beginning of the message.
private int headerInsertionPoint = 0;
diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java
index 9426a7b6d..66b07cf26 100644
--- a/src/com/fsck/k9/activity/MessageCompose.java
+++ b/src/com/fsck/k9/activity/MessageCompose.java
@@ -88,6 +88,7 @@ import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
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.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
@@ -111,6 +112,7 @@ import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -194,7 +196,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private static final int CONTACT_PICKER_BCC2 = 9;
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[0];
-
+
private static final int REQUEST_CODE_SIGN_ENCRYPT = 12;
/**
@@ -318,10 +320,10 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private PgpData mPgpData = null;
private boolean mAutoEncrypt = false;
private boolean mContinueWithoutPublicKey = false;
-
+
private String mOpenPgpProvider;
private OpenPgpServiceConnection mOpenPgpServiceConnection;
-
+
private String mReferences;
private String mInReplyTo;
private Menu mMenu;
@@ -381,36 +383,36 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
- case MSG_PROGRESS_ON:
- setSupportProgressBarIndeterminateVisibility(true);
- break;
- case MSG_PROGRESS_OFF:
- setSupportProgressBarIndeterminateVisibility(false);
- break;
- case MSG_SKIPPED_ATTACHMENTS:
- Toast.makeText(
- MessageCompose.this,
- getString(R.string.message_compose_attachments_skipped_toast),
- Toast.LENGTH_LONG).show();
- break;
- case MSG_SAVED_DRAFT:
- Toast.makeText(
- MessageCompose.this,
- getString(R.string.message_saved_toast),
- Toast.LENGTH_LONG).show();
- break;
- case MSG_DISCARDED_DRAFT:
- Toast.makeText(
- MessageCompose.this,
- getString(R.string.message_discarded_toast),
- Toast.LENGTH_LONG).show();
- break;
- case MSG_PERFORM_STALLED_ACTION:
- performStalledAction();
- break;
- default:
- super.handleMessage(msg);
- break;
+ case MSG_PROGRESS_ON:
+ setSupportProgressBarIndeterminateVisibility(true);
+ break;
+ case MSG_PROGRESS_OFF:
+ setSupportProgressBarIndeterminateVisibility(false);
+ break;
+ case MSG_SKIPPED_ATTACHMENTS:
+ Toast.makeText(
+ MessageCompose.this,
+ getString(R.string.message_compose_attachments_skipped_toast),
+ Toast.LENGTH_LONG).show();
+ break;
+ case MSG_SAVED_DRAFT:
+ Toast.makeText(
+ MessageCompose.this,
+ getString(R.string.message_saved_toast),
+ Toast.LENGTH_LONG).show();
+ break;
+ case MSG_DISCARDED_DRAFT:
+ Toast.makeText(
+ MessageCompose.this,
+ getString(R.string.message_discarded_toast),
+ Toast.LENGTH_LONG).show();
+ break;
+ case MSG_PERFORM_STALLED_ACTION:
+ performStalledAction();
+ break;
+ default:
+ super.handleMessage(msg);
+ break;
}
}
};
@@ -560,8 +562,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
mMessageReference = intent.getParcelableExtra(EXTRA_MESSAGE_REFERENCE);
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.");
+ }
final String accountUuid = (mMessageReference != null) ?
mMessageReference.accountUuid :
@@ -869,7 +872,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
initializeCrypto();
-
+
final CryptoProvider crypto = mAccount.getCryptoProvider();
mOpenPgpProvider = mAccount.getOpenPgpProvider();
if (mOpenPgpProvider != null) {
@@ -878,7 +881,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
// bind to service
mOpenPgpServiceConnection = new OpenPgpServiceConnection(this, mOpenPgpProvider);
mOpenPgpServiceConnection.bindToService();
-
+
mEncryptLayout.setVisibility(View.VISIBLE);
mCryptoSignatureCheckbox.setOnClickListener(new OnClickListener() {
@Override
@@ -947,7 +950,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
setTitle();
}
-
+
@Override
public void onDestroy() {
super.onDestroy();
@@ -1350,136 +1353,56 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* original message.
*/
private TextBody buildText(boolean isDraft, SimpleMessageFormat messageFormat) {
- // The length of the formatted version of the user-supplied text/reply
- int composedMessageLength;
+ String messageText = mMessageContentView.getCharacters();
- // The offset of the user-supplied text/reply in the final text body
- int composedMessageOffset;
+ TextBodyBuilder textBodyBuilder = new TextBodyBuilder(messageText);
/*
* 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
- * include the quoted text when we're saving a draft. That's so the user is able to
- * "un-hide" the quoted text if (s)he opens a saved draft.
+ * We include the quoted text in the body if the user didn't choose to
+ * hide it. We always include the quoted text when we're saving a 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
- boolean replyAfterQuote = (mQuoteStyle == QuoteStyle.HEADER) ?
- false : mAccount.isReplyAfterQuote();
-
- boolean signatureBeforeQuotedText = mAccount.isSignatureBeforeQuotedText();
-
- // 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 = "
" + text;
- }
- } else {
- mQuotedHtmlContent.setInsertionLocation(
- InsertableHtmlContent.InsertionLocation.BEFORE_QUOTE);
- if (!isDraft) {
- text += "
";
- }
- }
-
- 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);
- }
+ textBodyBuilder.setIncludeQuotedText(false);
+ if (includeQuotedText) {
+ if (messageFormat == SimpleMessageFormat.HTML && mQuotedHtmlContent != null) {
+ textBodyBuilder.setIncludeQuotedText(true);
+ textBodyBuilder.setQuotedTextHtml(mQuotedHtmlContent);
+ textBodyBuilder.setReplyAfterQuote(isReplyAfterQuote);
}
String quotedText = mQuotedText.getCharacters();
- if (includeQuotedText && quotedText.length() > 0) {
- if (replyAfterQuote) {
- composedMessageOffset = quotedText.length() + "\r\n".length();
- text = quotedText + "\r\n" + text;
- } else {
- text += "\r\n\r\n" + quotedText.toString();
- }
- }
-
- if (!isDraft) {
- // Place signature immediately after the quoted text
- if (!(replyAfterQuote || signatureBeforeQuotedText)) {
- text = appendSignature(text);
- }
+ if (messageFormat == SimpleMessageFormat.TEXT && quotedText.length() > 0) {
+ textBodyBuilder.setIncludeQuotedText(true);
+ textBodyBuilder.setQuotedText(quotedText);
+ textBodyBuilder.setReplyAfterQuote(isReplyAfterQuote);
}
}
- TextBody body = new TextBody(text);
- body.setComposedMessageLength(composedMessageLength);
- body.setComposedMessageOffset(composedMessageOffset);
+ textBodyBuilder.setInsertSeparator(!isDraft);
+ 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;
}
-
/**
* 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.
@@ -1628,8 +1551,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* title*3="isn't it!"
*/
bp.addHeader(MimeHeader.HEADER_CONTENT_DISPOSITION, String.format(Locale.US,
- "attachment;\r\n filename=\"%s\";\r\n size=%d",
- attachment.name, attachment.size));
+ "attachment;\r\n filename=\"%s\";\r\n size=%d",
+ attachment.name, attachment.size));
mp.addBodyPart(bp);
}
@@ -1762,8 +1685,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private Map parseIdentityHeader(final String identityString) {
Map identity = new HashMap();
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Decoding identity: " + identityString);
+ }
if (identityString == null || identityString.length() < 1) {
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());
+ }
// Sanity check our Integers so that recipients of this result don't have to.
for (IdentityField key : IdentityField.getIntegerFields()) {
@@ -1797,8 +1722,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
} else {
// Legacy identity
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Got a saved legacy identity: " + identityString);
+ }
StringTokenizer tokenizer = new StringTokenizer(identityString, ":", false);
// 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;
}
-
- 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() {
new SendMessageTask().execute();
}
@@ -1911,7 +1808,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private void performSend() {
final CryptoProvider crypto = mAccount.getCryptoProvider();
-
+
if (mOpenPgpProvider != null) {
// OpenPGP Provider API
@@ -1929,18 +1826,18 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
emailsArray = emails.toArray(new String[emails.size()]);
}
- if (mEncryptCheckbox.isChecked() && mCryptoSignatureCheckbox.isChecked()) {
- Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
- intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, emailsArray);
- executeOpenPgpMethod(intent);
- } else if (mCryptoSignatureCheckbox.isChecked()) {
- Intent intent = new Intent(OpenPgpApi.ACTION_SIGN);
- executeOpenPgpMethod(intent);
- } else if (mEncryptCheckbox.isChecked()) {
- Intent intent = new Intent(OpenPgpApi.ACTION_ENCRYPT);
- intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, emailsArray);
- executeOpenPgpMethod(intent);
- }
+ if (mEncryptCheckbox.isChecked() && mCryptoSignatureCheckbox.isChecked()) {
+ Intent intent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
+ intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, emailsArray);
+ executeOpenPgpMethod(intent);
+ } else if (mCryptoSignatureCheckbox.isChecked()) {
+ Intent intent = new Intent(OpenPgpApi.ACTION_SIGN);
+ executeOpenPgpMethod(intent);
+ } else if (mEncryptCheckbox.isChecked()) {
+ Intent intent = new Intent(OpenPgpApi.ACTION_ENCRYPT);
+ intent.putExtra(OpenPgpApi.EXTRA_USER_IDS, emailsArray);
+ executeOpenPgpMethod(intent);
+ }
// onSend() is called again in SignEncryptCallback and with
// encryptedData set in pgpData!
@@ -1948,7 +1845,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
} else if (crypto.isAvailable(this)) {
// Legacy APG API
-
+
if (mEncryptCheckbox.isChecked() && !mPgpData.hasEncryptionKeys()) {
// key selection before encryption
StringBuilder emails = new StringBuilder();
@@ -1988,8 +1885,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
sendMessage();
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);
+ }
final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
final String folderName = mMessageReference.folderName;
@@ -2000,7 +1898,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
mDraftNeedsSaving = false;
finish();
}
-
+
private InputStream getOpenPgpInputStream() {
String text = buildText(false).getText();
@@ -2012,7 +1910,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
return is;
}
-
+
private void executeOpenPgpMethod(Intent intent) {
intent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
// this follows user id format of OpenPGP to allow key generation based on it
@@ -2028,7 +1926,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
OpenPgpApi api = new OpenPgpApi(this, mOpenPgpServiceConnection.getService());
api.executeApiAsync(intent, is, os, callback);
}
-
+
/**
* Called on successful encrypt/verify
*/
@@ -2049,8 +1947,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
final String output = os.toString("UTF-8");
if (K9.DEBUG)
- Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length
- + " str=" + output);
+ Log.d(OpenPgpApi.TAG, "result: " + os.toByteArray().length +
+ " str=" + output);
mPgpData.setEncryptedData(output);
onSend();
@@ -2078,7 +1976,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
}
}
-
+
private void handleOpenPgpErrors(final OpenPgpError error) {
runOnUiThread(new Runnable() {
@@ -2086,7 +1984,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
public void run() {
Log.e(K9.LOG_TAG, "OpenPGP Error ID:" + error.getErrorId());
Log.e(K9.LOG_TAG, "OpenPGP Error Message:" + error.getMessage());
-
+
Toast.makeText(MessageCompose.this,
getString(R.string.openpgp_error) + " " + error.getMessage(),
Toast.LENGTH_LONG).show();
@@ -2364,7 +2262,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// if a CryptoSystem activity is returning, then mPreventDraftSaving was set to true
mPreventDraftSaving = false;
-
+
// OpenPGP: try again after user interaction
if (resultCode == RESULT_OK && requestCode == REQUEST_CODE_SIGN_ENCRYPT) {
/*
@@ -2382,71 +2280,72 @@ public class MessageCompose extends K9Activity implements OnClickListener,
return;
}
- if (resultCode != RESULT_OK)
+ if (resultCode != RESULT_OK) {
return;
+ }
if (data == null) {
return;
}
switch (requestCode) {
- case ACTIVITY_REQUEST_PICK_ATTACHMENT:
- addAttachmentsFromResultIntent(data);
- mDraftNeedsSaving = true;
- break;
- case CONTACT_PICKER_TO:
- case CONTACT_PICKER_CC:
- case CONTACT_PICKER_BCC:
- ContactItem contact = mContacts.extractInfoFromContactPickerIntent(data);
- if (contact == null) {
- Toast.makeText(this, getString(R.string.error_contact_address_not_found), Toast.LENGTH_LONG).show();
- return;
- }
- if (contact.emailAddresses.size() > 1) {
- Intent i = new Intent(this, EmailAddressList.class);
- i.putExtra(EmailAddressList.EXTRA_CONTACT_ITEM, contact);
+ case ACTIVITY_REQUEST_PICK_ATTACHMENT:
+ addAttachmentsFromResultIntent(data);
+ mDraftNeedsSaving = true;
+ break;
+ case CONTACT_PICKER_TO:
+ case CONTACT_PICKER_CC:
+ case CONTACT_PICKER_BCC:
+ ContactItem contact = mContacts.extractInfoFromContactPickerIntent(data);
+ if (contact == null) {
+ Toast.makeText(this, getString(R.string.error_contact_address_not_found), Toast.LENGTH_LONG).show();
+ return;
+ }
+ if (contact.emailAddresses.size() > 1) {
+ Intent i = new Intent(this, EmailAddressList.class);
+ 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 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) {
- startActivityForResult(i, CONTACT_PICKER_TO2);
+ addAddress(mToView, new Address(email, ""));
} else if (requestCode == CONTACT_PICKER_CC) {
- startActivityForResult(i, CONTACT_PICKER_CC2);
+ addAddress(mCcView, new Address(email, ""));
} else if (requestCode == CONTACT_PICKER_BCC) {
- startActivityForResult(i, CONTACT_PICKER_BCC2);
+ addAddress(mBccView, new Address(email, ""));
+ } else {
+ return;
}
- return;
- }
- if (K9.DEBUG) {
- List emails = contact.emailAddresses;
- for (int i = 0; i < emails.size(); i++) {
- Log.v(K9.LOG_TAG, "email[" + i + "]: " + emails.get(i));
+
+
+
+ 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, ""));
}
- }
-
-
- 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;
+ break;
}
}
@@ -2563,39 +2462,39 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override
public void onClick(View view) {
switch (view.getId()) {
- case R.id.attachment_delete:
- /*
- * 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
- * view is very complex and could change in the future.
- */
- mAttachments.removeView((View) view.getTag());
- mDraftNeedsSaving = true;
- break;
- case R.id.quoted_text_show:
- showOrHideQuotedText(QuotedTextMode.SHOW);
- updateMessageFormat();
- mDraftNeedsSaving = true;
- break;
- case R.id.quoted_text_delete:
- showOrHideQuotedText(QuotedTextMode.HIDE);
- updateMessageFormat();
- mDraftNeedsSaving = true;
- break;
- case R.id.quoted_text_edit:
- mForcePlainText = true;
- if (mMessageReference != null) { // shouldn't happen...
- // TODO - Should we check if mSourceMessageBody is already present and bypass the MessagingController call?
- MessagingController.getInstance(getApplication()).addListener(mListener);
- final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
- final String folderName = mMessageReference.folderName;
- final String sourceMessageUid = mMessageReference.uid;
- MessagingController.getInstance(getApplication()).loadMessageForView(account, folderName, sourceMessageUid, null);
- }
- break;
- case R.id.identity:
- showDialog(DIALOG_CHOOSE_IDENTITY);
- break;
+ case R.id.attachment_delete:
+ /*
+ * 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
+ * view is very complex and could change in the future.
+ */
+ mAttachments.removeView((View) view.getTag());
+ mDraftNeedsSaving = true;
+ break;
+ case R.id.quoted_text_show:
+ showOrHideQuotedText(QuotedTextMode.SHOW);
+ updateMessageFormat();
+ mDraftNeedsSaving = true;
+ break;
+ case R.id.quoted_text_delete:
+ showOrHideQuotedText(QuotedTextMode.HIDE);
+ updateMessageFormat();
+ mDraftNeedsSaving = true;
+ break;
+ case R.id.quoted_text_edit:
+ mForcePlainText = true;
+ if (mMessageReference != null) { // shouldn't happen...
+ // TODO - Should we check if mSourceMessageBody is already present and bypass the MessagingController call?
+ MessagingController.getInstance(getApplication()).addListener(mListener);
+ final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid);
+ final String folderName = mMessageReference.folderName;
+ final String sourceMessageUid = mMessageReference.uid;
+ MessagingController.getInstance(getApplication()).loadMessageForView(account, folderName, sourceMessageUid, null);
+ }
+ break;
+ case R.id.identity:
+ showDialog(DIALOG_CHOOSE_IDENTITY);
+ break;
}
}
@@ -2642,36 +2541,36 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
- case R.id.send:
- mPgpData.setEncryptionKeys(null);
- onSend();
- break;
- case R.id.save:
- if (mEncryptCheckbox.isChecked()) {
- showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
- } else {
- onSave();
- }
- break;
- case R.id.discard:
- onDiscard();
- break;
- case R.id.add_cc_bcc:
- onAddCcBcc();
- break;
- case R.id.add_attachment:
- onAddAttachment();
- break;
- case R.id.add_attachment_image:
- onAddAttachment2("image/*");
- break;
- case R.id.add_attachment_video:
- onAddAttachment2("video/*");
- break;
- case R.id.read_receipt:
- onReadReceipt();
- default:
- return super.onOptionsItemSelected(item);
+ case R.id.send:
+ mPgpData.setEncryptionKeys(null);
+ onSend();
+ break;
+ case R.id.save:
+ if (mEncryptCheckbox.isChecked()) {
+ showDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
+ } else {
+ onSave();
+ }
+ break;
+ case R.id.discard:
+ onDiscard();
+ break;
+ case R.id.add_cc_bcc:
+ onAddCcBcc();
+ break;
+ case R.id.add_attachment:
+ onAddAttachment();
+ break;
+ case R.id.add_attachment_image:
+ onAddAttachment2("image/*");
+ break;
+ case R.id.add_attachment_video:
+ onAddAttachment2("video/*");
+ break;
+ case R.id.read_receipt:
+ onReadReceipt();
+ default:
+ return super.onOptionsItemSelected(item);
}
return true;
}
@@ -2769,94 +2668,94 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override
public Dialog onCreateDialog(int id) {
switch (id) {
- case DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.save_or_discard_draft_message_dlg_title)
- .setMessage(R.string.save_or_discard_draft_message_instructions_fmt)
- .setPositiveButton(R.string.save_draft_action, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dismissDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
- onSave();
- }
- })
- .setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dismissDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
- onDiscard();
- }
- })
- .create();
- case DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.refuse_to_save_draft_marked_encrypted_dlg_title)
- .setMessage(R.string.refuse_to_save_draft_marked_encrypted_instructions_fmt)
- .setNeutralButton(R.string.okay_action, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dismissDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
- }
- })
- .create();
- case DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.continue_without_public_key_dlg_title)
- .setMessage(R.string.continue_without_public_key_instructions_fmt)
- .setPositiveButton(R.string.continue_action, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
- mContinueWithoutPublicKey = true;
- onSend();
- }
- })
- .setNegativeButton(R.string.back_action, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
- mContinueWithoutPublicKey = false;
- }
- })
- .create();
- case DIALOG_CONFIRM_DISCARD_ON_BACK:
- return new AlertDialog.Builder(this)
- .setTitle(R.string.confirm_discard_draft_message_title)
- .setMessage(R.string.confirm_discard_draft_message)
- .setPositiveButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
- }
- })
- .setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int whichButton) {
- dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
- Toast.makeText(MessageCompose.this,
- getString(R.string.message_discarded_toast),
- Toast.LENGTH_LONG).show();
- onDiscard();
- }
- })
- .create();
- case DIALOG_CHOOSE_IDENTITY:
- Context context = new ContextThemeWrapper(this,
- (K9.getK9Theme() == K9.Theme.LIGHT) ?
- R.style.Theme_K9_Dialog_Light :
- R.style.Theme_K9_Dialog_Dark);
- Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(R.string.send_as);
- final IdentityAdapter adapter = new IdentityAdapter(context);
- builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- IdentityContainer container = (IdentityContainer) adapter.getItem(which);
- onAccountChosen(container.account, container.identity);
- }
- });
+ case DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE:
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.save_or_discard_draft_message_dlg_title)
+ .setMessage(R.string.save_or_discard_draft_message_instructions_fmt)
+ .setPositiveButton(R.string.save_draft_action, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dismissDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
+ onSave();
+ }
+ })
+ .setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dismissDialog(DIALOG_SAVE_OR_DISCARD_DRAFT_MESSAGE);
+ onDiscard();
+ }
+ })
+ .create();
+ case DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED:
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.refuse_to_save_draft_marked_encrypted_dlg_title)
+ .setMessage(R.string.refuse_to_save_draft_marked_encrypted_instructions_fmt)
+ .setNeutralButton(R.string.okay_action, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dismissDialog(DIALOG_REFUSE_TO_SAVE_DRAFT_MARKED_ENCRYPTED);
+ }
+ })
+ .create();
+ case DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY:
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.continue_without_public_key_dlg_title)
+ .setMessage(R.string.continue_without_public_key_instructions_fmt)
+ .setPositiveButton(R.string.continue_action, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
+ mContinueWithoutPublicKey = true;
+ onSend();
+ }
+ })
+ .setNegativeButton(R.string.back_action, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dismissDialog(DIALOG_CONTINUE_WITHOUT_PUBLIC_KEY);
+ mContinueWithoutPublicKey = false;
+ }
+ })
+ .create();
+ case DIALOG_CONFIRM_DISCARD_ON_BACK:
+ return new AlertDialog.Builder(this)
+ .setTitle(R.string.confirm_discard_draft_message_title)
+ .setMessage(R.string.confirm_discard_draft_message)
+ .setPositiveButton(R.string.cancel_action, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
+ }
+ })
+ .setNegativeButton(R.string.discard_action, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int whichButton) {
+ dismissDialog(DIALOG_CONFIRM_DISCARD_ON_BACK);
+ Toast.makeText(MessageCompose.this,
+ getString(R.string.message_discarded_toast),
+ Toast.LENGTH_LONG).show();
+ onDiscard();
+ }
+ })
+ .create();
+ case DIALOG_CHOOSE_IDENTITY:
+ Context context = new ContextThemeWrapper(this,
+ (K9.getK9Theme() == K9.Theme.LIGHT) ?
+ R.style.Theme_K9_Dialog_Light :
+ R.style.Theme_K9_Dialog_Dark);
+ Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(R.string.send_as);
+ final IdentityAdapter adapter = new IdentityAdapter(context);
+ builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ IdentityContainer container = (IdentityContainer) adapter.getItem(which);
+ onAccountChosen(container.account, container.identity);
+ }
+ });
- return builder.create();
+ return builder.create();
}
return super.onCreateDialog(id);
}
@@ -2993,8 +2892,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
} else {
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "could not get Message-ID.");
+ }
}
// Quote the message and setup the UI.
@@ -3387,10 +3287,10 @@ public class MessageCompose extends K9Activity implements OnClickListener,
List start = new ArrayList();
List end = new ArrayList();
- while(blockquoteStart.find()) {
+ while (blockquoteStart.find()) {
start.add(blockquoteStart.start());
}
- while(blockquoteEnd.find()) {
+ while (blockquoteEnd.find()) {
end.add(blockquoteEnd.start());
}
if (start.size() != end.size()) {
@@ -3405,8 +3305,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
} else {
for (int i = 0; i < start.size() - 1; i++) {
// within blockquotes.
- if (end.get(i) < start.get(i+1)) {
- dashSignatureHtml.region(end.get(i), start.get(i+1));
+ if (end.get(i) < start.get(i + 1)) {
+ dashSignatureHtml.region(end.get(i), start.get(i + 1));
if (dashSignatureHtml.find()) {
content = content.substring(0, dashSignatureHtml.start());
break;
@@ -3493,30 +3393,34 @@ public class MessageCompose extends K9Activity implements OnClickListener,
// HTML takes precedence, then text.
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
if (part != null) {
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, HTML found.");
+ }
return MimeUtility.getTextFromPart(part);
}
part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part != null) {
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, text found.");
+ }
return HtmlConverter.textToHtml(MimeUtility.getTextFromPart(part));
}
} else if (format == SimpleMessageFormat.TEXT) {
// Text takes precedence, then html.
part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part != null) {
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, text found.");
+ }
return MimeUtility.getTextFromPart(part);
}
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
if (part != null) {
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, HTML found.");
+ }
return HtmlConverter.htmlToText(MimeUtility.getTextFromPart(part));
}
}
@@ -3583,8 +3487,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
hasBodyTag = true;
}
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Open: hasHtmlTag:" + hasHtmlTag + " hasHeadTag:" + hasHeadTag + " hasBodyTag:" + hasBodyTag);
+ }
// 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.
@@ -3635,8 +3540,9 @@ public class MessageCompose extends K9Activity implements OnClickListener,
hasBodyEndTag = true;
}
- if (K9.DEBUG)
+ if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Close: hasHtmlEndTag:" + hasHtmlEndTag + " hasBodyEndTag:" + hasBodyEndTag);
+ }
// 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.
diff --git a/src/com/fsck/k9/mail/internet/TextBodyBuilder.java b/src/com/fsck/k9/mail/internet/TextBodyBuilder.java
new file mode 100644
index 000000000..96e19e9e9
--- /dev/null
+++ b/src/com/fsck/k9/mail/internet/TextBodyBuilder.java
@@ -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 = "
" + text;
+ }
+ } else {
+ quotedHtmlContent.setInsertionLocation(
+ InsertableHtmlContent.InsertionLocation.BEFORE_QUOTE);
+ if (mInsertSeparator) {
+ text += "
";
+ }
+ }
+
+ 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;
+ }
+}
diff --git a/tests-on-jvm/src/com/fsck/k9/K9.java b/tests-on-jvm/src/com/fsck/k9/K9.java
new file mode 100644
index 000000000..64c6b7c47
--- /dev/null
+++ b/tests-on-jvm/src/com/fsck/k9/K9.java
@@ -0,0 +1,5 @@
+package com.fsck.k9;
+
+public class K9 {
+ public static boolean DEBUG = false;
+}
diff --git a/tests-on-jvm/src/com/fsck/k9/mail/internet/TextBodyBuilderTest.java b/tests-on-jvm/src/com/fsck/k9/mail/internet/TextBodyBuilderTest.java
new file mode 100644
index 000000000..de7701b6a
--- /dev/null
+++ b/tests-on-jvm/src/com/fsck/k9/mail/internet/TextBodyBuilderTest.java
@@ -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 "" + text + "";
+ }
+}
+
+@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 = "
";
+ }
+ String expectedPostfix = "";
+ if (!isDraft && includeQuotedText) {
+ expectedPostfix = "
";
+ }
+
+ // 1.quoted text
+ // 2.message content
+ // 3.signature
+ if (quoteStyle == QuoteStyle.PREFIX && isReplyAfterQuote) {
+ expectedText = expectedPrefix
+ + "message content";
+ if (!isDraft && isSignatureUse) {
+ expectedText += "\r\n" + "signature";
+ }
+ expectedText += "";
+ 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
+ + "message content";
+ if (!isDraft && isSignatureUse) {
+ expectedText += "\r\n" + "signature";
+ }
+ expectedText += "";
+ 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
+ + "message content";
+
+ if (!isDraft && isSignatureUse) {
+ if (!includeQuotedText) {
+ expectedText += "\r\n" + "signature";
+ } else {
+ expectedSignature = "\r\nsignature";
+ }
+ }
+ expectedText += "";
+ 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));
+ }
+
+}