1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-12-26 01:28:50 -05:00

Merge pull request #516 from k9mail/untangle-network-code

Untangle network code
This commit is contained in:
cketti 2014-12-17 01:45:16 +01:00
commit deb11b2226
114 changed files with 4198 additions and 4109 deletions

View File

@ -29,9 +29,12 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mailstore.StorageManager;
import com.fsck.k9.mailstore.StorageManager.StorageProvider;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.StatsColumns;
import com.fsck.k9.search.ConditionsTreeNode;
@ -40,7 +43,7 @@ import com.fsck.k9.search.SqlQueryBuilder;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield;
import com.fsck.k9.security.LocalKeyStore;
import com.fsck.k9.mail.ssl.LocalKeyStore;
import com.fsck.k9.view.ColorChip;
import com.larswerkman.colorpicker.ColorPicker;
@ -48,7 +51,7 @@ import com.larswerkman.colorpicker.ColorPicker;
* Account stores all of the settings for a single account defined by the user. It is able to save
* and delete itself given a Preferences to work with. Each account is defined by a UUID.
*/
public class Account implements BaseAccount {
public class Account implements BaseAccount, StoreConfig {
/**
* Default value for the inbox folder (never changes for POP3 and IMAP)
*/
@ -361,9 +364,9 @@ public class Account implements BaseAccount {
SharedPreferences prefs = preferences.getPreferences();
mStoreUri = Utility.base64Decode(prefs.getString(mUuid + ".storeUri", null));
mStoreUri = Base64.decode(prefs.getString(mUuid + ".storeUri", null));
mLocalStorageProviderId = prefs.getString(mUuid + ".localStorageProvider", StorageManager.getInstance(K9.app).getDefaultProviderId());
mTransportUri = Utility.base64Decode(prefs.getString(mUuid + ".transportUri", null));
mTransportUri = Base64.decode(prefs.getString(mUuid + ".transportUri", null));
mDescription = prefs.getString(mUuid + ".description", null);
mAlwaysBcc = prefs.getString(mUuid + ".alwaysBcc", mAlwaysBcc);
mAutomaticCheckIntervalMinutes = prefs.getInt(mUuid + ".automaticCheckIntervalMinutes", -1);
@ -689,9 +692,9 @@ public class Account implements BaseAccount {
editor.putString("accountUuids", accountUuids);
}
editor.putString(mUuid + ".storeUri", Utility.base64Encode(mStoreUri));
editor.putString(mUuid + ".storeUri", Base64.encode(mStoreUri));
editor.putString(mUuid + ".localStorageProvider", mLocalStorageProviderId);
editor.putString(mUuid + ".transportUri", Utility.base64Encode(mTransportUri));
editor.putString(mUuid + ".transportUri", Base64.encode(mTransportUri));
editor.putString(mUuid + ".description", mDescription);
editor.putString(mUuid + ".alwaysBcc", mAlwaysBcc);
editor.putInt(mUuid + ".automaticCheckIntervalMinutes", mAutomaticCheckIntervalMinutes);
@ -1076,8 +1079,8 @@ public class Account implements BaseAccount {
return mDraftsFolderName;
}
public synchronized void setDraftsFolderName(String draftsFolderName) {
mDraftsFolderName = draftsFolderName;
public synchronized void setDraftsFolderName(String name) {
mDraftsFolderName = name;
}
/**
@ -1096,8 +1099,8 @@ public class Account implements BaseAccount {
return K9.ERROR_FOLDER_NAME;
}
public synchronized void setSentFolderName(String sentFolderName) {
mSentFolderName = sentFolderName;
public synchronized void setSentFolderName(String name) {
mSentFolderName = name;
}
/**
@ -1113,8 +1116,8 @@ public class Account implements BaseAccount {
return mTrashFolderName;
}
public synchronized void setTrashFolderName(String trashFolderName) {
mTrashFolderName = trashFolderName;
public synchronized void setTrashFolderName(String name) {
mTrashFolderName = name;
}
/**
@ -1145,8 +1148,8 @@ public class Account implements BaseAccount {
return mSpamFolderName;
}
public synchronized void setSpamFolderName(String spamFolderName) {
mSpamFolderName = spamFolderName;
public synchronized void setSpamFolderName(String name) {
mSpamFolderName = name;
}
/**
@ -1165,8 +1168,8 @@ public class Account implements BaseAccount {
return mAutoExpandFolderName;
}
public synchronized void setAutoExpandFolderName(String autoExpandFolderName) {
mAutoExpandFolderName = autoExpandFolderName;
public synchronized void setAutoExpandFolderName(String name) {
mAutoExpandFolderName = name;
}
public synchronized int getAccountNumber() {
@ -1289,11 +1292,11 @@ public class Account implements BaseAccount {
}
public LocalStore getLocalStore() throws MessagingException {
return Store.getLocalInstance(this, K9.app);
return LocalStore.getInstance(this, K9.app);
}
public Store getRemoteStore() throws MessagingException {
return Store.getRemoteInstance(this);
return RemoteStore.getInstance(this);
}
// It'd be great if this actually went into the store implementation
@ -1657,8 +1660,8 @@ public class Account implements BaseAccount {
return mInboxFolderName;
}
public void setInboxFolderName(String mInboxFolderName) {
this.mInboxFolderName = mInboxFolderName;
public void setInboxFolderName(String name) {
this.mInboxFolderName = name;
}
public synchronized boolean syncRemoteDeletions() {

View File

@ -4,7 +4,19 @@ package com.fsck.k9;
import android.text.util.Rfc822Tokenizer;
import android.widget.AutoCompleteTextView.Validator;
import java.util.regex.Pattern;
public class EmailAddressValidator implements Validator {
private static final Pattern EMAIL_ADDRESS_PATTERN = Pattern.compile(
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
"\\@" +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
"(" +
"\\." +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
")+"
);
public CharSequence fixText(CharSequence invalidText) {
return "";
}
@ -14,6 +26,6 @@ public class EmailAddressValidator implements Validator {
}
public boolean isValidAddressOnly(CharSequence text) {
return com.fsck.k9.helper.Regex.EMAIL_ADDRESS_PATTERN.matcher(text).matches();
return EMAIL_ADDRESS_PATTERN.matcher(text).matches();
}
}

View File

@ -36,9 +36,9 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.provider.UnreadWidgetProvider;
import com.fsck.k9.security.LocalKeyStore;
import com.fsck.k9.mail.ssl.LocalKeyStore;
import com.fsck.k9.service.BootReceiver;
import com.fsck.k9.service.MailService;
import com.fsck.k9.service.ShutdownReceiver;
@ -136,37 +136,6 @@ public class K9 extends Application {
*/
public static boolean DEBUG = false;
/**
* Should K-9 log the conversation it has over the wire with
* SMTP servers?
*/
public static boolean DEBUG_PROTOCOL_SMTP = true;
/**
* Should K-9 log the conversation it has over the wire with
* IMAP servers?
*/
public static boolean DEBUG_PROTOCOL_IMAP = true;
/**
* Should K-9 log the conversation it has over the wire with
* POP3 servers?
*/
public static boolean DEBUG_PROTOCOL_POP3 = true;
/**
* Should K-9 log the conversation it has over the wire with
* WebDAV servers?
*/
public static boolean DEBUG_PROTOCOL_WEBDAV = true;
/**
* If this is enabled than logging that normally hides sensitive information
* like passwords will show that information.

View File

@ -13,7 +13,8 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.preferences.Editor;
import com.fsck.k9.preferences.Storage;
@ -121,7 +122,12 @@ public class Preferences {
accountsInOrder.remove(account);
}
Store.removeAccount(account);
try {
RemoteStore.removeInstance(account);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to reset remote store for account " + account.getUuid(), e);
}
LocalStore.removeAccount(account);
account.deleteCertificates();
account.delete(this);

View File

@ -5,7 +5,6 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.EnumSet;
import java.util.HashSet;
@ -76,13 +75,12 @@ import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.activity.setup.WelcomeMessage;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.SizeFormatter;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mailstore.StorageManager;
import com.fsck.k9.mail.store.WebDavStore;
import com.fsck.k9.preferences.SettingsExporter;
import com.fsck.k9.preferences.SettingsImportExportException;
@ -771,7 +769,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
}
private void show(final Accounts activity, boolean restore) {
ServerSettings incoming = Store.decodeStoreUri(mAccount.getStoreUri());
ServerSettings incoming = RemoteStore.decodeStoreUri(mAccount.getStoreUri());
ServerSettings outgoing = Transport.decodeTransportUri(mAccount.getTransportUri());
/*
@ -992,9 +990,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
if (mIncomingPassword != null) {
// Set incoming server password
String storeUri = mAccount.getStoreUri();
ServerSettings incoming = Store.decodeStoreUri(storeUri);
ServerSettings incoming = RemoteStore.decodeStoreUri(storeUri);
ServerSettings newIncoming = incoming.newPassword(mIncomingPassword);
String newStoreUri = Store.createStoreUri(newIncoming);
String newStoreUri = RemoteStore.createStoreUri(newIncoming);
mAccount.setStoreUri(newStoreUri);
}

View File

@ -280,22 +280,16 @@ public class ChooseFolder extends K9ListActivity {
mAccount.getInboxFolderName().equalsIgnoreCase(name)))) {
continue;
}
try {
folder.refresh(prefs);
Folder.FolderClass fMode = folder.getDisplayClass();
Folder.FolderClass fMode = folder.getDisplayClass();
if ((aMode == Account.FolderMode.FIRST_CLASS &&
fMode != Folder.FolderClass.FIRST_CLASS) || (
aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
fMode != Folder.FolderClass.FIRST_CLASS &&
fMode != Folder.FolderClass.SECOND_CLASS) || (
aMode == Account.FolderMode.NOT_SECOND_CLASS &&
fMode == Folder.FolderClass.SECOND_CLASS)) {
continue;
}
} catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Couldn't get prefs to check for displayability of folder " +
folder.getName(), me);
if ((aMode == FolderMode.FIRST_CLASS &&
fMode != Folder.FolderClass.FIRST_CLASS) || (
aMode == FolderMode.FIRST_AND_SECOND_CLASS &&
fMode != Folder.FolderClass.FIRST_CLASS &&
fMode != Folder.FolderClass.SECOND_CLASS) || (
aMode == FolderMode.NOT_SECOND_CLASS &&
fMode == Folder.FolderClass.SECOND_CLASS)) {
continue;
}
if (folder.isInTopGroup()) {

View File

@ -55,8 +55,7 @@ import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.Searchfield;
@ -749,20 +748,14 @@ public class FolderList extends K9ListActivity {
Account.FolderMode aMode = account.getFolderDisplayMode();
Preferences prefs = Preferences.getPreferences(getApplication().getApplicationContext());
for (Folder folder : folders) {
try {
folder.refresh(prefs);
Folder.FolderClass fMode = folder.getDisplayClass();
Folder.FolderClass fMode = folder.getDisplayClass();
if ((aMode == Account.FolderMode.FIRST_CLASS && fMode != Folder.FolderClass.FIRST_CLASS)
|| (aMode == Account.FolderMode.FIRST_AND_SECOND_CLASS &&
fMode != Folder.FolderClass.FIRST_CLASS &&
fMode != Folder.FolderClass.SECOND_CLASS)
|| (aMode == Account.FolderMode.NOT_SECOND_CLASS && fMode == Folder.FolderClass.SECOND_CLASS)) {
continue;
}
} catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Couldn't get prefs to check for displayability of folder " + folder.getName(), me);
if ((aMode == FolderMode.FIRST_CLASS && fMode != Folder.FolderClass.FIRST_CLASS)
|| (aMode == FolderMode.FIRST_AND_SECOND_CLASS &&
fMode != Folder.FolderClass.FIRST_CLASS &&
fMode != Folder.FolderClass.SECOND_CLASS)
|| (aMode == FolderMode.NOT_SECOND_CLASS && fMode == Folder.FolderClass.SECOND_CLASS)) {
continue;
}
FolderInfoHolder holder = null;

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?
*/
public class InsertableHtmlContent implements Serializable {
class InsertableHtmlContent implements Serializable {
private static final long serialVersionUID = 2397327034L;
// Default to a headerInsertionPoint at the beginning of the message.
private int headerInsertionPoint = 0;

View File

@ -89,6 +89,7 @@ import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.fragment.ProgressDialogFragment;
import com.fsck.k9.helper.ContactItem;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.IdentityHelper;
import com.fsck.k9.helper.Utility;
@ -100,16 +101,17 @@ import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MessageExtractor;
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.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.internet.TextBodyBuilder;
import com.fsck.k9.mail.store.local.LocalAttachmentBody;
import com.fsck.k9.mail.store.local.TempFileBody;
import com.fsck.k9.mail.store.local.TempFileMessageBody;
import com.fsck.k9.mailstore.LocalAttachmentBody;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.TempFileBody;
import com.fsck.k9.mailstore.TempFileMessageBody;
import com.fsck.k9.view.MessageWebView;
import org.apache.james.mime4j.codec.EncoderUtil;
@ -435,17 +437,15 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* Get intent for composing a new message as a reply to the given message. If replyAll is true
* the function is reply all instead of simply reply.
* @param context
* @param account
* @param message
* @param replyAll
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static Intent getActionReplyIntent(
Context context,
Account account,
Message message,
boolean replyAll,
String messageBody) {
Context context,
LocalMessage message,
boolean replyAll,
String messageBody) {
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(EXTRA_MESSAGE_BODY, messageBody);
i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
@ -468,25 +468,22 @@ public class MessageCompose extends K9Activity implements OnClickListener,
*/
public static void actionReply(
Context context,
Account account,
Message message,
LocalMessage message,
boolean replyAll,
String messageBody) {
context.startActivity(getActionReplyIntent(context, account, message, replyAll, messageBody));
context.startActivity(getActionReplyIntent(context, message, replyAll, messageBody));
}
/**
* Compose a new message as a forward of the given message.
* @param context
* @param account
* @param message
* @param messageBody optional, for decrypted messages, null if it should be grabbed from the given message
*/
public static void actionForward(
Context context,
Account account,
Message message,
String messageBody) {
Context context,
LocalMessage message,
String messageBody) {
Intent i = new Intent(context, MessageCompose.class);
i.putExtra(EXTRA_MESSAGE_BODY, messageBody);
i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
@ -1339,7 +1336,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
*/
private MimeMessage createMessage(boolean isDraft) throws MessagingException {
MimeMessage message = new MimeMessage();
message.addSentDate(new Date());
message.addSentDate(new Date(), K9.hideTimeZone());
Address from = new Address(mIdentity.getEmail(), mIdentity.getName());
message.setFrom(from);
message.setRecipients(RecipientType.TO, getAddresses(mToView));
@ -1658,7 +1655,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
// First item is the body length. We use this to separate the composed reply from the quoted text.
if (tokenizer.hasMoreTokens()) {
String bodyLengthS = Utility.base64Decode(tokenizer.nextToken());
String bodyLengthS = Base64.decode(tokenizer.nextToken());
try {
identity.put(IdentityField.LENGTH, Integer.valueOf(bodyLengthS).toString());
} catch (Exception e) {
@ -1666,16 +1663,16 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
}
if (tokenizer.hasMoreTokens()) {
identity.put(IdentityField.SIGNATURE, Utility.base64Decode(tokenizer.nextToken()));
identity.put(IdentityField.SIGNATURE, Base64.decode(tokenizer.nextToken()));
}
if (tokenizer.hasMoreTokens()) {
identity.put(IdentityField.NAME, Utility.base64Decode(tokenizer.nextToken()));
identity.put(IdentityField.NAME, Base64.decode(tokenizer.nextToken()));
}
if (tokenizer.hasMoreTokens()) {
identity.put(IdentityField.EMAIL, Utility.base64Decode(tokenizer.nextToken()));
identity.put(IdentityField.EMAIL, Base64.decode(tokenizer.nextToken()));
}
if (tokenizer.hasMoreTokens()) {
identity.put(IdentityField.QUOTED_TEXT_MODE, Utility.base64Decode(tokenizer.nextToken()));
identity.put(IdentityField.QUOTED_TEXT_MODE, Base64.decode(tokenizer.nextToken()));
}
}
@ -2646,7 +2643,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
* @param message
* The source message used to populate the various text fields.
*/
private void processSourceMessage(Message message) {
private void processSourceMessage(LocalMessage message) {
try {
switch (mAction) {
case REPLY:
@ -2800,7 +2797,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
}
private void processDraftMessage(Message message) throws MessagingException {
private void processDraftMessage(LocalMessage message) throws MessagingException {
String showQuotedTextMode = "NONE";
mDraftId = MessagingController.getInstance(getApplication()).getId(message);
@ -2852,7 +2849,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
newIdentity.setSignature(k9identity.get(IdentityField.SIGNATURE));
mSignatureChanged = true;
} else {
newIdentity.setSignatureUse(message.getFolder().getAccount().getSignatureUse());
newIdentity.setSignatureUse(message.getFolder().getSignatureUse());
newIdentity.setSignature(mIdentity.getSignature());
}
@ -2962,7 +2959,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
Part part = MimeUtility.findFirstPartByMimeType(message, "text/html");
if (part != null) { // Shouldn't happen if we were the one who saved it.
mQuotedTextFormat = SimpleMessageFormat.HTML;
String text = MimeUtility.getTextFromPart(part);
String text = MessageExtractor.getTextFromPart(part);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
@ -3027,7 +3024,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
boolean viewMessageContent) throws MessagingException {
Part textPart = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (textPart != null) {
String text = MimeUtility.getTextFromPart(textPart);
String text = MessageExtractor.getTextFromPart(textPart);
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "Loading message with offset " + bodyOffset + ", length " + bodyLength + ". Text length is " + text.length() + ".");
}
@ -3230,7 +3227,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, HTML found.");
}
return MimeUtility.getTextFromPart(part);
return MessageExtractor.getTextFromPart(part);
}
part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
@ -3238,7 +3235,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: HTML requested, text found.");
}
return HtmlConverter.textToHtml(MimeUtility.getTextFromPart(part));
String text = MessageExtractor.getTextFromPart(part);
return HtmlConverter.textToHtml(text);
}
} else if (format == SimpleMessageFormat.TEXT) {
// Text takes precedence, then html.
@ -3247,7 +3245,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, text found.");
}
return MimeUtility.getTextFromPart(part);
return MessageExtractor.getTextFromPart(part);
}
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
@ -3255,7 +3253,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "getBodyTextFromMessage: Text requested, HTML found.");
}
return HtmlConverter.htmlToText(MimeUtility.getTextFromPart(part));
String text = MessageExtractor.getTextFromPart(part);
return HtmlConverter.htmlToText(text);
}
}
@ -3437,7 +3436,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
updateMessageFormat();
} else {
processSourceMessage(message);
processSourceMessage((LocalMessage) message);
mSourceProcessed = true;
}
}

View File

@ -1,7 +1,8 @@
package com.fsck.k9.activity;
import java.util.Date;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mailstore.LocalMessage;
public class MessageInfoHolder {
public String date;
@ -18,7 +19,7 @@ public class MessageInfoHolder {
public boolean forwarded;
public boolean flagged;
public boolean dirty;
public Message message;
public LocalMessage message;
public FolderInfoHolder folder;
public boolean selected;
public String account;
@ -31,7 +32,7 @@ public class MessageInfoHolder {
@Override
public boolean equals(Object o) {
if (o instanceof MessageInfoHolder == false) {
if (!(o instanceof MessageInfoHolder)) {
return false;
}
MessageInfoHolder other = (MessageInfoHolder)o;

View File

@ -42,8 +42,8 @@ import com.fsck.k9.fragment.MessageListFragment;
import com.fsck.k9.fragment.MessageListFragment.MessageListFragmentListener;
import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.fragment.MessageViewFragment.MessageViewFragmentListener;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mailstore.StorageManager;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchAccount;
import com.fsck.k9.search.SearchSpecification;
@ -1193,23 +1193,23 @@ public class MessageList extends K9Activity implements MessageListFragmentListen
}
@Override
public void onResendMessage(Message message) {
public void onResendMessage(LocalMessage message) {
MessageCompose.actionEditDraft(this, message.makeMessageReference());
}
@Override
public void onForward(Message message) {
MessageCompose.actionForward(this, message.getFolder().getAccount(), message, null);
public void onForward(LocalMessage message) {
MessageCompose.actionForward(this, message, null);
}
@Override
public void onReply(Message message) {
MessageCompose.actionReply(this, message.getFolder().getAccount(), message, false, null);
public void onReply(LocalMessage message) {
MessageCompose.actionReply(this, message, false, null);
}
@Override
public void onReplyAll(Message message) {
MessageCompose.actionReply(this, message.getFolder().getAccount(), message, true, null);
public void onReplyAll(LocalMessage message) {
MessageCompose.actionReply(this, message, true, null);
}
@Override
@ -1399,18 +1399,18 @@ public class MessageList extends K9Activity implements MessageListFragmentListen
}
@Override
public void onReply(Message message, PgpData pgpData) {
MessageCompose.actionReply(this, mAccount, message, false, pgpData.getDecryptedData());
public void onReply(LocalMessage message, PgpData pgpData) {
MessageCompose.actionReply(this, message, false, pgpData.getDecryptedData());
}
@Override
public void onReplyAll(Message message, PgpData pgpData) {
MessageCompose.actionReply(this, mAccount, message, true, pgpData.getDecryptedData());
public void onReplyAll(LocalMessage message, PgpData pgpData) {
MessageCompose.actionReply(this, message, true, pgpData.getDecryptedData());
}
@Override
public void onForward(Message mMessage, PgpData mPgpData) {
MessageCompose.actionForward(this, mAccount, mMessage, mPgpData.getDecryptedData());
public void onForward(LocalMessage mMessage, PgpData mPgpData) {
MessageCompose.actionForward(this, mMessage, mPgpData.getDecryptedData());
}
@Override

View File

@ -8,11 +8,11 @@ import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mail.filter.Base64;
import java.util.StringTokenizer;
@ -49,9 +49,9 @@ public class MessageReference implements Parcelable {
// Split the identity, stripping away the first two characters representing the version and delimiter.
StringTokenizer tokens = new StringTokenizer(identity.substring(2), IDENTITY_SEPARATOR, false);
if (tokens.countTokens() >= 3) {
accountUuid = Utility.base64Decode(tokens.nextToken());
folderName = Utility.base64Decode(tokens.nextToken());
uid = Utility.base64Decode(tokens.nextToken());
accountUuid = Base64.decode(tokens.nextToken());
folderName = Base64.decode(tokens.nextToken());
uid = Base64.decode(tokens.nextToken());
if (tokens.hasMoreTokens()) {
final String flagString = tokens.nextToken();
@ -80,11 +80,11 @@ public class MessageReference implements Parcelable {
refString.append(IDENTITY_VERSION_1);
refString.append(IDENTITY_SEPARATOR);
refString.append(Utility.base64Encode(accountUuid));
refString.append(Base64.encode(accountUuid));
refString.append(IDENTITY_SEPARATOR);
refString.append(Utility.base64Encode(folderName));
refString.append(Base64.encode(folderName));
refString.append(IDENTITY_SEPARATOR);
refString.append(Utility.base64Encode(uid));
refString.append(Base64.encode(uid));
if (flag != null) {
refString.append(IDENTITY_SEPARATOR);
refString.append(flag.name());
@ -128,13 +128,13 @@ public class MessageReference implements Parcelable {
'}';
}
public Message restoreToLocalMessage(Context context) {
public LocalMessage restoreToLocalMessage(Context context) {
try {
Account account = Preferences.getPreferences(context).getAccount(accountUuid);
if (account != null) {
Folder folder = account.getLocalStore().getFolder(folderName);
LocalFolder folder = account.getLocalStore().getFolder(folderName);
if (folder != null) {
Message message = folder.getMessage(uid);
LocalMessage message = folder.getMessage(uid);
if (message != null) {
return message;
} else {

View File

@ -1,14 +1,14 @@
package com.fsck.k9.mail.internet;
package com.fsck.k9.activity;
import android.text.TextUtils;
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.mail.Body;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.mail.internet.TextBody;
public class TextBodyBuilder {
class TextBodyBuilder {
private boolean mIncludeQuotedText = true;
private boolean mReplyAfterQuote = false;
private boolean mSignatureBeforeQuotedText = false;
@ -27,7 +27,7 @@ public class TextBodyBuilder {
/**
* Build the {@link Body} that will contain the text of the message.
*
* @return {@link TextBody} instance that contains the entered text and
* @return {@link com.fsck.k9.mail.internet.TextBody} instance that contains the entered text and
* possibly the quoted original message.
*/
public TextBody buildTextHtml() {

View File

@ -30,7 +30,7 @@ import android.widget.TextView;
* <li>{@link #actionUpgradeDatabases(Context, Intent)} will call {@link K9#areDatabasesUpToDate()}
* to check if we already know whether the databases have been upgraded.</li>
* <li>{@link K9#areDatabasesUpToDate()} will compare the last known database version stored in a
* {@link SharedPreferences} file to {@link com.fsck.k9.mail.store.local.LocalStore#DB_VERSION}. This
* {@link SharedPreferences} file to {@link com.fsck.k9.mailstore.LocalStore#DB_VERSION}. This
* is done as an optimization because it's faster than opening all of the accounts' databases
* one by one.</li>
* <li>If there was an error reading the cached database version or if it shows the databases need

View File

@ -36,8 +36,8 @@ import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.activity.ManageIdentities;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.StorageManager;
import com.fsck.k9.service.MailService;
import org.openintents.openpgp.util.OpenPgpListPreference;

View File

@ -38,9 +38,9 @@ import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mail.transport.SmtpTransport;
import com.fsck.k9.view.ClientCertificateSpinner;
import com.fsck.k9.view.ClientCertificateSpinner.OnClientCertificateChangedListener;
@ -421,7 +421,7 @@ public class AccountSetupBasics extends K9Activity
ConnectionSecurity.SSL_TLS_REQUIRED, authenticationType, user, password, clientCertificateAlias);
ServerSettings transportServer = new ServerSettings(SmtpTransport.TRANSPORT_TYPE, "mail." + domain, -1,
ConnectionSecurity.SSL_TLS_REQUIRED, authenticationType, user, password, clientCertificateAlias);
String storeUri = Store.createStoreUri(storeServer);
String storeUri = RemoteStore.createStoreUri(storeServer);
String transportUri = Transport.createTransportUri(transportServer);
mAccount.setStoreUri(storeUri);
mAccount.setTransportUri(transportUri);

View File

@ -27,6 +27,7 @@ import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mail.store.WebDavStore;
import com.fsck.k9.mail.store.ImapStore.ImapStoreSettings;
import com.fsck.k9.mail.store.WebDavStore.WebDavStoreSettings;
@ -163,7 +164,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
}
try {
ServerSettings settings = Store.decodeStoreUri(mAccount.getStoreUri());
ServerSettings settings = RemoteStore.decodeStoreUri(mAccount.getStoreUri());
if (savedInstanceState == null) {
// The first item is selected if settings.authenticationType is null or is not in mAuthTypeAdapter
@ -610,7 +611,7 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener
ServerSettings settings = new ServerSettings(mStoreType, host, port,
connectionSecurity, authType, username, password, clientCertificateAlias, extra);
mAccount.setStoreUri(Store.createStoreUri(settings));
mAccount.setStoreUri(RemoteStore.createStoreUri(settings));
mAccount.setCompression(Account.TYPE_MOBILE, mCompressionMobile.isChecked());
mAccount.setCompression(Account.TYPE_WIFI, mCompressionWifi.isChecked());

View File

@ -16,8 +16,8 @@ import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.service.MailService;
public class FolderSettings extends K9PreferenceActivity {

View File

@ -11,8 +11,8 @@ import android.support.v4.content.LocalBroadcastManager;
import com.fsck.k9.fragment.MessageListFragment;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.provider.EmailProvider;
/**
@ -123,13 +123,11 @@ public class EmailProviderCache {
}
}
public void hideMessages(List<Message> messages) {
public void hideMessages(List<LocalMessage> messages) {
synchronized (mHiddenMessageCache) {
for (Message message : messages) {
LocalMessage localMessage = (LocalMessage) message;
long messageId = localMessage.getId();
long folderId = ((LocalFolder) localMessage.getFolder()).getId();
mHiddenMessageCache.put(messageId, folderId);
for (LocalMessage message : messages) {
long messageId = message.getId();
mHiddenMessageCache.put(messageId, message.getFolder().getId());
}
}

View File

@ -61,6 +61,7 @@ import com.fsck.k9.activity.setup.AccountSetupIncoming;
import com.fsck.k9.activity.setup.AccountSetupOutgoing;
import com.fsck.k9.cache.EmailProviderCache;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.Address;
@ -78,16 +79,18 @@ import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.local.LocalStore.PendingCommand;
import com.fsck.k9.mailstore.MessageRemovalListener;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.mailstore.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mailstore.UnavailableStorageException;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.StatsColumns;
import com.fsck.k9.search.ConditionsTreeNode;
@ -212,7 +215,7 @@ public class MessagingController implements Runnable {
* Don't modify this list directly, but use {@link addMessage} and
* {@link removeMatchingMessage} instead.
*/
LinkedList<Message> messages;
LinkedList<LocalMessage> messages;
/**
* List of references for messages that the user is still to be notified of,
* but which don't fit into the inbox style anymore. It's sorted from newest
@ -236,7 +239,7 @@ public class MessagingController implements Runnable {
public NotificationData(int unread) {
unreadBeforeNotification = unread;
droppedMessages = new LinkedList<MessageReference>();
messages = new LinkedList<Message>();
messages = new LinkedList<LocalMessage>();
}
/**
@ -247,9 +250,9 @@ public class MessagingController implements Runnable {
*
* @param m The new message to add.
*/
public void addMessage(Message m) {
public void addMessage(LocalMessage m) {
while (messages.size() >= MAX_MESSAGES) {
Message dropped = messages.removeLast();
LocalMessage dropped = messages.removeLast();
droppedMessages.addFirst(dropped.makeMessageReference());
}
messages.addFirst(m);
@ -270,10 +273,10 @@ public class MessagingController implements Runnable {
}
}
for (Message message : messages) {
for (LocalMessage message : messages) {
if (message.makeMessageReference().equals(ref)) {
if (messages.remove(message) && !droppedMessages.isEmpty()) {
Message restoredMessage = droppedMessages.getFirst().restoreToLocalMessage(context);
LocalMessage restoredMessage = droppedMessages.getFirst().restoreToLocalMessage(context);
if (restoredMessage != null) {
messages.addLast(restoredMessage);
droppedMessages.removeFirst();
@ -291,7 +294,7 @@ public class MessagingController implements Runnable {
* List.
*/
public void supplyAllMessageRefs(List<MessageReference> refs) {
for (Message m : messages) {
for (LocalMessage m : messages) {
refs.add(m.makeMessageReference());
}
refs.addAll(droppedMessages);
@ -312,7 +315,7 @@ public class MessagingController implements Runnable {
private static final Set<Flag> SYNC_FLAGS = EnumSet.of(Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED);
private void suppressMessages(Account account, List<Message> messages) {
private void suppressMessages(Account account, List<LocalMessage> messages) {
EmailProviderCache cache = EmailProviderCache.getCache(account.getUuid(),
mApplication.getApplicationContext());
cache.hideMessages(messages);
@ -324,13 +327,11 @@ public class MessagingController implements Runnable {
cache.unhideMessages(messages);
}
private boolean isMessageSuppressed(Account account, Message message) {
LocalMessage localMessage = (LocalMessage) message;
String accountUuid = account.getUuid();
long messageId = localMessage.getId();
long folderId = ((LocalFolder) localMessage.getFolder()).getId();
private boolean isMessageSuppressed(LocalMessage message) {
long messageId = message.getId();
long folderId = message.getFolder().getId();
EmailProviderCache cache = EmailProviderCache.getCache(accountUuid,
EmailProviderCache cache = EmailProviderCache.getCache(message.getFolder().getAccountUuid(),
mApplication.getApplicationContext());
return cache.isMessageHidden(messageId, folderId);
}
@ -690,15 +691,16 @@ public class MessagingController implements Runnable {
}
// Collecting statistics of the search result
MessageRetrievalListener retrievalListener = new MessageRetrievalListener() {
MessageRetrievalListener retrievalListener = new MessageRetrievalListener<LocalMessage>() {
@Override
public void messageStarted(String message, int number, int ofTotal) {}
@Override
public void messagesFinished(int number) {}
@Override
public void messageFinished(Message message, int number, int ofTotal) {
if (!isMessageSuppressed(message.getFolder().getAccount(), message)) {
List<Message> messages = new ArrayList<Message>();
public void messageFinished(LocalMessage message, int number, int ofTotal) {
if (!isMessageSuppressed(message)) {
List<LocalMessage> messages = new ArrayList<LocalMessage>();
messages.add(message);
stats.unreadMessageCount += (!message.isSet(Flag.SEEN)) ? 1 : 0;
@ -762,7 +764,7 @@ public class MessagingController implements Runnable {
final Account acct = Preferences.getPreferences(mApplication.getApplicationContext()).getAccount(acctUuid);
if (listener != null) {
listener.remoteSearchStarted(acct, folderName);
listener.remoteSearchStarted(folderName);
}
List<Message> extraResults = new ArrayList<Message>();
@ -791,7 +793,7 @@ public class MessagingController implements Runnable {
messages.clear();
if (listener != null) {
listener.remoteSearchServerQueryComplete(acct, folderName, remoteMessages.size());
listener.remoteSearchServerQueryComplete(folderName, remoteMessages.size(), acct.getRemoteSearchNumResults());
}
Collections.sort(remoteMessages, new UidReverseComparator());
@ -811,13 +813,13 @@ public class MessagingController implements Runnable {
} else {
Log.e(K9.LOG_TAG, "Could not complete remote search", e);
if (listener != null) {
listener.remoteSearchFailed(acct, null, e.getMessage());
listener.remoteSearchFailed(null, e.getMessage());
}
addErrorMessage(acct, null, e);
}
} finally {
if (listener != null) {
listener.remoteSearchFinished(acct, folderName, 0, extraResults);
listener.remoteSearchFinished(folderName, 0, acct.getRemoteSearchNumResults(), extraResults);
}
}
@ -878,7 +880,7 @@ public class MessagingController implements Runnable {
}
if (listener != null) {
listener.remoteSearchAddMessage(remoteFolder.getAccount(), remoteFolder.getName(), localMsg, i, messages.size());
listener.remoteSearchAddMessage(remoteFolder.getName(), localMsg, i, messages.size());
}
}
}
@ -1455,7 +1457,7 @@ public class MessagingController implements Runnable {
}
}
private void fetchUnsyncedMessages(final Account account, final Folder remoteFolder,
private <T extends Message> void fetchUnsyncedMessages(final Account account, final Folder<T> remoteFolder,
final LocalFolder localFolder,
List<Message> unsyncedMessages,
final List<Message> smallMessages,
@ -1473,9 +1475,9 @@ public class MessagingController implements Runnable {
final List<Message> chunk = new ArrayList<Message>(UNSYNC_CHUNK_SIZE);
remoteFolder.fetch(unsyncedMessages, fp,
new MessageRetrievalListener() {
new MessageRetrievalListener<T>() {
@Override
public void messageFinished(Message message, int number, int ofTotal) {
public void messageFinished(T message, int number, int ofTotal) {
try {
String newPushState = remoteFolder.getNewPushState(localFolder.getPushState(), message);
if (newPushState != null) {
@ -1564,7 +1566,7 @@ public class MessagingController implements Runnable {
localFolder.appendMessages(messages);
for (final Message message : messages) {
final Message localMessage = localFolder.getMessage(message.getUid());
final LocalMessage localMessage = localFolder.getMessage(message.getUid());
syncFlags(localMessage, message);
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "About to notify listeners that we got a new unsynced message "
@ -1592,9 +1594,9 @@ public class MessagingController implements Runnable {
return true;
}
private void downloadSmallMessages(final Account account, final Folder remoteFolder,
private <T extends Message> void downloadSmallMessages(final Account account, final Folder<T> remoteFolder,
final LocalFolder localFolder,
List<Message> smallMessages,
List<T> smallMessages,
final AtomicInteger progress,
final int unreadBeforeStart,
final AtomicInteger newMessages,
@ -1608,9 +1610,9 @@ public class MessagingController implements Runnable {
Log.d(K9.LOG_TAG, "SYNC: Fetching small messages for folder " + folder);
remoteFolder.fetch(smallMessages,
fp, new MessageRetrievalListener() {
fp, new MessageRetrievalListener<T>() {
@Override
public void messageFinished(final Message message, int number, int ofTotal) {
public void messageFinished(final T message, int number, int ofTotal) {
try {
if (!shouldImportMessage(account, folder, message, progress, earliestDate)) {
@ -1620,7 +1622,7 @@ public class MessagingController implements Runnable {
}
// Store the updated message locally
final Message localMessage = localFolder.storeSmallMessage(message, new Runnable() {
final LocalMessage localMessage = localFolder.storeSmallMessage(message, new Runnable() {
@Override
public void run() {
progress.incrementAndGet();
@ -1671,9 +1673,9 @@ public class MessagingController implements Runnable {
private void downloadLargeMessages(final Account account, final Folder remoteFolder,
private <T extends Message> void downloadLargeMessages(final Account account, final Folder<T> remoteFolder,
final LocalFolder localFolder,
List<Message> largeMessages,
List<T> largeMessages,
final AtomicInteger progress,
final int unreadBeforeStart,
final AtomicInteger newMessages,
@ -1744,7 +1746,7 @@ public class MessagingController implements Runnable {
* right now, attachments will be left for later.
*/
Set<Part> viewables = MimeUtility.collectTextParts(message);
Set<Part> viewables = MessageExtractor.collectTextParts(message);
/*
* Now download the parts we're interested in storing.
@ -1768,7 +1770,7 @@ public class MessagingController implements Runnable {
// Update the listener with what we've found
progress.incrementAndGet();
// TODO do we need to re-fetch this here?
Message localMessage = localFolder.getMessage(message.getUid());
LocalMessage localMessage = localFolder.getMessage(message.getUid());
// Increment the number of "new messages" if the newly downloaded message is
// not marked as read.
@ -1821,11 +1823,11 @@ public class MessagingController implements Runnable {
remoteFolder.fetch(undeletedMessages, fp, null);
for (Message remoteMessage : syncFlagMessages) {
Message localMessage = localFolder.getMessage(remoteMessage.getUid());
LocalMessage localMessage = localFolder.getMessage(remoteMessage.getUid());
boolean messageChanged = syncFlags(localMessage, remoteMessage);
if (messageChanged) {
boolean shouldBeNotifiedOf = false;
if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(account, localMessage)) {
if (localMessage.isSet(Flag.DELETED) || isMessageSuppressed(localMessage)) {
for (MessagingListener l : getListeners()) {
l.synchronizeMailboxRemovedMessage(account, folder, localMessage);
}
@ -1859,13 +1861,13 @@ public class MessagingController implements Runnable {
}
}
private boolean syncFlags(Message localMessage, Message remoteMessage) throws MessagingException {
private boolean syncFlags(LocalMessage localMessage, Message remoteMessage) throws MessagingException {
boolean messageChanged = false;
if (localMessage == null || localMessage.isSet(Flag.DELETED)) {
return false;
}
if (remoteMessage.isSet(Flag.DELETED)) {
if (localMessage.getFolder().getAccount().syncRemoteDeletions()) {
if (localMessage.getFolder().syncRemoteDeletions()) {
localMessage.setFlag(Flag.DELETED, true);
messageChanged = true;
}
@ -2722,7 +2724,7 @@ public class MessagingController implements Runnable {
long nowTime = System.currentTimeMillis();
Date nowDate = new Date(nowTime);
message.setInternalDate(nowDate);
message.addSentDate(nowDate);
message.addSentDate(nowDate, K9.hideTimeZone());
message.setFrom(new Address(account.getEmail(), "K9mail internal"));
localFolder.appendMessages(Collections.singletonList(message));
@ -2857,7 +2859,7 @@ public class MessagingController implements Runnable {
* @param newState
* {@code true}, if the flag should be set. {@code false} if it should be removed.
*/
public void setFlag(Account account, String folderName, List<Message> messages, Flag flag,
public void setFlag(Account account, String folderName, List<? extends Message> messages, Flag flag,
boolean newState) {
// TODO: Put this into the background, but right now some callers depend on the message
// objects being modified right after this method returns.
@ -3197,7 +3199,7 @@ public class MessagingController implements Runnable {
try {
LocalStore localStore = account.getLocalStore();
List<Part> attachments = MimeUtility.collectAttachments(message);
List<Part> attachments = MessageExtractor.collectAttachments(message);
for (Part attachment : attachments) {
attachment.setBody(null);
}
@ -3775,7 +3777,7 @@ public class MessagingController implements Runnable {
}
}
public void moveMessages(final Account account, final String srcFolder,
final List<Message> messages, final String destFolder,
final List<LocalMessage> messages, final String destFolder,
final MessagingListener listener) {
suppressMessages(account, messages);
@ -3790,7 +3792,7 @@ public class MessagingController implements Runnable {
}
public void moveMessagesInThread(final Account account, final String srcFolder,
final List<Message> messages, final String destFolder) {
final List<LocalMessage> messages, final String destFolder) {
suppressMessages(account, messages);
@ -3808,14 +3810,14 @@ public class MessagingController implements Runnable {
});
}
public void moveMessage(final Account account, final String srcFolder, final Message message,
public void moveMessage(final Account account, final String srcFolder, final LocalMessage message,
final String destFolder, final MessagingListener listener) {
moveMessages(account, srcFolder, Collections.singletonList(message), destFolder, listener);
}
public void copyMessages(final Account account, final String srcFolder,
final List<Message> messages, final String destFolder,
final List<? extends Message> messages, final String destFolder,
final MessagingListener listener) {
putBackground("copyMessages", null, new Runnable() {
@ -3828,7 +3830,7 @@ public class MessagingController implements Runnable {
}
public void copyMessagesInThread(final Account account, final String srcFolder,
final List<Message> messages, final String destFolder) {
final List<? extends Message> messages, final String destFolder) {
putBackground("copyMessagesInThread", null, new Runnable() {
@Override
@ -3851,7 +3853,7 @@ public class MessagingController implements Runnable {
}
private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder,
final List<Message> inMessages, final String destFolder, final boolean isCopy,
final List<? extends Message> inMessages, final String destFolder, final boolean isCopy,
MessagingListener listener) {
try {
@ -3962,7 +3964,7 @@ public class MessagingController implements Runnable {
localFolder.open(Folder.OPEN_MODE_RW);
String uid = localFolder.getMessageUidById(id);
if (uid != null) {
Message message = localFolder.getMessage(uid);
LocalMessage message = localFolder.getMessage(uid);
if (message != null) {
deleteMessages(Collections.singletonList(message), null);
}
@ -3974,7 +3976,7 @@ public class MessagingController implements Runnable {
}
}
public void deleteThreads(final List<Message> messages) {
public void deleteThreads(final List<LocalMessage> messages) {
actOnMessages(messages, new MessageActor() {
@Override
@ -4006,7 +4008,7 @@ public class MessagingController implements Runnable {
}
}
public List<Message> collectMessagesInThreads(Account account, List<Message> messages)
public List<Message> collectMessagesInThreads(Account account, List<? extends Message> messages)
throws MessagingException {
LocalStore localStore = account.getLocalStore();
@ -4025,7 +4027,7 @@ public class MessagingController implements Runnable {
return messagesInThreads;
}
public void deleteMessages(final List<Message> messages, final MessagingListener listener) {
public void deleteMessages(final List<LocalMessage> messages, final MessagingListener listener) {
actOnMessages(messages, new MessageActor() {
@Override
@ -4244,13 +4246,12 @@ public class MessagingController implements Runnable {
try {
Intent msg = new Intent(Intent.ACTION_SEND);
String quotedText = null;
Part part = MimeUtility.findFirstPartByMimeType(message,
"text/plain");
Part part = MimeUtility.findFirstPartByMimeType(message, "text/plain");
if (part == null) {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
quotedText = MimeUtility.getTextFromPart(part);
quotedText = MessageExtractor.getTextFromPart(part);
}
if (quotedText != null) {
msg.putExtra(Intent.EXTRA_TEXT, quotedText);
@ -4393,7 +4394,6 @@ public class MessagingController implements Runnable {
Store localStore = account.getLocalStore();
for (final Folder folder : localStore.getPersonalNamespaces(false)) {
folder.open(Folder.OPEN_MODE_RW);
folder.refresh(prefs);
Folder.FolderClass fDisplayClass = folder.getDisplayClass();
Folder.FolderClass fSyncClass = folder.getSyncClass();
@ -4696,7 +4696,7 @@ public class MessagingController implements Runnable {
if (fromAddrs != null) {
isSelf = account.isAnIdentity(fromAddrs);
if (!isSelf && fromAddrs.length > 0) {
return fromAddrs[0].toFriendly(contacts).toString();
return MessageHelper.toFriendly(fromAddrs[0], contacts).toString();
}
}
@ -4706,7 +4706,7 @@ public class MessagingController implements Runnable {
if (rcpts != null && rcpts.length > 0) {
return context.getString(R.string.message_to_fmt,
rcpts[0].toFriendly(contacts).toString());
MessageHelper.toFriendly(rcpts[0], contacts).toString());
}
return context.getString(R.string.general_no_sender);
@ -4781,8 +4781,7 @@ public class MessagingController implements Runnable {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
private Message findNewestMessageForNotificationLocked(Context context,
Account account, NotificationData data) {
private LocalMessage findNewestMessageForNotificationLocked(Context context, NotificationData data) {
if (!data.messages.isEmpty()) {
return data.messages.getFirst();
}
@ -4798,7 +4797,7 @@ public class MessagingController implements Runnable {
* Creates a notification of a newly received message.
*/
private void notifyAccount(Context context, Account account,
Message message, int previousUnreadMessageCount) {
LocalMessage message, int previousUnreadMessageCount) {
final NotificationData data = getNotificationData(account, previousUnreadMessageCount);
synchronized (data) {
notifyAccountWithDataLocked(context, account, message, data);
@ -4809,12 +4808,12 @@ public class MessagingController implements Runnable {
private static final int NUM_SENDERS_IN_LOCK_SCREEN_NOTIFICATION = 5;
private void notifyAccountWithDataLocked(Context context, Account account,
Message message, NotificationData data) {
LocalMessage message, NotificationData data) {
boolean updateSilently = false;
if (message == null) {
/* this can happen if a message we previously notified for is read or deleted remotely */
message = findNewestMessageForNotificationLocked(context, account, data);
message = findNewestMessageForNotificationLocked(context, data);
updateSilently = true;
if (message == null) {
// seemingly both the message list as well as the overflow list is empty;
@ -5110,7 +5109,7 @@ public class MessagingController implements Runnable {
int unreadCount,
CharSequence accountDescription,
CharSequence formattedSender,
List<Message> messages) {
List<? extends Message> messages) {
if (!platformSupportsLockScreenNotifications()) {
return;
}
@ -5307,7 +5306,6 @@ public class MessagingController implements Runnable {
continue;
}
folder.open(Folder.OPEN_MODE_RW);
folder.refresh(prefs);
Folder.FolderClass fDisplayClass = folder.getDisplayClass();
Folder.FolderClass fPushClass = folder.getPushClass();
@ -5685,15 +5683,15 @@ public class MessagingController implements Runnable {
}
private void actOnMessages(List<Message> messages, MessageActor actor) {
private void actOnMessages(List<LocalMessage> messages, MessageActor actor) {
Map<Account, Map<Folder, List<Message>>> accountMap = new HashMap<Account, Map<Folder, List<Message>>>();
for (Message message : messages) {
for (LocalMessage message : messages) {
if ( message == null) {
continue;
}
Folder folder = message.getFolder();
Account account = folder.getAccount();
Account account = message.getAccount();
Map<Folder, List<Message>> folderMap = accountMap.get(account);
if (folderMap == null) {

View File

@ -11,8 +11,8 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.service.SleepService;
import java.util.List;

View File

@ -11,6 +11,7 @@ import com.fsck.k9.BaseAccount;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mailstore.LocalMessage;
/**
* Defines the interface that {@link MessagingController} will use to callback to requesters.
@ -42,10 +43,8 @@ public class MessagingListener {
public void listLocalMessagesStarted(Account account, String folder) {}
public void listLocalMessages(Account account, String folder, Message[] messages) {}
public void listLocalMessagesAddMessages(Account account, String folder,
List<Message> messages) {}
List<LocalMessage> messages) {}
public void listLocalMessagesUpdateMessage(Account account, String folder, Message message) {}
@ -156,10 +155,9 @@ public class MessagingListener {
/**
* Called when a remote search is started
*
* @param acct
* @param folder
*/
public void remoteSearchStarted(Account acct, String folder) {}
public void remoteSearchStarted(String folder) {}
/**
@ -167,35 +165,30 @@ public class MessagingListener {
*
* @param numResults
*/
public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) { }
public void remoteSearchServerQueryComplete(String folderName, int numResults, int maxResults) { }
/**
* Called when a new result message is available for a remote search
* Can assume headers have been downloaded, but potentially not body.
* @param account
* @param folder
* @param message
*/
public void remoteSearchAddMessage(Account account, String folder, Message message, int numDone, int numTotal) { }
public void remoteSearchAddMessage(String folder, Message message, int numDone, int numTotal) { }
/**
* Called when Remote Search is fully complete
*
* @param acct
* @param folder
* @param folder
* @param numResults
*/
public void remoteSearchFinished(Account acct, String folder, int numResults, List<Message> extraResults) {}
public void remoteSearchFinished(String folder, int numResults, int maxResults, List<Message> extraResults) {}
/**
* Called when there was a problem with a remote search operation.
*
* @param acct
* @param folder
* @param folder
* @param err
*/
public void remoteSearchFailed(Account acct, String folder, String err) { }
public void remoteSearchFailed(String folder, String err) { }
/**
* General notification messages subclasses can override to be notified that the controller

View File

@ -1,10 +1,8 @@
package com.fsck.k9.mail.store;
import com.fsck.k9.Account;
package com.fsck.k9.controller;
/**
* An {@link Account} is not
* {@link Account#isAvailable(android.content.Context)}.<br/>
* An {@link com.fsck.k9.Account} is not
* {@link com.fsck.k9.Account#isAvailable(android.content.Context)}.<br/>
* The operation may be retried later.
*/
public class UnavailableAccountException extends RuntimeException {

View File

@ -7,8 +7,10 @@ import java.util.regex.Pattern;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeUtility;
public class CryptoHelper {
public static Pattern PGP_MESSAGE =
@ -37,7 +39,7 @@ public class CryptoHelper {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
data = MimeUtility.getTextFromPart(part);
data = MessageExtractor.getTextFromPart(part);
}
} catch (MessagingException e) {
// guess not...
@ -60,7 +62,7 @@ public class CryptoHelper {
part = MimeUtility.findFirstPartByMimeType(message, "text/html");
}
if (part != null) {
data = MimeUtility.getTextFromPart(part);
data = MessageExtractor.getTextFromPart(part);
}
} catch (MessagingException e) {
// guess not...

View File

@ -88,8 +88,9 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.SpecialColumns;
@ -427,7 +428,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* Relevant messages for the current context when we have to remember the chosen messages
* between user interactions (e.g. selecting a folder for move operation).
*/
private List<Message> mActiveMessages;
private List<LocalMessage> mActiveMessages;
/* package visibility for faster inner class access */
MessageHelper mMessageHelper;
@ -1035,7 +1036,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return null;
}
private Folder getFolderById(Account account, long folderId) {
private LocalFolder getFolderById(Account account, long folderId) {
try {
LocalStore localStore = account.getLocalStore();
LocalFolder localFolder = localStore.getFolderById(folderId);
@ -1189,19 +1190,19 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
}
public void onReply(Message message) {
public void onReply(LocalMessage message) {
mFragmentListener.onReply(message);
}
public void onReplyAll(Message message) {
public void onReplyAll(LocalMessage message) {
mFragmentListener.onReplyAll(message);
}
public void onForward(Message message) {
public void onForward(LocalMessage message) {
mFragmentListener.onForward(message);
}
public void onResendMessage(Message message) {
public void onResendMessage(LocalMessage message) {
mFragmentListener.onResendMessage(message);
}
@ -1309,11 +1310,11 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
changeSort(sorts[curIndex]);
}
private void onDelete(Message message) {
private void onDelete(LocalMessage message) {
onDelete(Collections.singletonList(message));
}
private void onDelete(List<Message> messages) {
private void onDelete(List<LocalMessage> messages) {
if (K9.confirmDelete()) {
// remember the message selection for #onCreateDialog(int)
mActiveMessages = messages;
@ -1323,7 +1324,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
}
private void onDeleteConfirmed(List<Message> messages) {
private void onDeleteConfirmed(List<LocalMessage> messages) {
if (mThreadedList) {
mController.deleteThreads(messages);
} else {
@ -1345,15 +1346,14 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER);
final List<Message> messages = mActiveMessages;
final List<LocalMessage> messages = mActiveMessages;
if (destFolderName != null) {
mActiveMessages = null; // don't need it any more
if (messages.size() > 0) {
Account account = messages.get(0).getFolder().getAccount();
account.setLastSelectedFolderName(destFolderName);
messages.get(0).getFolder().setLastSelectedFolderName(destFolderName);
}
switch (requestCode) {
@ -1510,23 +1510,19 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
break;
}
case R.id.reply: {
Message message = getMessageAtPosition(adapterPosition);
onReply(message);
onReply(getMessageAtPosition(adapterPosition));
break;
}
case R.id.reply_all: {
Message message = getMessageAtPosition(adapterPosition);
onReplyAll(message);
onReplyAll(getMessageAtPosition(adapterPosition));
break;
}
case R.id.forward: {
Message message = getMessageAtPosition(adapterPosition);
onForward(message);
onForward(getMessageAtPosition(adapterPosition));
break;
}
case R.id.send_again: {
Message message = getMessageAtPosition(adapterPosition);
onResendMessage(message);
onResendMessage(getMessageAtPosition(adapterPosition));
mSelectedCount = 0;
break;
}
@ -1539,7 +1535,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
break;
}
case R.id.delete: {
Message message = getMessageAtPosition(adapterPosition);
LocalMessage message = getMessageAtPosition(adapterPosition);
onDelete(message);
break;
}
@ -1562,23 +1558,19 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
// only if the account supports this
case R.id.archive: {
Message message = getMessageAtPosition(adapterPosition);
onArchive(message);
onArchive(getMessageAtPosition(adapterPosition));
break;
}
case R.id.spam: {
Message message = getMessageAtPosition(adapterPosition);
onSpam(message);
onSpam(getMessageAtPosition(adapterPosition));
break;
}
case R.id.move: {
Message message = getMessageAtPosition(adapterPosition);
onMove(message);
onMove(getMessageAtPosition(adapterPosition));
break;
}
case R.id.copy: {
Message message = getMessageAtPosition(adapterPosition);
onCopy(message);
onCopy(getMessageAtPosition(adapterPosition));
break;
}
}
@ -1711,7 +1703,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
class MessageListActivityListener extends ActivityListener {
@Override
public void remoteSearchFailed(Account acct, String folder, final String err) {
public void remoteSearchFailed(String folder, final String err) {
mHandler.post(new Runnable() {
@Override
public void run() {
@ -1725,7 +1717,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
@Override
public void remoteSearchStarted(Account acct, String folder) {
public void remoteSearchStarted(String folder) {
mHandler.progress(true);
mHandler.updateFooter(mContext.getString(R.string.remote_search_sending_query));
}
@ -1736,12 +1728,12 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
@Override
public void remoteSearchFinished(Account acct, String folder, int numResults, List<Message> extraResults) {
public void remoteSearchFinished(String folder, int numResults, int maxResults, List<Message> extraResults) {
mHandler.progress(false);
mHandler.remoteSearchFinished();
mExtraSearchResults = extraResults;
if (extraResults != null && extraResults.size() > 0) {
mHandler.updateFooter(String.format(mContext.getString(R.string.load_more_messages_fmt), acct.getRemoteSearchNumResults()));
mHandler.updateFooter(String.format(mContext.getString(R.string.load_more_messages_fmt), maxResults));
} else {
mHandler.updateFooter("");
}
@ -1750,11 +1742,11 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
@Override
public void remoteSearchServerQueryComplete(Account account, String folderName, int numResults) {
public void remoteSearchServerQueryComplete(String folderName, int numResults, int maxResults) {
mHandler.progress(true);
if (account != null && account.getRemoteSearchNumResults() != 0 && numResults > account.getRemoteSearchNumResults()) {
if (maxResults != 0 && numResults > maxResults) {
mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading_limited,
account.getRemoteSearchNumResults(), numResults));
maxResults, numResults));
} else {
mHandler.updateFooter(mContext.getString(R.string.remote_search_downloading, numResults));
}
@ -2416,7 +2408,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
computeBatchDirection();
}
private void onMove(Message message) {
private void onMove(LocalMessage message) {
onMove(Collections.singletonList(message));
}
@ -2426,7 +2418,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param messages
* Never {@code null}.
*/
private void onMove(List<Message> messages) {
private void onMove(List<LocalMessage> messages) {
if (!checkCopyOrMovePossible(messages, FolderOperation.MOVE)) {
return;
}
@ -2440,12 +2432,13 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
folder = null;
}
Account account = messages.get(0).getFolder().getAccount();
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, account, folder, messages);
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder,
messages.get(0).getFolder().getAccountUuid(), null,
messages);
}
private void onCopy(Message message) {
private void onCopy(LocalMessage message) {
onCopy(Collections.singletonList(message));
}
@ -2455,7 +2448,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param messages
* Never {@code null}.
*/
private void onCopy(List<Message> messages) {
private void onCopy(List<LocalMessage> messages) {
if (!checkCopyOrMovePossible(messages, FolderOperation.COPY)) {
return;
}
@ -2469,7 +2462,10 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
folder = null;
}
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, mAccount, folder, messages);
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder,
messages.get(0).getFolder().getAccountUuid(),
null,
messages);
}
/**
@ -2486,12 +2482,13 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
*
* @see #startActivityForResult(Intent, int)
*/
private void displayFolderChoice(int requestCode, Account account, Folder folder,
List<Message> messages) {
private void displayFolderChoice(int requestCode, Folder folder,
String accountUuid, String lastSelectedFolderName,
List<LocalMessage> messages) {
Intent intent = new Intent(getActivity(), ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, account.getUuid());
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, account.getLastSelectedFolderName());
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, accountUuid);
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, lastSelectedFolderName);
if (folder == null) {
intent.putExtra(ChooseFolder.EXTRA_SHOW_CURRENT, "yes");
@ -2504,14 +2501,14 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
startActivityForResult(intent, requestCode);
}
private void onArchive(final Message message) {
private void onArchive(final LocalMessage message) {
onArchive(Collections.singletonList(message));
}
private void onArchive(final List<Message> messages) {
Map<Account, List<Message>> messagesByAccount = groupMessagesByAccount(messages);
private void onArchive(final List<LocalMessage> messages) {
Map<Account, List<LocalMessage>> messagesByAccount = groupMessagesByAccount(messages);
for (Entry<Account, List<Message>> entry : messagesByAccount.entrySet()) {
for (Entry<Account, List<LocalMessage>> entry : messagesByAccount.entrySet()) {
Account account = entry.getKey();
String archiveFolder = account.getArchiveFolderName();
@ -2521,14 +2518,14 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
}
private Map<Account, List<Message>> groupMessagesByAccount(final List<Message> messages) {
Map<Account, List<Message>> messagesByAccount = new HashMap<Account, List<Message>>();
for (Message message : messages) {
Account account = message.getFolder().getAccount();
private Map<Account, List<LocalMessage>> groupMessagesByAccount(final List<LocalMessage> messages) {
Map<Account, List<LocalMessage>> messagesByAccount = new HashMap<Account, List<LocalMessage>>();
for (LocalMessage message : messages) {
Account account = message.getAccount();
List<Message> msgList = messagesByAccount.get(account);
List<LocalMessage> msgList = messagesByAccount.get(account);
if (msgList == null) {
msgList = new ArrayList<Message>();
msgList = new ArrayList<LocalMessage>();
messagesByAccount.put(account, msgList);
}
@ -2537,7 +2534,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return messagesByAccount;
}
private void onSpam(Message message) {
private void onSpam(LocalMessage message) {
onSpam(Collections.singletonList(message));
}
@ -2547,7 +2544,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param messages
* The messages to move to the spam folder. Never {@code null}.
*/
private void onSpam(List<Message> messages) {
private void onSpam(List<LocalMessage> messages) {
if (K9.confirmSpam()) {
// remember the message selection for #onCreateDialog(int)
mActiveMessages = messages;
@ -2557,10 +2554,10 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
}
private void onSpamConfirmed(List<Message> messages) {
Map<Account, List<Message>> messagesByAccount = groupMessagesByAccount(messages);
private void onSpamConfirmed(List<LocalMessage> messages) {
Map<Account, List<LocalMessage>> messagesByAccount = groupMessagesByAccount(messages);
for (Entry<Account, List<Message>> entry : messagesByAccount.entrySet()) {
for (Entry<Account, List<LocalMessage>> entry : messagesByAccount.entrySet()) {
Account account = entry.getKey();
String spamFolder = account.getSpamFolderName();
@ -2584,7 +2581,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
*
* @return {@code true}, if operation is possible.
*/
private boolean checkCopyOrMovePossible(final List<Message> messages,
private boolean checkCopyOrMovePossible(final List<LocalMessage> messages,
final FolderOperation operation) {
if (messages.isEmpty()) {
@ -2592,11 +2589,11 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
boolean first = true;
for (final Message message : messages) {
for (final LocalMessage message : messages) {
if (first) {
first = false;
// account check
final Account account = message.getFolder().getAccount();
final Account account = message.getAccount();
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) ||
(operation == FolderOperation.COPY && !mController.isCopyCapable(account))) {
return false;
@ -2622,7 +2619,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param destination
* The name of the destination folder. Never {@code null}.
*/
private void copy(List<Message> messages, final String destination) {
private void copy(List<LocalMessage> messages, final String destination) {
copyOrMove(messages, destination, FolderOperation.COPY);
}
@ -2634,7 +2631,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param destination
* The name of the destination folder. Never {@code null}.
*/
private void move(List<Message> messages, final String destination) {
private void move(List<LocalMessage> messages, final String destination) {
copyOrMove(messages, destination, FolderOperation.MOVE);
}
@ -2650,12 +2647,12 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
* @param operation
* Specifies what operation to perform. Never {@code null}.
*/
private void copyOrMove(List<Message> messages, final String destination,
private void copyOrMove(List<LocalMessage> messages, final String destination,
final FolderOperation operation) {
Map<String, List<Message>> folderMap = new HashMap<String, List<Message>>();
Map<String, List<LocalMessage>> folderMap = new HashMap<String, List<LocalMessage>>();
for (Message message : messages) {
for (LocalMessage message : messages) {
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) ||
(operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
@ -2674,19 +2671,19 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
continue;
}
List<Message> outMessages = folderMap.get(folderName);
List<LocalMessage> outMessages = folderMap.get(folderName);
if (outMessages == null) {
outMessages = new ArrayList<Message>();
outMessages = new ArrayList<LocalMessage>();
folderMap.put(folderName, outMessages);
}
outMessages.add(message);
}
for (Map.Entry<String, List<Message>> entry : folderMap.entrySet()) {
for (Map.Entry<String, List<LocalMessage>> entry : folderMap.entrySet()) {
String folderName = entry.getKey();
List<Message> outMessages = entry.getValue();
Account account = outMessages.get(0).getFolder().getAccount();
List<LocalMessage> outMessages = entry.getValue();
Account account = outMessages.get(0).getAccount();
if (operation == FolderOperation.MOVE) {
if (mThreadedList) {
@ -2859,7 +2856,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
*/
switch (item.getItemId()) {
case R.id.delete: {
List<Message> messages = getCheckedMessages();
List<LocalMessage> messages = getCheckedMessages();
onDelete(messages);
mSelectedCount = 0;
break;
@ -2887,26 +2884,22 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
// only if the account supports this
case R.id.archive: {
List<Message> messages = getCheckedMessages();
onArchive(messages);
onArchive(getCheckedMessages());
mSelectedCount = 0;
break;
}
case R.id.spam: {
List<Message> messages = getCheckedMessages();
onSpam(messages);
onSpam(getCheckedMessages());
mSelectedCount = 0;
break;
}
case R.id.move: {
List<Message> messages = getCheckedMessages();
onMove(messages);
onMove(getCheckedMessages());
mSelectedCount = 0;
break;
}
case R.id.copy: {
List<Message> messages = getCheckedMessages();
onCopy(messages);
onCopy(getCheckedMessages());
mSelectedCount = 0;
break;
}
@ -2987,7 +2980,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
final Folder remoteFolder = mCurrentFolder.folder;
remoteFolder.close();
// Send a remoteSearchFinished() message for good measure.
mListener.remoteSearchFinished(searchAccount, mCurrentFolder.name, 0, null);
mListener.remoteSearchFinished(mCurrentFolder.name, 0, searchAccount.getRemoteSearchNumResults(), null);
} catch (Exception e) {
// Since the user is going back, log and squash any exceptions.
Log.e(K9.LOG_TAG, "Could not abort remote search before going back", e);
@ -3116,10 +3109,10 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
void setMessageListProgress(int level);
void showThread(Account account, String folderName, long rootId);
void showMoreFromSameSender(String senderAddress);
void onResendMessage(Message message);
void onForward(Message message);
void onReply(Message message);
void onReplyAll(Message message);
void onResendMessage(LocalMessage message);
void onForward(LocalMessage message);
void onReply(LocalMessage message);
void onReplyAll(LocalMessage message);
void openMessage(MessageReference messageReference);
void setMessageListTitle(String title);
void setMessageListSubTitle(String subTitle);
@ -3135,7 +3128,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
changeSort(mSortType);
}
private Message getSelectedMessage() {
private LocalMessage getSelectedMessage() {
int listViewPosition = mListView.getSelectedItemPosition();
int adapterPosition = listViewToAdapterPosition(listViewPosition);
@ -3158,7 +3151,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return AdapterView.INVALID_POSITION;
}
private Message getMessageAtPosition(int adapterPosition) {
private LocalMessage getMessageAtPosition(int adapterPosition) {
if (adapterPosition == AdapterView.INVALID_POSITION) {
return null;
}
@ -3168,7 +3161,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
Account account = getAccountFromCursor(cursor);
long folderId = cursor.getLong(FOLDER_ID_COLUMN);
Folder folder = getFolderById(account, folderId);
LocalFolder folder = getFolderById(account, folderId);
try {
return folder.getMessage(uid);
@ -3179,14 +3172,14 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
return null;
}
private List<Message> getCheckedMessages() {
List<Message> messages = new ArrayList<Message>(mSelected.size());
private List<LocalMessage> getCheckedMessages() {
List<LocalMessage> messages = new ArrayList<LocalMessage>(mSelected.size());
for (int position = 0, end = mAdapter.getCount(); position < end; position++) {
Cursor cursor = (Cursor) mAdapter.getItem(position);
long uniqueId = cursor.getLong(mUniqueIdColumn);
if (mSelected.contains(uniqueId)) {
Message message = getMessageAtPosition(position);
LocalMessage message = getMessageAtPosition(position);
if (message != null) {
messages.add(message);
}
@ -3197,7 +3190,7 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
public void onDelete() {
Message message = getSelectedMessage();
LocalMessage message = getSelectedMessage();
if (message != null) {
onDelete(Collections.singletonList(message));
}
@ -3227,21 +3220,21 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
}
public void onMove() {
Message message = getSelectedMessage();
LocalMessage message = getSelectedMessage();
if (message != null) {
onMove(message);
}
}
public void onArchive() {
Message message = getSelectedMessage();
LocalMessage message = getSelectedMessage();
if (message != null) {
onArchive(message);
}
}
public void onCopy() {
Message message = getSelectedMessage();
LocalMessage message = getSelectedMessage();
if (message != null) {
onCopy(message);
}

View File

@ -38,7 +38,7 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
import com.fsck.k9.view.MessageHeader;
@ -75,7 +75,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
private PgpData mPgpData;
private Account mAccount;
private MessageReference mMessageReference;
private Message mMessage;
private LocalMessage mMessage;
private MessagingController mController;
private Listener mListener = new Listener();
private MessageViewHandler mHandler = new MessageViewHandler();
@ -311,7 +311,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
// Disable the delete button after it's tapped (to try to prevent
// accidental clicks)
mFragmentListener.disableDeleteAction();
Message messageToDelete = mMessage;
LocalMessage messageToDelete = mMessage;
mFragmentListener.showNextMessageOrReturn();
mController.deleteMessages(Collections.singletonList(messageToDelete), null);
}
@ -341,7 +341,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
private void refileMessage(String dstFolder) {
String srcFolder = mMessageReference.folderName;
Message messageToMove = mMessage;
LocalMessage messageToMove = mMessage;
mFragmentListener.showNextMessageOrReturn();
mController.moveMessage(mAccount, srcFolder, messageToMove, dstFolder, null);
}
@ -576,7 +576,8 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
@Override
public void loadMessageForViewBodyAvailable(final Account account, String folder,
String uid, final Message message) {
if (!mMessageReference.uid.equals(uid) ||
if (!(message instanceof LocalMessage) ||
!mMessageReference.uid.equals(uid) ||
!mMessageReference.folderName.equals(folder) ||
!mMessageReference.accountUuid.equals(account.getUuid())) {
return;
@ -586,7 +587,7 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
@Override
public void run() {
try {
mMessage = message;
mMessage = (LocalMessage) message;
mMessageView.setMessage(account, (LocalMessage) message, mPgpData,
mController, mListener);
mFragmentListener.updateMenu();
@ -845,10 +846,10 @@ public class MessageViewFragment extends Fragment implements OnClickListener,
}
public interface MessageViewFragmentListener {
public void onForward(Message mMessage, PgpData mPgpData);
public void onForward(LocalMessage mMessage, PgpData mPgpData);
public void disableDeleteAction();
public void onReplyAll(Message mMessage, PgpData mPgpData);
public void onReply(Message mMessage, PgpData mPgpData);
public void onReplyAll(LocalMessage mMessage, PgpData mPgpData);
public void onReply(LocalMessage mMessage, PgpData mPgpData);
public void displayMessageSubject(String title);
public void setProgress(boolean b);
public void showNextMessageOrReturn();

View File

@ -4,6 +4,7 @@ import android.text.*;
import android.text.Html.TagHandler;
import android.util.Log;
import com.fsck.k9.K9;
import org.xml.sax.XMLReader;
import java.io.IOException;
@ -13,11 +14,75 @@ import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Contains common routines to convert html to text and vice versa.
*/
public class HtmlConverter {
/* This comprises most common used Unicode characters allowed in IRI
* as detailed in RFC 3987.
* Specifically, those two byte Unicode characters are not included.
*/
private static final String GOOD_IRI_CHAR =
"a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
/**
* Goegular expression to match all IANA top-level domains for WEB_URL.
* List accurate as of 2011/01/12. List taken from:
* http://data.iana.org/TLD/tlds-alpha-by-domain.txt
* This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
*/
private static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
"(?:"
+ "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ "|(?:biz|b[abdefghijmnorstvwyz])"
+ "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+ "|d[ejkmoz]"
+ "|(?:edu|e[cegrstu])"
+ "|f[ijkmor]"
+ "|(?:gov|g[abdefghilmnpqrstuwy])"
+ "|h[kmnrtu]"
+ "|(?:info|int|i[delmnoqrst])"
+ "|(?:jobs|j[emop])"
+ "|k[eghimnprwyz]"
+ "|l[abcikrstuvy]"
+ "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+ "|(?:name|net|n[acefgilopruz])"
+ "|(?:org|om)"
+ "|(?:pro|p[aefghklmnrstwy])"
+ "|qa"
+ "|r[eosuw]"
+ "|s[abcdeghijklmnortuvyz]"
+ "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+ "|u[agksyz]"
+ "|v[aceginu]"
+ "|w[fs]"
+ "|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah)"
+ "|y[et]"
+ "|z[amw]))";
private static final String BITCOIN_URI_PATTERN =
"bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?";
/**
* Regular expression pattern to match most part of RFC 3987
* Internationalized URLs, aka IRIs. Commonly used Unicode characters are
* added.
*/
private static final Pattern WEB_URL_PATTERN = Pattern.compile(
"((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+ "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
+ TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
+ "|(?:(?:25[0-5]|2[0-4]" // or ip address
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
+ "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ "|[1-9][0-9]|[0-9])))"
+ "(?:\\:\\d{1,5})?)" // plus option port number
+ "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ "(?:\\b|$)"); // and finally, a word boundary or end of
/**
* When generating previews, Spannable objects that can't be converted into a String are
* represented as 0xfffc. When displayed, these show up as undisplayed squares. These constants
@ -381,9 +446,9 @@ public class HtmlConverter {
* @param outputBuffer Buffer to append linked text to.
*/
protected static void linkifyText(final String text, final StringBuffer outputBuffer) {
String prepared = text.replaceAll(Regex.BITCOIN_URI_PATTERN, "<a href=\"$0\">$0</a>");
String prepared = text.replaceAll(BITCOIN_URI_PATTERN, "<a href=\"$0\">$0</a>");
Matcher m = Regex.WEB_URL_PATTERN.matcher(prepared);
Matcher m = WEB_URL_PATTERN.matcher(prepared);
while (m.find()) {
int start = m.start();
if (start == 0 || (start != 0 && prepared.charAt(start - 1) != '@')) {

View File

@ -1,7 +1,11 @@
package com.fsck.k9.helper;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import com.fsck.k9.Account;
@ -11,11 +15,23 @@ import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.MessageInfoHolder;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mailstore.LocalMessage;
public class MessageHelper {
/**
* If the number of addresses exceeds this value the addresses aren't
* resolved to the names of Android contacts.
*
* <p>
* TODO: This number was chosen arbitrarily and should be determined by
* performance tests.
* </p>
*
* @see #toFriendly(Address[], com.fsck.k9.helper.Contacts)
*/
private static final int TOO_MANY_ADDRESSES = 50;
private static MessageHelper sInstance;
@ -32,8 +48,10 @@ public class MessageHelper {
mContext = context;
}
public void populate(final MessageInfoHolder target, final Message message,
final FolderInfoHolder folder, final Account account) {
public void populate(final MessageInfoHolder target,
final LocalMessage message,
final FolderInfoHolder folder,
Account account) {
final Contacts contactHelper = K9.showContactName() ? Contacts.getInstance(mContext) : null;
try {
target.message = message;
@ -53,11 +71,11 @@ public class MessageHelper {
Address[] addrs = message.getFrom();
if (addrs.length > 0 && account.isAnIdentity(addrs[0])) {
CharSequence to = Address.toFriendly(message .getRecipients(RecipientType.TO), contactHelper);
CharSequence to = toFriendly(message.getRecipients(RecipientType.TO), contactHelper);
target.compareCounterparty = to.toString();
target.sender = new SpannableStringBuilder(mContext.getString(R.string.message_to_label)).append(to);
} else {
target.sender = Address.toFriendly(addrs, contactHelper);
target.sender = toFriendly(addrs, contactHelper);
target.compareCounterparty = target.sender.toString();
}
@ -68,14 +86,9 @@ public class MessageHelper {
target.senderAddress = target.compareCounterparty;
}
target.uid = message.getUid();
target.account = account.getUuid();
target.uri = "email://messages/" + account.getAccountNumber() + "/" + message.getFolder().getName() + "/" + message.getUid();
target.account = message.getFolder().getAccountUuid();
target.uri = message.getUri();
} catch (MessagingException me) {
Log.w(K9.LOG_TAG, "Unable to load message info", me);
}
@ -86,11 +99,11 @@ public class MessageHelper {
CharSequence displayName;
if (fromAddrs.length > 0 && account.isAnIdentity(fromAddrs[0])) {
CharSequence to = Address.toFriendly(toAddrs, contactHelper);
CharSequence to = toFriendly(toAddrs, contactHelper);
displayName = new SpannableStringBuilder(
mContext.getString(R.string.message_to_label)).append(to);
} else {
displayName = Address.toFriendly(fromAddrs, contactHelper);
displayName = toFriendly(fromAddrs, contactHelper);
}
return displayName;
@ -104,4 +117,69 @@ public class MessageHelper {
}
return false;
}
/**
* Returns the name of the contact this email address belongs to if
* the {@link Contacts contacts} parameter is not {@code null} and a
* contact is found. Otherwise the personal portion of the {@link Address}
* is returned. If that isn't available either, the email address is
* returned.
*
* @param address An {@link com.fsck.k9.mail.Address}
* @param contacts A {@link Contacts} instance or {@code null}.
* @return A "friendly" name for this {@link Address}.
*/
public static CharSequence toFriendly(Address address, Contacts contacts) {
return toFriendly(address,contacts,
K9.showCorrespondentNames(),
K9.changeContactNameColor(),
K9.getContactNameColor());
}
public static CharSequence toFriendly(Address[] addresses, Contacts contacts) {
if (addresses == null) {
return null;
}
if (addresses.length >= TOO_MANY_ADDRESSES) {
// Don't look up contacts if the number of addresses is very high.
contacts = null;
}
SpannableStringBuilder sb = new SpannableStringBuilder();
for (int i = 0; i < addresses.length; i++) {
sb.append(toFriendly(addresses[i], contacts));
if (i < addresses.length - 1) {
sb.append(',');
}
}
return sb;
}
/* package, for testing */ static CharSequence toFriendly(Address address, Contacts contacts,
boolean showCorrespondentNames,
boolean changeContactNameColor,
int contactNameColor) {
if (!showCorrespondentNames) {
return address.getAddress();
} else if (contacts != null) {
final String name = contacts.getNameForAddress(address.getAddress());
// TODO: The results should probably be cached for performance reasons.
if (name != null) {
if (changeContactNameColor) {
final SpannableString coloredName = new SpannableString(name);
coloredName.setSpan(new ForegroundColorSpan(contactNameColor),
0,
coloredName.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
return coloredName;
} else {
return name;
}
}
}
return (!TextUtils.isEmpty(address.getPersonal())) ? address.getPersonal() : address.getAddress();
}
}

View File

@ -1,109 +0,0 @@
/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Imported from AOSP on 2011-01-12 by JRV.
* Domain patterns updated from IANA on 2010-01-12
*
*
*/
package com.fsck.k9.helper;
import java.util.regex.Pattern;
/**
* Commonly used regular expression patterns.
*/
public class Regex {
/**
* Goegular expression to match all IANA top-level domains for WEB_URL.
* List accurate as of 2011/01/12. List taken from:
* http://data.iana.org/TLD/tlds-alpha-by-domain.txt
* This pattern is auto-generated by frameworks/base/common/tools/make-iana-tld-pattern.py
*/
public static final String TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL =
"(?:"
+ "(?:aero|arpa|asia|a[cdefgilmnoqrstuwxz])"
+ "|(?:biz|b[abdefghijmnorstvwyz])"
+ "|(?:cat|com|coop|c[acdfghiklmnoruvxyz])"
+ "|d[ejkmoz]"
+ "|(?:edu|e[cegrstu])"
+ "|f[ijkmor]"
+ "|(?:gov|g[abdefghilmnpqrstuwy])"
+ "|h[kmnrtu]"
+ "|(?:info|int|i[delmnoqrst])"
+ "|(?:jobs|j[emop])"
+ "|k[eghimnprwyz]"
+ "|l[abcikrstuvy]"
+ "|(?:mil|mobi|museum|m[acdeghklmnopqrstuvwxyz])"
+ "|(?:name|net|n[acefgilopruz])"
+ "|(?:org|om)"
+ "|(?:pro|p[aefghklmnrstwy])"
+ "|qa"
+ "|r[eosuw]"
+ "|s[abcdeghijklmnortuvyz]"
+ "|(?:tel|travel|t[cdfghjklmnoprtvwz])"
+ "|u[agksyz]"
+ "|v[aceginu]"
+ "|w[fs]"
+ "|(?:xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-80akhbyknj4f|xn\\-\\-9t4b11yi5a|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah)"
+ "|y[et]"
+ "|z[amw]))";
/* This comprises most common used Unicode characters allowed in IRI
* as detailed in RFC 3987.
* Specifically, those two byte Unicode characters are not included.
*/
public static final String GOOD_IRI_CHAR =
"a-zA-Z0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF";
/**
* Regular expression pattern to match most part of RFC 3987
* Internationalized URLs, aka IRIs. Commonly used Unicode characters are
* added.
*/
public static final Pattern WEB_URL_PATTERN = Pattern.compile(
"((?:(http|https|Http|Https|rtsp|Rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)"
+ "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_"
+ "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?"
+ "((?:(?:[" + GOOD_IRI_CHAR + "][" + GOOD_IRI_CHAR + "\\-]{0,64}\\.)+" // named host
+ TOP_LEVEL_DOMAIN_STR_FOR_WEB_URL
+ "|(?:(?:25[0-5]|2[0-4]" // or ip address
+ "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(?:25[0-5]|2[0-4][0-9]"
+ "|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1]"
+ "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}"
+ "|[1-9][0-9]|[0-9])))"
+ "(?:\\:\\d{1,5})?)" // plus option port number
+ "(\\/(?:(?:[" + GOOD_IRI_CHAR + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus option query params
+ "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?"
+ "(?:\\b|$)"); // and finally, a word boundary or end of
// input. This is to stop foo.sure from
// matching as foo.su
public static final Pattern EMAIL_ADDRESS_PATTERN
= Pattern.compile(
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
"\\@" +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
"(" +
"\\." +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
")+"
);
public static final String BITCOIN_URI_PATTERN =
"bitcoin:[1-9a-km-zA-HJ-NP-Z]{27,34}(\\?[a-zA-Z0-9$\\-_.+!*'(),%:@&=]*)?";
}

View File

@ -15,7 +15,6 @@ import android.widget.EditText;
import android.widget.TextView;
import com.fsck.k9.K9;
import com.fsck.k9.mail.filter.Base64;
import java.nio.charset.Charset;
import java.util.ArrayList;
@ -91,22 +90,6 @@ public class Utility {
return TextUtils.join(String.valueOf(separator), parts);
}
public static String base64Decode(String encoded) {
if (encoded == null) {
return null;
}
byte[] decoded = new Base64().decode(encoded.getBytes());
return new String(decoded);
}
public static String base64Encode(String s) {
if (s == null) {
return s;
}
byte[] encoded = new Base64().encode(s.getBytes());
return new String(encoded);
}
public static boolean requiredFieldValid(TextView view) {
return view.getText() != null && view.getText().length() > 0;
}
@ -130,48 +113,6 @@ public class Utility {
return false;
}
private static final Pattern ATOM = Pattern.compile("^(?:[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]|\\s)+$");
/**
* Quote a string, if necessary, based upon the definition of an "atom," as defined by RFC2822
* (http://tools.ietf.org/html/rfc2822#section-3.2.4). Strings that consist purely of atoms are
* left unquoted; anything else is returned as a quoted string.
* @param text String to quote.
* @return Possibly quoted string.
*/
public static String quoteAtoms(final String text) {
if (ATOM.matcher(text).matches()) {
return text;
} else {
return quoteString(text);
}
}
/**
* Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the
* double quote character to start and end if it's not already there.
* sample -> "sample"
* "sample" -> "sample"
* ""sample"" -> "sample"
* "sample"" -> "sample"
* sa"mp"le -> "sa"mp"le"
* "sa"mp"le" -> "sa"mp"le"
* (empty string) -> ""
* " -> ""
* @param s
* @return
*/
public static String quoteString(String s) {
if (s == null) {
return null;
}
if (!s.matches("^\".*\"$")) {
return "\"" + s + "\"";
} else {
return s;
}
}
/**
* A fast version of URLDecoder.decode() that works only with UTF-8 and does only two
* allocations. This version is around 3x as fast as the standard one and I'm using it

View File

@ -3,6 +3,7 @@ package com.fsck.k9.mail;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.codec.EncoderUtil;
@ -10,33 +11,15 @@ import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.mime4j.field.address.AddressBuilder;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.text.util.Rfc822Token;
import android.text.util.Rfc822Tokenizer;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.Utility;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class Address {
/**
* If the number of addresses exceeds this value the addresses aren't
* resolved to the names of Android contacts.
*
* <p>
* TODO: This number was chosen arbitrarily and should be determined by
* performance tests.
* </p>
*
* @see Address#toFriendly(Address[], Contacts)
*/
private static final int TOO_MANY_ADDRESSES = 50;
private static final Pattern ATOM = Pattern.compile("^(?:[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]|\\s)+$");
/**
* Immutable empty {@link Address} array
@ -162,12 +145,12 @@ public class Address {
Mailbox mailbox = (Mailbox)address;
addresses.add(new Address(mailbox.getLocalPart() + "@" + mailbox.getDomain(), mailbox.getName(), false));
} else {
Log.e(K9.LOG_TAG, "Unknown address type from Mime4J: "
Log.e(LOG_TAG, "Unknown address type from Mime4J: "
+ address.getClass().toString());
}
}
} catch (MimeException pe) {
Log.e(K9.LOG_TAG, "MimeException in Address.parse()", pe);
Log.e(LOG_TAG, "MimeException in Address.parse()", pe);
//but we do an silent failover : we just use the given string as name with empty address
addresses.add(new Address(null, addressList, false));
}
@ -198,7 +181,7 @@ public class Address {
@Override
public String toString() {
if (!TextUtils.isEmpty(mPersonal)) {
return Utility.quoteAtoms(mPersonal) + " <" + mAddress + ">";
return quoteAtoms(mPersonal) + " <" + mAddress + ">";
} else {
return mAddress;
}
@ -233,77 +216,6 @@ public class Address {
return sb.toString();
}
/**
* Returns either the personal portion of the Address or the address portion if the personal
* is not available.
* @return
*/
public CharSequence toFriendly() {
return toFriendly((Contacts)null);
}
/**
* Returns the name of the contact this email address belongs to if
* the {@link Contacts contacts} parameter is not {@code null} and a
* contact is found. Otherwise the personal portion of the {@link Address}
* is returned. If that isn't available either, the email address is
* returned.
*
* @param contacts
* A {@link Contacts} instance or {@code null}.
* @return
* A "friendly" name for this {@link Address}.
*/
public CharSequence toFriendly(final Contacts contacts) {
if (!K9.showCorrespondentNames()) {
return mAddress;
} else if (contacts != null) {
final String name = contacts.getNameForAddress(mAddress);
// TODO: The results should probably be cached for performance reasons.
if (name != null) {
if (K9.changeContactNameColor()) {
final SpannableString coloredName = new SpannableString(name);
coloredName.setSpan(new ForegroundColorSpan(K9.getContactNameColor()),
0,
coloredName.length(),
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
);
return coloredName;
} else {
return name;
}
}
}
return (!TextUtils.isEmpty(mPersonal)) ? mPersonal : mAddress;
}
public static CharSequence toFriendly(Address[] addresses) {
return toFriendly(addresses, null);
}
public static CharSequence toFriendly(Address[] addresses, Contacts contacts) {
if (addresses == null) {
return null;
}
if (addresses.length >= TOO_MANY_ADDRESSES) {
// Don't look up contacts if the number of addresses is very high.
contacts = null;
}
SpannableStringBuilder sb = new SpannableStringBuilder();
for (int i = 0; i < addresses.length; i++) {
sb.append(addresses[i].toFriendly(contacts));
if (i < addresses.length - 1) {
sb.append(',');
}
}
return sb;
}
/**
* Unpacks an address list previously packed with packAddressList()
@ -368,4 +280,44 @@ public class Address {
}
return sb.toString();
}
/**
* Quote a string, if necessary, based upon the definition of an "atom," as defined by RFC2822
* (http://tools.ietf.org/html/rfc2822#section-3.2.4). Strings that consist purely of atoms are
* left unquoted; anything else is returned as a quoted string.
* @param text String to quote.
* @return Possibly quoted string.
*/
public static String quoteAtoms(final String text) {
if (ATOM.matcher(text).matches()) {
return text;
} else {
return quoteString(text);
}
}
/**
* Ensures that the given string starts and ends with the double quote character. The string is not modified in any way except to add the
* double quote character to start and end if it's not already there.
* sample -> "sample"
* "sample" -> "sample"
* ""sample"" -> "sample"
* "sample"" -> "sample"
* sa"mp"le -> "sa"mp"le"
* "sa"mp"le" -> "sa"mp"le"
* (empty string) -> ""
* " -> ""
* @param s
* @return
*/
private static String quoteString(String s) {
if (s == null) {
return null;
}
if (!s.matches("^\".*\"$")) {
return "\"" + s + "\"";
} else {
return s;
}
}
}

View File

@ -5,8 +5,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import com.fsck.k9.mail.store.UnavailableStorageException;
public interface Body {
/**
* Returns the raw data of the body, without transfer encoding etc applied.
@ -18,7 +16,7 @@ public interface Body {
/**
* Sets the content transfer encoding (7bit, 8bit, quoted-printable or base64).
*/
public void setEncoding(String encoding) throws UnavailableStorageException, MessagingException;
public void setEncoding(String encoding) throws MessagingException;
/**
* Writes the body's data to the given {@link OutputStream}.

View File

@ -1,6 +1,6 @@
package com.fsck.k9.mail;
public abstract class BodyPart implements Part {
private Multipart mParent;
@ -13,7 +13,4 @@ public abstract class BodyPart implements Part {
}
public abstract void setEncoding(String encoding) throws MessagingException;
@Override
public abstract void setUsing7bitTransport() throws MessagingException;
}

View File

@ -1,21 +1,15 @@
package com.fsck.k9.mail;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.controller.MessageRetrievalListener;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public abstract class Folder {
protected final Account mAccount;
public abstract class Folder<T extends Message> {
private String status = null;
private long lastChecked = 0;
private long lastPush = 0;
@ -32,10 +26,6 @@ public abstract class Folder {
HOLDS_FOLDERS, HOLDS_MESSAGES,
}
protected Folder(Account account) {
mAccount = account;
}
/**
* Forces an open of the MailProvider. If the provider is already open this
* function returns without doing anything.
@ -83,7 +73,7 @@ public abstract class Folder {
public abstract int getUnreadMessageCount() throws MessagingException;
public abstract int getFlaggedMessageCount() throws MessagingException;
public abstract Message getMessage(String uid) throws MessagingException;
public abstract T getMessage(String uid) throws MessagingException;
/**
* Fetch the shells of messages between a range of UIDs and after a given date.
@ -94,7 +84,7 @@ public abstract class Folder {
* @return List of messages
* @throws MessagingException
*/
public abstract List<? extends Message> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener) throws MessagingException;
public abstract List<T> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener<T> listener) throws MessagingException;
/**
* Fetches the given list of messages. The specified listener is notified as
@ -104,13 +94,13 @@ public abstract class Folder {
* @param listener Listener to notify as we download messages.
* @return List of messages
*/
public abstract List<? extends Message> getMessages(MessageRetrievalListener listener) throws MessagingException;
public abstract List<T> getMessages(MessageRetrievalListener<T> listener) throws MessagingException;
public List<? extends Message> getMessages(MessageRetrievalListener listener, boolean includeDeleted) throws MessagingException {
public List<T> getMessages(MessageRetrievalListener<T> listener, boolean includeDeleted) throws MessagingException {
return getMessages(listener);
}
public abstract List<? extends Message> getMessages(String[] uids, MessageRetrievalListener listener)
public abstract List<T> getMessages(String[] uids, MessageRetrievalListener<T> listener)
throws MessagingException;
public abstract Map<String, String> appendMessages(List<? extends Message> messages) throws MessagingException;
@ -149,15 +139,15 @@ public abstract class Folder {
* @throws MessagingException
*/
public abstract void fetch(List<? extends Message> messages, FetchProfile fp,
MessageRetrievalListener listener) throws MessagingException;
MessageRetrievalListener<T> listener) throws MessagingException;
public void fetchPart(Message message, Part part,
MessageRetrievalListener listener) throws MessagingException {
MessageRetrievalListener<T> listener) throws MessagingException {
// This is causing trouble. Disabled for now. See issue 1733
//throw new RuntimeException("fetchPart() not implemented.");
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "fetchPart() not implemented.");
if (K9MailLib.isDebug())
Log.d(LOG_TAG, "fetchPart() not implemented.");
}
public abstract void delete(boolean recurse) throws MessagingException;
@ -225,10 +215,6 @@ public abstract class Folder {
return getSyncClass();
}
public void refresh(Preferences preferences) throws MessagingException {
}
public boolean isInTopGroup() {
return false;
}
@ -241,10 +227,6 @@ public abstract class Folder {
this.status = status;
}
public Account getAccount() {
return mAccount;
}
public List<Message> search(String queryString, final Set<Flag> requiredFlags, final Set<Flag> forbiddenFlags)
throws MessagingException {
throw new MessagingException("K-9 does not support searches on this folder type");

View File

@ -0,0 +1,44 @@
package com.fsck.k9.mail;
import com.fsck.k9.K9;
public class K9MailLib {
private K9MailLib() {}
public static final String LOG_TAG = K9.LOG_TAG;
public static final int PUSH_WAKE_LOCK_TIMEOUT = K9.PUSH_WAKE_LOCK_TIMEOUT;
public static final String IDENTITY_HEADER = K9.IDENTITY_HEADER;
/**
* Should K-9 log the conversation it has over the wire with
* SMTP servers?
*/
public static boolean DEBUG_PROTOCOL_SMTP = true;
/**
* Should K-9 log the conversation it has over the wire with
* IMAP servers?
*/
public static boolean DEBUG_PROTOCOL_IMAP = true;
/**
* Should K-9 log the conversation it has over the wire with
* POP3 servers?
*/
public static boolean DEBUG_PROTOCOL_POP3 = true;
/**
* Should K-9 log the conversation it has over the wire with
* WebDAV servers?
*/
public static boolean DEBUG_PROTOCOL_WEBDAV = true;
public static boolean isDebug() {
return K9.DEBUG;
}
public static boolean isDebugSensitive() {
return K9.DEBUG_SENSITIVE;
}
}

View File

@ -2,26 +2,20 @@
package com.fsck.k9.mail;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.mail.filter.CountingOutputStream;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.store.UnavailableStorageException;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public abstract class Message implements Part, CompositeBody {
private MessageReference mReference = null;
public enum RecipientType {
TO, CC, BCC,
}
@ -54,9 +48,8 @@ public abstract class Message implements Part, CompositeBody {
return false;
}
Message other = (Message)o;
return (mUid.equals(other.getUid())
&& mFolder.getName().equals(other.getFolder().getName())
&& mFolder.getAccount().getUuid().equals(other.getFolder().getAccount().getUuid()));
return (getUid().equals(other.getUid())
&& getFolder().getName().equals(other.getFolder().getName()));
}
@Override
@ -65,7 +58,6 @@ public abstract class Message implements Part, CompositeBody {
int result = 1;
result = MULTIPLIER * result + mFolder.getName().hashCode();
result = MULTIPLIER * result + mFolder.getAccount().getUuid().hashCode();
result = MULTIPLIER * result + mUid.hashCode();
return result;
}
@ -75,7 +67,6 @@ public abstract class Message implements Part, CompositeBody {
}
public void setUid(String uid) {
mReference = null;
this.mUid = uid;
}
@ -97,7 +88,7 @@ public abstract class Message implements Part, CompositeBody {
public abstract Date getSentDate();
public abstract void setSentDate(Date sentDate) throws MessagingException;
public abstract void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException;
public abstract Address[] getRecipients(RecipientType type) throws MessagingException;
@ -126,28 +117,7 @@ public abstract class Message implements Part, CompositeBody {
public abstract void setReferences(String references) throws MessagingException;
@Override
public abstract Body getBody();
@Override
public abstract String getContentType() throws MessagingException;
@Override
public abstract void addHeader(String name, String value) throws MessagingException;
@Override
public abstract void setHeader(String name, String value) throws MessagingException;
@Override
public abstract String[] getHeader(String name) throws MessagingException;
public abstract Set<String> getHeaderNames() throws UnavailableStorageException;
@Override
public abstract void removeHeader(String name) throws MessagingException;
@Override
public abstract void setBody(Body body) throws MessagingException;
public abstract Set<String> getHeaderNames() throws MessagingException;
public abstract long getId();
@ -249,20 +219,10 @@ public abstract class Message implements Part, CompositeBody {
public void destroy() throws MessagingException {}
@Override
public abstract void setEncoding(String encoding) throws UnavailableStorageException, MessagingException;
public abstract void setEncoding(String encoding) throws MessagingException;
public abstract void setCharset(String charset) throws MessagingException;
public MessageReference makeMessageReference() {
if (mReference == null) {
mReference = new MessageReference();
mReference.accountUuid = getFolder().getAccount().getUuid();
mReference.folderName = getFolder().getName();
mReference.uid = mUid;
}
return mReference;
}
public long calculateSize() {
try {
@ -272,9 +232,9 @@ public abstract class Message implements Part, CompositeBody {
eolOut.flush();
return out.getCount();
} catch (IOException e) {
Log.e(K9.LOG_TAG, "Failed to calculate a message size", e);
Log.e(LOG_TAG, "Failed to calculate a message size", e);
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Failed to calculate a message size", e);
Log.e(LOG_TAG, "Failed to calculate a message size", e);
}
return 0;
}
@ -282,14 +242,12 @@ public abstract class Message implements Part, CompositeBody {
/**
* Copy the contents of this object into another {@code Message} object.
*
* @param destination
* The {@code Message} object to receive the contents of this instance.
* @param destination The {@code Message} object to receive the contents of this instance.
*/
protected void copy(Message destination) {
destination.mUid = mUid;
destination.mInternalDate = mInternalDate;
destination.mFolder = mFolder;
destination.mReference = mReference;
// mFlags contents can change during the object lifetime, so copy the Set
destination.mFlags = EnumSet.copyOf(mFlags);
@ -308,6 +266,5 @@ public abstract class Message implements Part, CompositeBody {
*/
@Override
public abstract Message clone();
@Override
public abstract void setUsing7bitTransport() throws MessagingException;
}

View File

@ -1,12 +1,11 @@
package com.fsck.k9.controller;
package com.fsck.k9.mail;
import com.fsck.k9.mail.Message;
public interface MessageRetrievalListener {
public interface MessageRetrievalListener<T extends Message> {
public void messageStarted(String uid, int number, int ofTotal);
public void messageFinished(Message message, int number, int ofTotal);
public void messageFinished(T message, int number, int ofTotal);
/**
* FIXME <strong>this method is almost never invoked by various Stores! Don't rely on it unless fixed!!</strong>

View File

@ -7,7 +7,7 @@ import java.util.List;
import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.CharsetSupport;
import com.fsck.k9.mail.internet.TextBody;
public abstract class Multipart implements CompositeBody {
@ -64,7 +64,7 @@ public abstract class Multipart implements CompositeBody {
BodyPart part = mParts.get(0);
Body body = part.getBody();
if (body instanceof TextBody) {
MimeUtility.setCharset(charset, part);
CharsetSupport.setCharset(charset, part);
((TextBody)body).setCharset(charset);
}
}

View File

@ -5,29 +5,29 @@ import java.io.IOException;
import java.io.OutputStream;
public interface Part {
public void addHeader(String name, String value) throws MessagingException;
void addHeader(String name, String value) throws MessagingException;
public void removeHeader(String name) throws MessagingException;
void removeHeader(String name) throws MessagingException;
public void setHeader(String name, String value) throws MessagingException;
void setHeader(String name, String value) throws MessagingException;
public Body getBody();
Body getBody();
public String getContentType() throws MessagingException;
String getContentType() throws MessagingException;
public String getDisposition() throws MessagingException;
String getDisposition() throws MessagingException;
public String getContentId() throws MessagingException;
String getContentId() throws MessagingException;
public String[] getHeader(String name) throws MessagingException;
String[] getHeader(String name) throws MessagingException;
public boolean isMimeType(String mimeType) throws MessagingException;
boolean isMimeType(String mimeType) throws MessagingException;
public String getMimeType() throws MessagingException;
String getMimeType() throws MessagingException;
public void setBody(Body body) throws MessagingException;
void setBody(Body body) throws MessagingException;
public void writeTo(OutputStream out) throws IOException, MessagingException;
void writeTo(OutputStream out) throws IOException, MessagingException;
/**
* Called just prior to transmission, once the type of transport is known to
@ -41,5 +41,5 @@ public interface Part {
*
*/
//TODO perhaps it would be clearer to use a flag "force7bit" in writeTo
public abstract void setUsing7bitTransport() throws MessagingException;
void setUsing7bitTransport() throws MessagingException;
}

View File

@ -3,7 +3,6 @@ package com.fsck.k9.mail;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import com.fsck.k9.Account;
/**
* This is an abstraction to get rid of the store- and transport-specific URIs.
@ -13,8 +12,8 @@ import com.fsck.k9.Account;
* store/transport URIs altogether.
* </p>
*
* @see Account#getStoreUri()
* @see Account#getTransportUri()
* @see com.fsck.k9.mail.store.StoreConfig#getStoreUri()
* @see com.fsck.k9.mail.store.StoreConfig#getTransportUri()
*/
public class ServerSettings {
/**

View File

@ -1,24 +1,10 @@
package com.fsck.k9.mail;
import java.util.HashMap;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import android.app.Application;
import android.content.Context;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.WebDavStore;
/**
* Store is the access point for an email message store. It's location can be
@ -29,191 +15,6 @@ import com.fsck.k9.mail.store.WebDavStore;
* making as few network connections as possible.
*/
public abstract class Store {
protected static final int SOCKET_CONNECT_TIMEOUT = 30000;
protected static final int SOCKET_READ_TIMEOUT = 60000;
/**
* Remote stores indexed by Uri.
*/
private static Map<String, Store> sStores = new HashMap<String, Store>();
/**
* Local stores indexed by UUID because the Uri may change due to migration to/from SD-card.
*/
private static ConcurrentMap<String, Store> sLocalStores = new ConcurrentHashMap<String, Store>();
/**
* Lock objects indexed by account UUID.
*
* @see #getLocalInstance(Account, Application)
*/
private static ConcurrentMap<String, Object> sAccountLocks = new ConcurrentHashMap<String, Object>();
/**
* Get an instance of a remote mail store.
*/
public synchronized static Store getRemoteInstance(Account account) throws MessagingException {
String uri = account.getStoreUri();
if (uri.startsWith("local")) {
throw new RuntimeException("Asked to get non-local Store object but given LocalStore URI");
}
Store store = sStores.get(uri);
if (store == null) {
if (uri.startsWith("imap")) {
store = new ImapStore(account);
} else if (uri.startsWith("pop3")) {
store = new Pop3Store(account);
} else if (uri.startsWith("webdav")) {
store = new WebDavStore(account);
}
if (store != null) {
sStores.put(uri, store);
}
}
if (store == null) {
throw new MessagingException("Unable to locate an applicable Store for " + uri);
}
return store;
}
/**
* Get an instance of a local mail store.
*
* @throws UnavailableStorageException
* if not {@link StorageProvider#isReady(Context)}
*/
public static LocalStore getLocalInstance(Account account, Application application)
throws MessagingException {
String accountUuid = account.getUuid();
// Create new per-account lock object if necessary
sAccountLocks.putIfAbsent(accountUuid, new Object());
// Get the account's lock object
Object lock = sAccountLocks.get(accountUuid);
// Use per-account locks so DatabaseUpgradeService always knows which account database is
// currently upgraded.
synchronized (lock) {
Store store = sLocalStores.get(accountUuid);
if (store == null) {
// Creating a LocalStore instance will create or upgrade the database if
// necessary. This could take some time.
store = new LocalStore(account, application);
sLocalStores.put(accountUuid, store);
}
return (LocalStore) store;
}
}
public static void removeAccount(Account account) {
try {
removeRemoteInstance(account);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to reset remote store for account " + account.getUuid(), e);
}
try {
removeLocalInstance(account);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to reset local store for account " + account.getUuid(), e);
}
}
/**
* Release reference to a local mail store instance.
*
* @param account
* {@link Account} instance that is used to get the local mail store instance.
*/
private static void removeLocalInstance(Account account) {
String accountUuid = account.getUuid();
sLocalStores.remove(accountUuid);
}
/**
* Release reference to a remote mail store instance.
*
* @param account
* {@link Account} instance that is used to get the remote mail store instance.
*/
private synchronized static void removeRemoteInstance(Account account) {
String uri = account.getStoreUri();
if (uri.startsWith("local")) {
throw new RuntimeException("Asked to get non-local Store object but given " +
"LocalStore URI");
}
sStores.remove(uri);
}
/**
* Decodes the contents of store-specific URIs and puts them into a {@link ServerSettings}
* object.
*
* @param uri
* the store-specific URI to decode
*
* @return A {@link ServerSettings} object holding the settings contained in the URI.
*
* @see ImapStore#decodeUri(String)
* @see Pop3Store#decodeUri(String)
* @see WebDavStore#decodeUri(String)
*/
public static ServerSettings decodeStoreUri(String uri) {
if (uri.startsWith("imap")) {
return ImapStore.decodeUri(uri);
} else if (uri.startsWith("pop3")) {
return Pop3Store.decodeUri(uri);
} else if (uri.startsWith("webdav")) {
return WebDavStore.decodeUri(uri);
} else {
throw new IllegalArgumentException("Not a valid store URI");
}
}
/**
* Creates a store URI from the information supplied in the {@link ServerSettings} object.
*
* @param server
* The {@link ServerSettings} object that holds the server settings.
*
* @return A store URI that holds the same information as the {@code server} parameter.
*
* @see ImapStore#createUri(ServerSettings)
* @see Pop3Store#createUri(ServerSettings)
* @see WebDavStore#createUri(ServerSettings)
*/
public static String createStoreUri(ServerSettings server) {
if (ImapStore.STORE_TYPE.equals(server.type)) {
return ImapStore.createUri(server);
} else if (Pop3Store.STORE_TYPE.equals(server.type)) {
return Pop3Store.createUri(server);
} else if (WebDavStore.STORE_TYPE.equals(server.type)) {
return WebDavStore.createUri(server);
} else {
throw new IllegalArgumentException("Not a valid store URI");
}
}
protected final Account mAccount;
protected Store(Account account) {
mAccount = account;
}
public abstract Folder getFolder(String name);
public abstract List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException;
@ -244,14 +45,25 @@ public abstract class Store {
return true;
}
public void sendMessages(List<? extends Message> messages) throws MessagingException {
}
public void sendMessages(List<? extends Message> messages) throws MessagingException { }
public Pusher getPusher(PushReceiver receiver) {
return null;
}
public Account getAccount() {
return mAccount;
protected static String decodeUtf8(String s) {
try {
return URLDecoder.decode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not found");
}
}
protected static String encodeUtf8(String s) {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not found");
}
}
}

View File

@ -1,22 +1,26 @@
package com.fsck.k9.mail;
import com.fsck.k9.Account;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mail.transport.SmtpTransport;
import com.fsck.k9.mail.transport.WebDavTransport;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
public abstract class Transport {
protected static final int SOCKET_CONNECT_TIMEOUT = 10000;
// RFC 1047
protected static final int SOCKET_READ_TIMEOUT = 300000;
public synchronized static Transport getInstance(Account account) throws MessagingException {
String uri = account.getTransportUri();
public synchronized static Transport getInstance(StoreConfig storeConfig) throws MessagingException {
String uri = storeConfig.getTransportUri();
if (uri.startsWith("smtp")) {
return new SmtpTransport(account);
return new SmtpTransport(storeConfig);
} else if (uri.startsWith("webdav")) {
return new WebDavTransport(account);
return new WebDavTransport(storeConfig);
} else {
throw new MessagingException("Unable to locate an applicable Transport for " + uri);
}
@ -71,4 +75,19 @@ public abstract class Transport {
public abstract void sendMessage(Message message) throws MessagingException;
public abstract void close();
protected static String encodeUtf8(String s) {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not found");
}
}
protected static String decodeUtf8(String s) {
try {
return URLDecoder.decode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not found");
}
}
}

View File

@ -34,6 +34,22 @@ import java.nio.charset.Charset;
* @version $Id$
*/
public class Base64 {
public static String decode(String encoded) {
if (encoded == null) {
return null;
}
byte[] decoded = new Base64().decode(encoded.getBytes());
return new String(decoded);
}
public static String encode(String s) {
if (s == null) {
return null;
}
byte[] encoded = new Base64().encode(s.getBytes());
return new String(encoded);
}
/**
* Chunk size per RFC 2045 section 6.8.
*

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,6 @@
package com.fsck.k9.mail.internet;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import java.io.ByteArrayInputStream;
@ -13,6 +12,8 @@ import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
import org.apache.james.mime4j.util.CharsetUtil;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
/**
* Static methods for decoding strings, byte arrays and encoded words.
@ -35,7 +36,7 @@ class DecoderUtil {
Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes));
try {
return MimeUtility.readToString(is, charset);
return CharsetSupport.readToString(is, charset);
} catch (IOException e) {
return null;
}
@ -68,7 +69,7 @@ class DecoderUtil {
QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes));
try {
return MimeUtility.readToString(is, charset);
return CharsetSupport.readToString(is, charset);
} catch (IOException e) {
return null;
}
@ -162,13 +163,13 @@ class DecoderUtil {
String charset;
try {
charset = MimeUtility.fixupCharset(mimeCharset, message);
charset = CharsetSupport.fixupCharset(mimeCharset, message);
} catch (MessagingException e) {
return null;
}
if (encodedText.isEmpty()) {
Log.w(K9.LOG_TAG, "Missing encoded text in encoded word: '" + body.substring(begin, end) + "'");
Log.w(LOG_TAG, "Missing encoded text in encoded word: '" + body.substring(begin, end) + "'");
return null;
}
@ -177,7 +178,7 @@ class DecoderUtil {
} else if (encoding.equalsIgnoreCase("B")) {
return DecoderUtil.decodeB(encodedText, charset);
} else {
Log.w(K9.LOG_TAG, "Warning: Unknown encoding in encoded word '" + body.substring(begin, end) + "'");
Log.w(LOG_TAG, "Warning: Unknown encoding in encoded word '" + body.substring(begin, end) + "'");
return null;
}
}

View File

@ -68,7 +68,7 @@ class EncoderUtil {
if (charset == null)
charset = determineCharset(text);
String mimeCharset = MimeUtility.getExternalCharset(charset.name());
String mimeCharset = CharsetSupport.getExternalCharset(charset.name());
byte[] bytes = encode(text, charset);

View File

@ -0,0 +1,103 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
class JisSupport {
public static final String SHIFT_JIS = "shift_jis";
public static String getJisVariantFromMessage(Message message) throws MessagingException {
if (message == null)
return null;
// If a receiver is known to use a JIS variant, the sender transfers the message after converting the
// charset as a convention.
String variant = getJisVariantFromReceivedHeaders(message);
if (variant != null)
return variant;
// If a receiver is not known to use any JIS variants, the sender transfers the message without converting
// the charset.
variant = getJisVariantFromFromHeaders(message);
if (variant != null)
return variant;
return getJisVariantFromMailerHeaders(message);
}
public static boolean isShiftJis(String charset) {
return charset.length() > 17 && charset.startsWith("x-")
&& charset.endsWith("-shift_jis-2007");
}
public static String getJisVariantFromAddress(String address) {
if (address == null)
return null;
if (isInDomain(address, "docomo.ne.jp") || isInDomain(address, "dwmail.jp") ||
isInDomain(address, "pdx.ne.jp") || isInDomain(address, "willcom.com") ||
isInDomain(address, "emnet.ne.jp") || isInDomain(address, "emobile.ne.jp"))
return "docomo";
else if (isInDomain(address, "softbank.ne.jp") || isInDomain(address, "vodafone.ne.jp") ||
isInDomain(address, "disney.ne.jp") || isInDomain(address, "vertuclub.ne.jp"))
return "softbank";
else if (isInDomain(address, "ezweb.ne.jp") || isInDomain(address, "ido.ne.jp"))
return "kddi";
return null;
}
private static String getJisVariantFromMailerHeaders(Message message) throws MessagingException {
String mailerHeaders[] = message.getHeader("X-Mailer");
if (mailerHeaders == null || mailerHeaders.length == 0)
return null;
if (mailerHeaders[0].startsWith("iPhone Mail ") || mailerHeaders[0].startsWith("iPad Mail "))
return "iphone";
return null;
}
private static String getJisVariantFromReceivedHeaders(Part message) throws MessagingException {
String receivedHeaders[] = message.getHeader("Received");
if (receivedHeaders == null)
return null;
for (String receivedHeader : receivedHeaders) {
String address = getAddressFromReceivedHeader(receivedHeader);
if (address == null)
continue;
String variant = getJisVariantFromAddress(address);
if (variant != null)
return variant;
}
return null;
}
private static String getAddressFromReceivedHeader(String receivedHeader) {
// Not implemented yet! Extract an address from the FOR clause of the given Received header.
return null;
}
private static String getJisVariantFromFromHeaders(Message message) throws MessagingException {
Address addresses[] = message.getFrom();
if (addresses == null || addresses.length == 0)
return null;
return getJisVariantFromAddress(addresses[0].getAddress());
}
private static boolean isInDomain(String address, String domain) {
int index = address.length() - domain.length() - 1;
if (index < 0)
return false;
char c = address.charAt(index);
if (c != '@' && c != '.')
return false;
return address.endsWith(domain);
}
}

View File

@ -0,0 +1,453 @@
package com.fsck.k9.mail.internet;
import android.util.Log;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
import static com.fsck.k9.mail.internet.CharsetSupport.fixupCharset;
import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter;
import static com.fsck.k9.mail.internet.Viewable.Alternative;
import static com.fsck.k9.mail.internet.Viewable.Html;
import static com.fsck.k9.mail.internet.Viewable.MessageHeader;
import static com.fsck.k9.mail.internet.Viewable.Text;
import static com.fsck.k9.mail.internet.Viewable.Textual;
public class MessageExtractor {
private MessageExtractor() {}
public static String getTextFromPart(Part part) {
try {
if ((part != null) && (part.getBody() != null)) {
final Body body = part.getBody();
if (body instanceof TextBody) {
return ((TextBody)body).getText();
}
final String mimeType = part.getMimeType();
if ((mimeType != null) && MimeUtility.mimeTypeMatches(mimeType, "text/*")) {
/*
* We've got a text part, so let's see if it needs to be processed further.
*/
String charset = getHeaderParameter(part.getContentType(), "charset");
/*
* determine the charset from HTML message.
*/
if (mimeType.equalsIgnoreCase("text/html") && charset == null) {
InputStream in = part.getBody().getInputStream();
try {
byte[] buf = new byte[256];
in.read(buf, 0, buf.length);
String str = new String(buf, "US-ASCII");
if (str.isEmpty()) {
return "";
}
Pattern p = Pattern.compile("<meta http-equiv=\"?Content-Type\"? content=\"text/html; charset=(.+?)\">", Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(str);
if (m.find()) {
charset = m.group(1);
}
} finally {
try {
if (in instanceof BinaryTempFileBody.BinaryTempFileBodyInputStream) {
/*
* If this is a BinaryTempFileBodyInputStream, calling close()
* will delete the file. But we can't let that happen because
* the file needs to be opened again by the code a few lines
* down.
*/
((BinaryTempFileBody.BinaryTempFileBodyInputStream) in).closeWithoutDeleting();
} else {
in.close();
}
} catch (Exception e) { /* ignore */ }
}
}
charset = fixupCharset(charset, getMessageFromPart(part));
/*
* Now we read the part into a buffer for further processing. Because
* the stream is now wrapped we'll remove any transfer encoding at this point.
*/
InputStream in = part.getBody().getInputStream();
try {
String text = CharsetSupport.readToString(in, charset);
// Replace the body with a TextBody that already contains the decoded text
part.setBody(new TextBody(text));
return text;
} finally {
try {
/*
* This time we don't care if it's a BinaryTempFileBodyInputStream. We
* replaced the body with a TextBody instance and hence don't need the
* file anymore.
*/
in.close();
} catch (IOException e) { /* Ignore */ }
}
}
}
} catch (OutOfMemoryError oom) {
/*
* If we are not able to process the body there's nothing we can do about it. Return
* null and let the upper layers handle the missing content.
*/
Log.e(LOG_TAG, "Unable to getTextFromPart " + oom.toString());
} catch (Exception e) {
/*
* If we are not able to process the body there's nothing we can do about it. Return
* null and let the upper layers handle the missing content.
*/
Log.e(LOG_TAG, "Unable to getTextFromPart", e);
}
return null;
}
/**
* Traverse the MIME tree of a message an extract viewable parts.
*
* @param part
* The message part to start from.
* @param attachments
* A list that will receive the parts that are considered attachments.
*
* @return A list of {@link Viewable}s.
*
* @throws MessagingException
* In case of an error.
*/
public static List<Viewable> getViewables(Part part, List<Part> attachments) throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart multipart = (Multipart) body;
if (part.getMimeType().equalsIgnoreCase("multipart/alternative")) {
/*
* For multipart/alternative parts we try to find a text/plain and a text/html
* child. Everything else we find is put into 'attachments'.
*/
List<Viewable> text = findTextPart(multipart, true);
Set<Part> knownTextParts = getParts(text);
List<Viewable> html = findHtmlPart(multipart, knownTextParts, attachments, true);
if (!text.isEmpty() || !html.isEmpty()) {
Alternative alternative = new Alternative(text, html);
viewables.add(alternative);
}
} else {
// For all other multipart parts we recurse to grab all viewable children.
for (Part bodyPart : multipart.getBodyParts()) {
viewables.addAll(getViewables(bodyPart, attachments));
}
}
} else if (body instanceof Message &&
!("attachment".equalsIgnoreCase(getContentDisposition(part)))) {
/*
* We only care about message/rfc822 parts whose Content-Disposition header has a value
* other than "attachment".
*/
Message message = (Message) body;
// We add the Message object so we can extract the filename later.
viewables.add(new MessageHeader(part, message));
// Recurse to grab all viewable parts and attachments from that message.
viewables.addAll(getViewables(message, attachments));
} else if (isPartTextualBody(part)) {
/*
* Save text/plain and text/html
*/
String mimeType = part.getMimeType();
if (mimeType.equalsIgnoreCase("text/plain")) {
Text text = new Text(part);
viewables.add(text);
} else {
Html html = new Html(part);
viewables.add(html);
}
} else {
// Everything else is treated as attachment.
attachments.add(part);
}
return viewables;
}
public static Set<Part> getTextParts(Part part) throws MessagingException {
List<Part> attachments = new ArrayList<Part>();
return getParts(getViewables(part, attachments));
}
/**
* Collect attachment parts of a message.
* @return A list of parts regarded as attachments.
* @throws MessagingException In case of an error.
*/
public static List<Part> collectAttachments(Message message) throws MessagingException {
try {
List<Part> attachments = new ArrayList<Part>();
getViewables(message, attachments);
return attachments;
} catch (Exception e) {
throw new MessagingException("Couldn't collect attachment parts", e);
}
}
/**
* Collect the viewable textual parts of a message.
* @return A set of viewable parts of the message.
* @throws MessagingException In case of an error.
*/
public static Set<Part> collectTextParts(Message message) throws MessagingException {
try {
return getTextParts(message);
} catch (Exception e) {
throw new MessagingException("Couldn't extract viewable parts", e);
}
}
private static Message getMessageFromPart(Part part) {
while (part != null) {
if (part instanceof Message)
return (Message)part;
if (!(part instanceof BodyPart))
return null;
Multipart multipart = ((BodyPart)part).getParent();
if (multipart == null)
return null;
part = multipart.getParent();
}
return null;
}
/**
* Search the children of a {@link Multipart} for {@code text/plain} parts.
*
* @param multipart The {@code Multipart} to search through.
* @param directChild If {@code true}, this method will return after the first {@code text/plain} was
* found.
*
* @return A list of {@link Text} viewables.
*
* @throws MessagingException
* In case of an error.
*/
private static List<Viewable> findTextPart(Multipart multipart, boolean directChild)
throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
/*
* Recurse to find text parts. Since this is a multipart that is a child of a
* multipart/alternative we don't want to stop after the first text/plain part
* we find. This will allow to get all text parts for constructions like this:
*
* 1. multipart/alternative
* 1.1. multipart/mixed
* 1.1.1. text/plain
* 1.1.2. text/plain
* 1.2. text/html
*/
List<Viewable> textViewables = findTextPart(innerMultipart, false);
if (!textViewables.isEmpty()) {
viewables.addAll(textViewables);
if (directChild) {
break;
}
}
} else if (isPartTextualBody(part) && part.getMimeType().equalsIgnoreCase("text/plain")) {
Text text = new Text(part);
viewables.add(text);
if (directChild) {
break;
}
}
}
return viewables;
}
/**
* Search the children of a {@link Multipart} for {@code text/html} parts.
* Every part that is not a {@code text/html} we want to display, we add to 'attachments'.
*
* @param multipart The {@code Multipart} to search through.
* @param knownTextParts A set of {@code text/plain} parts that shouldn't be added to 'attachments'.
* @param attachments A list that will receive the parts that are considered attachments.
* @param directChild If {@code true}, this method will add all {@code text/html} parts except the first
* found to 'attachments'.
*
* @return A list of {@link Text} viewables.
*
* @throws MessagingException In case of an error.
*/
private static List<Viewable> findHtmlPart(Multipart multipart, Set<Part> knownTextParts,
List<Part> attachments, boolean directChild) throws MessagingException {
List<Viewable> viewables = new ArrayList<Viewable>();
boolean partFound = false;
for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
if (directChild && partFound) {
// We already found our text/html part. Now we're only looking for attachments.
findAttachments(innerMultipart, knownTextParts, attachments);
} else {
/*
* Recurse to find HTML parts. Since this is a multipart that is a child of a
* multipart/alternative we don't want to stop after the first text/html part
* we find. This will allow to get all text parts for constructions like this:
*
* 1. multipart/alternative
* 1.1. text/plain
* 1.2. multipart/mixed
* 1.2.1. text/html
* 1.2.2. text/html
* 1.3. image/jpeg
*/
List<Viewable> htmlViewables = findHtmlPart(innerMultipart, knownTextParts,
attachments, false);
if (!htmlViewables.isEmpty()) {
partFound = true;
viewables.addAll(htmlViewables);
}
}
} else if (!(directChild && partFound) && isPartTextualBody(part) &&
part.getMimeType().equalsIgnoreCase("text/html")) {
Html html = new Html(part);
viewables.add(html);
partFound = true;
} else if (!knownTextParts.contains(part)) {
// Only add this part as attachment if it's not a viewable text/plain part found
// earlier.
attachments.add(part);
}
}
return viewables;
}
/**
* Traverse the MIME tree and add everything that's not a known text part to 'attachments'.
*
* @param multipart
* The {@link Multipart} to start from.
* @param knownTextParts
* A set of known text parts we don't want to end up in 'attachments'.
* @param attachments
* A list that will receive the parts that are considered attachments.
*/
private static void findAttachments(Multipart multipart, Set<Part> knownTextParts,
List<Part> attachments) {
for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
findAttachments(innerMultipart, knownTextParts, attachments);
} else if (!knownTextParts.contains(part)) {
attachments.add(part);
}
}
}
/**
* Build a set of message parts for fast lookups.
*
* @param viewables
* A list of {@link Viewable}s containing references to the message parts to include in
* the set.
*
* @return The set of viewable {@code Part}s.
*
* @see MessageExtractor#findHtmlPart(Multipart, Set, List, boolean)
* @see MessageExtractor#findAttachments(Multipart, Set, List)
*/
private static Set<Part> getParts(List<Viewable> viewables) {
Set<Part> parts = new HashSet<Part>();
for (Viewable viewable : viewables) {
if (viewable instanceof Textual) {
parts.add(((Textual) viewable).getPart());
} else if (viewable instanceof Alternative) {
Alternative alternative = (Alternative) viewable;
parts.addAll(getParts(alternative.getText()));
parts.addAll(getParts(alternative.getHtml()));
}
}
return parts;
}
private static Boolean isPartTextualBody(Part part) throws MessagingException {
String disposition = part.getDisposition();
String dispositionType = null;
String dispositionFilename = null;
if (disposition != null) {
dispositionType = MimeUtility.getHeaderParameter(disposition, null);
dispositionFilename = MimeUtility.getHeaderParameter(disposition, "filename");
}
/*
* A best guess that this part is intended to be an attachment and not inline.
*/
boolean attachment = ("attachment".equalsIgnoreCase(dispositionType) || (dispositionFilename != null));
if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/html"))) {
return true;
}
/*
* If the part is plain text and it got this far it's part of a
* mixed (et al) and should be rendered inline.
*/
else if ((!attachment) && (part.getMimeType().equalsIgnoreCase("text/plain"))) {
return true;
}
/*
* Finally, if it's nothing else we will include it as an attachment.
*/
else {
return false;
}
}
private static String getContentDisposition(Part part) {
try {
String disposition = part.getDisposition();
if (disposition != null) {
return MimeUtility.getHeaderParameter(disposition, null);
}
} catch (MessagingException e) { /* ignore */ }
return null;
}
}

View File

@ -1,8 +1,6 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.helper.Utility;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
@ -97,7 +95,7 @@ public class MimeHeader {
public void writeTo(OutputStream out) throws IOException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
for (Field field : mFields) {
if (!Utility.arrayContains(writeOmitFields, field.name)) {
if (!Arrays.asList(writeOmitFields).contains(field.name)) {
String v = field.value;
if (hasToBeEncoded(v)) {

View File

@ -33,8 +33,6 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.K9;
/**
* An implementation of Message that stores all of it's metadata in RFC 822 and
@ -138,12 +136,12 @@ public class MimeMessage extends Message {
* @param sentDate
* @throws com.fsck.k9.mail.MessagingException
*/
public void addSentDate(Date sentDate) throws MessagingException {
public void addSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException {
if (mDateFormat == null) {
mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
}
if (K9.hideTimeZone()) {
if (hideTimeZone) {
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
}
@ -152,9 +150,9 @@ public class MimeMessage extends Message {
}
@Override
public void setSentDate(Date sentDate) throws MessagingException {
public void setSentDate(Date sentDate, boolean hideTimeZone) throws MessagingException {
removeHeader("Date");
addSentDate(sentDate);
addSentDate(sentDate, hideTimeZone);
}
public void setInternalSentDate(Date sentDate) {
@ -333,7 +331,7 @@ public class MimeMessage extends Message {
return "<" + UUID.randomUUID().toString().toUpperCase(Locale.US) + "@" + hostname + ">";
}
public void setMessageId(String messageId) throws UnavailableStorageException {
public void setMessageId(String messageId) throws MessagingException {
setHeader("Message-ID", messageId);
mMessageId = messageId;
}
@ -419,27 +417,27 @@ public class MimeMessage extends Message {
}
@Override
public void addHeader(String name, String value) throws UnavailableStorageException {
public void addHeader(String name, String value) throws MessagingException {
mHeader.addHeader(name, value);
}
@Override
public void setHeader(String name, String value) throws UnavailableStorageException {
public void setHeader(String name, String value) throws MessagingException {
mHeader.setHeader(name, value);
}
@Override
public String[] getHeader(String name) throws UnavailableStorageException {
public String[] getHeader(String name) throws MessagingException {
return mHeader.getHeader(name);
}
@Override
public void removeHeader(String name) throws UnavailableStorageException {
public void removeHeader(String name) throws MessagingException {
mHeader.removeHeader(name);
}
@Override
public Set<String> getHeaderNames() throws UnavailableStorageException {
public Set<String> getHeaderNames() throws MessagingException {
return mHeader.getHeaderNames();
}
@ -474,7 +472,7 @@ public class MimeMessage extends Message {
if (mBody instanceof Multipart) {
((Multipart)mBody).setCharset(charset);
} else if (mBody instanceof TextBody) {
MimeUtility.setCharset(charset, this);
CharsetSupport.setCharset(charset, this);
((TextBody)mBody).setCharset(charset);
}
}
@ -608,28 +606,27 @@ public class MimeMessage extends Message {
/**
* Copy the contents of this object into another {@code MimeMessage} object.
*
* @param message
* The {@code MimeMessage} object to receive the contents of this instance.
* @param destination The {@code MimeMessage} object to receive the contents of this instance.
*/
protected void copy(MimeMessage message) {
super.copy(message);
protected void copy(MimeMessage destination) {
super.copy(destination);
message.mHeader = mHeader.clone();
destination.mHeader = mHeader.clone();
message.mBody = mBody;
message.mMessageId = mMessageId;
message.mSentDate = mSentDate;
message.mDateFormat = mDateFormat;
message.mSize = mSize;
destination.mBody = mBody;
destination.mMessageId = mMessageId;
destination.mSentDate = mSentDate;
destination.mDateFormat = mDateFormat;
destination.mSize = mSize;
// These arrays are not supposed to be modified, so it's okay to reuse the references
message.mFrom = mFrom;
message.mTo = mTo;
message.mCc = mCc;
message.mBcc = mBcc;
message.mReplyTo = mReplyTo;
message.mReferences = mReferences;
message.mInReplyTo = mInReplyTo;
destination.mFrom = mFrom;
destination.mTo = mTo;
destination.mCc = mCc;
destination.mBcc = mBcc;
destination.mReplyTo = mReplyTo;
destination.mReferences = mReferences;
destination.mInReplyTo = mInReplyTo;
}
@Override

File diff suppressed because it is too large Load Diff

View File

@ -4,7 +4,11 @@ package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import java.io.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;

View File

@ -0,0 +1,104 @@
package com.fsck.k9.mail.internet;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.Part;
import java.util.List;
/**
* Empty marker class interface the class hierarchy used by
* {@link MessageExtractor#getViewables(com.fsck.k9.mail.Part, java.util.List)}
*
* @see Viewable.Text
* @see Viewable.Html
* @see Viewable.MessageHeader
* @see Viewable.Alternative
*/
public interface Viewable {
/**
* Class representing textual parts of a message that aren't marked as attachments.
*
* @see com.fsck.k9.mail.internet.MessageExtractor#isPartTextualBody(com.fsck.k9.mail.Part)
*/
abstract class Textual implements Viewable {
private Part mPart;
public Textual(Part part) {
mPart = part;
}
public Part getPart() {
return mPart;
}
}
/**
* Class representing a {@code text/plain} part of a message.
*/
class Text extends Textual {
public Text(Part part) {
super(part);
}
}
/**
* Class representing a {@code text/html} part of a message.
*/
class Html extends Textual {
public Html(Part part) {
super(part);
}
}
/**
* Class representing a {@code message/rfc822} part of a message.
*
* <p>
* This is used to extract basic header information when the message contents are displayed
* inline.
* </p>
*/
class MessageHeader implements Viewable {
private Part mContainerPart;
private Message mMessage;
public MessageHeader(Part containerPart, Message message) {
mContainerPart = containerPart;
mMessage = message;
}
public Part getContainerPart() {
return mContainerPart;
}
public Message getMessage() {
return mMessage;
}
}
/**
* Class representing a {@code multipart/alternative} part of a message.
*
* <p>
* Only relevant {@code text/plain} and {@code text/html} children are stored in this container
* class.
* </p>
*/
class Alternative implements Viewable {
private List<Viewable> mText;
private List<Viewable> mHtml;
public Alternative(List<Viewable> text, List<Viewable> html) {
mText = text;
mHtml = html;
}
public List<Viewable> getText() {
return mText;
}
public List<Viewable> getHtml() {
return mHtml;
}
}
}

View File

@ -1,5 +1,5 @@
package com.fsck.k9.net.ssl;
package com.fsck.k9.mail.ssl;
import java.net.Socket;
import java.security.Principal;
@ -20,16 +20,17 @@ import android.security.KeyChain;
import android.security.KeyChainException;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.mail.CertificateValidationException;
import com.fsck.k9.mail.MessagingException;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
/**
* For client certificate authentication! Provide private keys and certificates
* during the TLS handshake using the Android 4.0 KeyChain API.
*/
public class KeyChainKeyManager extends X509ExtendedKeyManager {
class KeyChainKeyManager extends X509ExtendedKeyManager {
private static PrivateKey sClientCertificateReferenceWorkaround;
@ -207,10 +208,10 @@ public class KeyChainKeyManager extends X509ExtendedKeyManager {
return mAlias;
}
}
Log.w(K9.LOG_TAG, "Client certificate " + mAlias + " not issued by any of the requested issuers");
Log.w(LOG_TAG, "Client certificate " + mAlias + " not issued by any of the requested issuers");
return null;
}
Log.w(K9.LOG_TAG, "Client certificate " + mAlias + " does not match any of the requested key types");
Log.w(LOG_TAG, "Client certificate " + mAlias + " does not match any of the requested key types");
return null;
}
}

View File

@ -1,4 +1,4 @@
package com.fsck.k9.security;
package com.fsck.k9.mail.ssl;
import java.io.File;
import java.io.FileInputStream;
@ -15,7 +15,7 @@ import org.apache.commons.io.IOUtils;
import android.util.Log;
import com.fsck.k9.K9;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class LocalKeyStore {
private static final int KEY_STORE_FILE_VERSION = 1;
@ -50,7 +50,7 @@ public class LocalKeyStore {
* error, presuming setKeyStoreFile(File) is called next with a
* non-null File.
*/
Log.w(K9.LOG_TAG, "Local key store has not been initialized");
Log.w(LOG_TAG, "Local key store has not been initialized");
}
}
@ -92,7 +92,7 @@ public class LocalKeyStore {
mKeyStore = store;
mKeyStoreFile = file;
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to initialize local key store", e);
Log.e(LOG_TAG, "Failed to initialize local key store", e);
// Use of the local key store is effectively disabled.
mKeyStore = null;
mKeyStoreFile = null;
@ -169,7 +169,7 @@ public class LocalKeyStore {
} catch (KeyStoreException e) {
// Ignore: most likely there was no cert. found
} catch (CertificateException e) {
Log.e(K9.LOG_TAG, "Error updating the local key store file", e);
Log.e(LOG_TAG, "Error updating the local key store file", e);
}
}

View File

@ -1,10 +1,9 @@
package com.fsck.k9.net.ssl;
package com.fsck.k9.mail.ssl;
import android.util.Log;
import com.fsck.k9.mail.CertificateChainException;
import com.fsck.k9.security.LocalKeyStore;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.net.ssl;
package com.fsck.k9.mail.ssl;
import android.util.Log;
@ -19,6 +19,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
/**
* Filter and reorder list of cipher suites and TLS versions.
@ -90,7 +92,7 @@ public class TrustedSocketFactory {
*/
supportedProtocols = sock.getSupportedProtocols();
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Error getting information about available SSL/TLS ciphers and " +
Log.e(LOG_TAG, "Error getting information about available SSL/TLS ciphers and " +
"protocols", e);
}

View File

@ -11,7 +11,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
public class ImapResponseParser {
class ImapResponseParser {
private static final SimpleDateFormat mDateTimeFormat = new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss Z", Locale.US);
private static final SimpleDateFormat badDateTimeFormat = new SimpleDateFormat("dd MMM yyyy HH:mm:ss Z", Locale.US);
private static final SimpleDateFormat badDateTimeFormat2 = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss Z", Locale.US);
@ -293,7 +293,7 @@ public class ImapResponseParser {
} catch (Exception e) {
// Catch everything else and save it for later.
mException = e;
//Log.e(K9.LOG_TAG, "parseLiteral(): Exception in callback method", e);
//Log.e(LOG_TAG, "parseLiteral(): Exception in callback method", e);
}
// Check if only some of the literal data was read

File diff suppressed because it is too large Load Diff

View File

@ -15,19 +15,19 @@
* limitations under the License.
*/
package com.fsck.k9.mail.store.imap;
package com.fsck.k9.mail.store;
import android.util.Log;
import com.fsck.k9.K9;
import java.util.ArrayList;
import java.util.List;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
/**
* Utility methods for use with IMAP.
*/
public class ImapUtility {
class ImapUtility {
/**
* Gets all of the values in a sequence set per RFC 3501.
*
@ -101,12 +101,12 @@ public class ImapUtility {
}
}
} else {
Log.d(K9.LOG_TAG, "Invalid range: " + range);
Log.d(LOG_TAG, "Invalid range: " + range);
}
}
}
} catch (NumberFormatException e) {
Log.d(K9.LOG_TAG, "Invalid range value: " + range, e);
Log.d(LOG_TAG, "Invalid range value: " + range, e);
}
return list;
@ -122,7 +122,7 @@ public class ImapUtility {
// do nothing
}
Log.d(K9.LOG_TAG, "Invalid UID value: " + number);
Log.d(LOG_TAG, "Invalid UID value: " + number);
return false;
}

View File

@ -3,17 +3,14 @@ package com.fsck.k9.mail.store;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.Hex;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.net.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.MessageRetrievalListener;
import javax.net.ssl.SSLException;
@ -24,7 +21,6 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
@ -35,7 +31,10 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
public class Pop3Store extends Store {
import static com.fsck.k9.mail.K9MailLib.DEBUG_PROTOCOL_POP3;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class Pop3Store extends RemoteStore {
public static final String STORE_TYPE = "POP3";
private static final String STLS_COMMAND = "STLS";
@ -128,12 +127,12 @@ public class Pop3Store extends Store {
passwordIndex++;
authType = AuthType.valueOf(userInfoParts[0]);
}
username = UrlEncodingHelper.decodeUtf8(userInfoParts[userIndex]);
username = decodeUtf8(userInfoParts[userIndex]);
if (userInfoParts.length > passwordIndex) {
if (authType == AuthType.EXTERNAL) {
clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
clientCertificateAlias = decodeUtf8(userInfoParts[passwordIndex]);
} else {
password = UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
password = decodeUtf8(userInfoParts[passwordIndex]);
}
}
}
@ -154,11 +153,11 @@ public class Pop3Store extends Store {
* @see Pop3Store#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
String userEnc = UrlEncodingHelper.encodeUtf8(server.username);
String userEnc = encodeUtf8(server.username);
String passwordEnc = (server.password != null) ?
UrlEncodingHelper.encodeUtf8(server.password) : "";
encodeUtf8(server.password) : "";
String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
@ -209,12 +208,12 @@ public class Pop3Store extends Store {
private boolean mTopNotSupported;
public Pop3Store(Account account) throws MessagingException {
super(account);
public Pop3Store(StoreConfig storeConfig) throws MessagingException {
super(storeConfig);
ServerSettings settings;
try {
settings = decodeUri(mAccount.getStoreUri());
settings = decodeUri(storeConfig.getStoreUri());
} catch (IllegalArgumentException e) {
throw new MessagingException("Error while decoding store URI", e);
}
@ -243,13 +242,13 @@ public class Pop3Store extends Store {
@Override
public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
List<Folder> folders = new LinkedList<Folder>();
folders.add(getFolder(mAccount.getInboxFolderName()));
folders.add(getFolder(mStoreConfig.getInboxFolderName()));
return folders;
}
@Override
public void checkSettings() throws MessagingException {
Pop3Folder folder = new Pop3Folder(mAccount.getInboxFolderName());
Pop3Folder folder = new Pop3Folder(mStoreConfig.getInboxFolderName());
folder.open(Folder.OPEN_MODE_RW);
if (!mCapabilities.uidl) {
/*
@ -272,7 +271,7 @@ public class Pop3Store extends Store {
return false;
}
class Pop3Folder extends Folder {
class Pop3Folder extends Folder<Pop3Message> {
private Socket mSocket;
private InputStream mIn;
private OutputStream mOut;
@ -283,11 +282,11 @@ public class Pop3Store extends Store {
private int mMessageCount;
public Pop3Folder(String name) {
super(Pop3Store.this.mAccount);
super();
this.mName = name;
if (mName.equalsIgnoreCase(mAccount.getInboxFolderName())) {
mName = mAccount.getInboxFolderName();
if (mName.equalsIgnoreCase(mStoreConfig.getInboxFolderName())) {
mName = mStoreConfig.getInboxFolderName();
}
}
@ -297,7 +296,7 @@ public class Pop3Store extends Store {
return;
}
if (!mName.equalsIgnoreCase(mAccount.getInboxFolderName())) {
if (!mName.equalsIgnoreCase(mStoreConfig.getInboxFolderName())) {
throw new MessagingException("Folder does not exist");
}
@ -313,7 +312,7 @@ public class Pop3Store extends Store {
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
if (!isOpen()) {
throw new MessagingException("Unable to connect socket");
}
@ -328,7 +327,7 @@ public class Pop3Store extends Store {
mSocket = TrustedSocketFactory.createSocket(mSocket, mHost, mPort,
mClientCertificateAlias);
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
mSocket.setSoTimeout(SOCKET_READ_TIMEOUT);
mIn = new BufferedInputStream(mSocket.getInputStream(), 1024);
mOut = new BufferedOutputStream(mSocket.getOutputStream(), 512);
if (!isOpen()) {
@ -461,7 +460,7 @@ public class Pop3Store extends Store {
try {
executeSimpleCommand(
String.format("AUTH EXTERNAL %s",
Utility.base64Encode(mUsername)), false);
Base64.encode(mUsername)), false);
} catch (Pop3ErrorResponse e) {
/*
* Provide notification to the user of a problem authenticating
@ -541,7 +540,7 @@ public class Pop3Store extends Store {
@Override
public boolean exists() throws MessagingException {
return mName.equalsIgnoreCase(mAccount.getInboxFolderName());
return mName.equalsIgnoreCase(mStoreConfig.getInboxFolderName());
}
@Override
@ -559,7 +558,7 @@ public class Pop3Store extends Store {
}
@Override
public Message getMessage(String uid) throws MessagingException {
public Pop3Message getMessage(String uid) throws MessagingException {
Pop3Message message = mUidToMsgMap.get(uid);
if (message == null) {
message = new Pop3Message(uid, this);
@ -635,7 +634,7 @@ public class Pop3Store extends Store {
// response = "+OK msgNum msgUid"
String[] uidParts = response.split(" +");
if (uidParts.length < 3 || !"+OK".equals(uidParts[0])) {
Log.e(K9.LOG_TAG, "ERR response: " + response);
Log.e(LOG_TAG, "ERR response: " + response);
return;
}
String msgUid = uidParts[2];
@ -693,8 +692,8 @@ public class Pop3Store extends Store {
Set<String> unindexedUids = new HashSet<String>();
for (String uid : uids) {
if (mUidToMsgMap.get(uid) == null) {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3) {
Log.d(K9.LOG_TAG, "Need to index UID " + uid);
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) {
Log.d(LOG_TAG, "Need to index UID " + uid);
}
unindexedUids.add(uid);
}
@ -719,8 +718,8 @@ public class Pop3Store extends Store {
Integer msgNum = Integer.valueOf(uidParts[0]);
String msgUid = uidParts[1];
if (unindexedUids.contains(msgUid)) {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3) {
Log.d(K9.LOG_TAG, "Got msgNum " + msgNum + " for UID " + msgUid);
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) {
Log.d(LOG_TAG, "Got msgNum " + msgNum + " for UID " + msgUid);
}
Pop3Message message = mUidToMsgMap.get(msgUid);
@ -734,8 +733,8 @@ public class Pop3Store extends Store {
}
private void indexMessage(int msgNum, Pop3Message message) {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3) {
Log.d(K9.LOG_TAG, "Adding index for UID " + message.getUid() + " to msgNum " + msgNum);
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) {
Log.d(LOG_TAG, "Adding index for UID " + message.getUid() + " to msgNum " + msgNum);
}
mMsgNumToMsgMap.put(msgNum, message);
mUidToMsgMap.put(message.getUid(), message);
@ -761,7 +760,7 @@ public class Pop3Store extends Store {
* @throws MessagingException
*/
@Override
public void fetch(List<? extends Message> messages, FetchProfile fp, MessageRetrievalListener listener)
public void fetch(List<? extends Message> messages, FetchProfile fp, MessageRetrievalListener<Pop3Message> listener)
throws MessagingException {
if (messages == null || messages.isEmpty()) {
return;
@ -805,9 +804,9 @@ public class Pop3Store extends Store {
* To convert the suggested download size we take the size
* divided by the maximum line size (76).
*/
if (mAccount.getMaximumAutoDownloadMessageSize() > 0) {
if (mStoreConfig.getMaximumAutoDownloadMessageSize() > 0) {
fetchBody(pop3Message,
(mAccount.getMaximumAutoDownloadMessageSize() / 76));
(mStoreConfig.getMaximumAutoDownloadMessageSize() / 76));
} else {
fetchBody(pop3Message, -1);
}
@ -819,7 +818,7 @@ public class Pop3Store extends Store {
pop3Message.setBody(null);
}
if (listener != null && !(fp.contains(FetchProfile.Item.ENVELOPE) && fp.size() == 1)) {
listener.messageFinished(message, i, count);
listener.messageFinished(pop3Message, i, count);
}
} catch (IOException ioe) {
throw new MessagingException("Unable to fetch message", ioe);
@ -906,8 +905,8 @@ public class Pop3Store extends Store {
// Try hard to use the TOP command if we're not asked to download the whole message.
if (lines != -1 && (!mTopNotSupported || mCapabilities.top)) {
try {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3 && !mCapabilities.top) {
Log.d(K9.LOG_TAG, "This server doesn't support the CAPA command. " +
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3 && !mCapabilities.top) {
Log.d(LOG_TAG, "This server doesn't support the CAPA command. " +
"Checking to see if the TOP command is supported nevertheless.");
}
@ -921,8 +920,8 @@ public class Pop3Store extends Store {
// The TOP command should be supported but something went wrong.
throw e;
} else {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3) {
Log.d(K9.LOG_TAG, "The server really doesn't support the TOP " +
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) {
Log.d(LOG_TAG, "The server really doesn't support the TOP " +
"command. Using RETR instead.");
}
@ -1029,8 +1028,8 @@ public class Pop3Store extends Store {
}
} while ((d = mIn.read()) != -1);
String ret = sb.toString();
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3) {
Log.d(K9.LOG_TAG, "<<< " + ret);
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) {
Log.d(LOG_TAG, "<<< " + ret);
}
return ret;
}
@ -1122,12 +1121,12 @@ public class Pop3Store extends Store {
open(Folder.OPEN_MODE_RW);
if (command != null) {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_POP3) {
if (sensitive && !K9.DEBUG_SENSITIVE) {
Log.d(K9.LOG_TAG, ">>> "
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_POP3) {
if (sensitive && !K9MailLib.isDebugSensitive()) {
Log.d(LOG_TAG, ">>> "
+ "[Command Hidden, Enable Sensitive Debug Logging To Show]");
} else {
Log.d(K9.LOG_TAG, ">>> " + command);
Log.d(LOG_TAG, ">>> " + command);
}
}
@ -1199,7 +1198,7 @@ public class Pop3Store extends Store {
// }
// catch (MessagingException me)
// {
// Log.w(K9.LOG_TAG, "Could not delete non-existent message", me);
// Log.w(LOG_TAG, "Could not delete non-existent message", me);
// }
}
}

View File

@ -0,0 +1,120 @@
package com.fsck.k9.mail.store;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Store;
import java.util.HashMap;
import java.util.Map;
public abstract class RemoteStore extends Store {
protected static final int SOCKET_CONNECT_TIMEOUT = 30000;
protected static final int SOCKET_READ_TIMEOUT = 60000;
protected StoreConfig mStoreConfig;
/**
* Remote stores indexed by Uri.
*/
private static Map<String, Store> sStores = new HashMap<String, Store>();
public RemoteStore(StoreConfig storeConfig) {
mStoreConfig = storeConfig;
}
/**
* Get an instance of a remote mail store.
*/
public synchronized static Store getInstance(StoreConfig storeConfig) throws MessagingException {
String uri = storeConfig.getStoreUri();
if (uri.startsWith("local")) {
throw new RuntimeException("Asked to get non-local Store object but given LocalStore URI");
}
Store store = sStores.get(uri);
if (store == null) {
if (uri.startsWith("imap")) {
store = new ImapStore(storeConfig);
} else if (uri.startsWith("pop3")) {
store = new Pop3Store(storeConfig);
} else if (uri.startsWith("webdav")) {
store = new WebDavStore(storeConfig);
}
if (store != null) {
sStores.put(uri, store);
}
}
if (store == null) {
throw new MessagingException("Unable to locate an applicable Store for " + uri);
}
return store;
}
/**
* Release reference to a remote mail store instance.
*
* @param storeConfig {@link com.fsck.k9.mail.store.StoreConfig} instance that is used to get the remote mail store instance.
*/
public static void removeInstance(StoreConfig storeConfig) {
String uri = storeConfig.getStoreUri();
if (uri.startsWith("local")) {
throw new RuntimeException("Asked to get non-local Store object but given " +
"LocalStore URI");
}
sStores.remove(uri);
}
/**
* Decodes the contents of store-specific URIs and puts them into a {@link com.fsck.k9.mail.ServerSettings}
* object.
*
* @param uri
* the store-specific URI to decode
*
* @return A {@link com.fsck.k9.mail.ServerSettings} object holding the settings contained in the URI.
*
* @see com.fsck.k9.mail.store.ImapStore#decodeUri(String)
* @see com.fsck.k9.mail.store.Pop3Store#decodeUri(String)
* @see com.fsck.k9.mail.store.WebDavStore#decodeUri(String)
*/
public static ServerSettings decodeStoreUri(String uri) {
if (uri.startsWith("imap")) {
return ImapStore.decodeUri(uri);
} else if (uri.startsWith("pop3")) {
return Pop3Store.decodeUri(uri);
} else if (uri.startsWith("webdav")) {
return WebDavStore.decodeUri(uri);
} else {
throw new IllegalArgumentException("Not a valid store URI");
}
}
/**
* Creates a store URI from the information supplied in the {@link com.fsck.k9.mail.ServerSettings} object.
*
* @param server
* The {@link com.fsck.k9.mail.ServerSettings} object that holds the server settings.
*
* @return A store URI that holds the same information as the {@code server} parameter.
*
* @see com.fsck.k9.mail.store.ImapStore#createUri(com.fsck.k9.mail.ServerSettings)
* @see com.fsck.k9.mail.store.Pop3Store#createUri(com.fsck.k9.mail.ServerSettings)
* @see com.fsck.k9.mail.store.WebDavStore#createUri(com.fsck.k9.mail.ServerSettings)
*/
public static String createStoreUri(ServerSettings server) {
if (ImapStore.STORE_TYPE.equals(server.type)) {
return ImapStore.createUri(server);
} else if (Pop3Store.STORE_TYPE.equals(server.type)) {
return Pop3Store.createUri(server);
} else if (WebDavStore.STORE_TYPE.equals(server.type)) {
return WebDavStore.createUri(server);
} else {
throw new IllegalArgumentException("Not a valid store URI");
}
}
}

View File

@ -0,0 +1,31 @@
package com.fsck.k9.mail.store;
public interface StoreConfig {
String getStoreUri();
String getTransportUri();
boolean subscribedFoldersOnly();
boolean useCompression(int type);
String getInboxFolderName();
String getOutboxFolderName();
String getDraftsFolderName();
void setDraftsFolderName(String name);
void setTrashFolderName(String name);
void setSpamFolderName(String name);
void setSentFolderName(String name);
void setAutoExpandFolderName(String name);
void setInboxFolderName(String name);
int getMaximumAutoDownloadMessageSize();
boolean allowRemoteSearch();
boolean isRemoteSearchFullText();
boolean isPushPollOnConnect();
int getDisplayCount();
int getIdleRefreshMinutes();
}

View File

@ -4,7 +4,7 @@ import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.params.HttpParams;
import com.fsck.k9.net.ssl.TrustManagerFactory;
import com.fsck.k9.mail.ssl.TrustManagerFactory;
import java.io.IOException;
import java.net.InetAddress;

View File

@ -2,15 +2,11 @@ package com.fsck.k9.mail.store;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.MessageRetrievalListener;
import org.apache.commons.io.IOUtils;
import org.apache.http.*;
@ -49,13 +45,16 @@ import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.GZIPInputStream;
import static com.fsck.k9.mail.K9MailLib.DEBUG_PROTOCOL_WEBDAV;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
/**
* <pre>
* Uses WebDAV formatted HTTP calls to an MS Exchange server to fetch email
* and email information.
* </pre>
*/
public class WebDavStore extends Store {
public class WebDavStore extends RemoteStore {
public static final String STORE_TYPE = "WebDAV";
// Authentication types
@ -138,7 +137,7 @@ public class WebDavStore extends Store {
String userInfo = webDavUri.getUserInfo();
if (userInfo != null) {
String[] userInfoParts = userInfo.split(":");
username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
username = decodeUtf8(userInfoParts[0]);
String userParts[] = username.split("\\\\", 2);
if (userParts.length > 1) {
@ -147,7 +146,7 @@ public class WebDavStore extends Store {
alias = username;
}
if (userInfoParts.length > 1) {
password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
password = decodeUtf8(userInfoParts[1]);
}
}
@ -187,9 +186,9 @@ public class WebDavStore extends Store {
* @see WebDavStore#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
String userEnc = UrlEncodingHelper.encodeUtf8(server.username);
String userEnc = encodeUtf8(server.username);
String passwordEnc = (server.password != null) ?
UrlEncodingHelper.encodeUtf8(server.password) : "";
encodeUtf8(server.password) : "";
String scheme;
switch (server.connectionSecurity) {
@ -294,12 +293,12 @@ public class WebDavStore extends Store {
private Map<String, WebDavFolder> mFolderList = new HashMap<String, WebDavFolder>();
public WebDavStore(Account account) throws MessagingException {
super(account);
public WebDavStore(StoreConfig storeConfig) throws MessagingException {
super(storeConfig);
WebDavStoreSettings settings;
try {
settings = decodeUri(mAccount.getStoreUri());
settings = decodeUri(storeConfig.getStoreUri());
} catch (IllegalArgumentException e) {
throw new MessagingException("Error while decoding store URI", e);
}
@ -340,7 +339,7 @@ public class WebDavStore extends Store {
// The inbox path would look like: "https://mail.domain.com/Exchange/alias/Inbox".
mUrl = getRoot() + mPath + mMailboxPath;
mAuthString = "Basic " + Utility.base64Encode(mUsername + ":" + mPassword);
mAuthString = "Basic " + Base64.encode(mUsername + ":" + mPassword);
}
private String getRoot() {
@ -380,21 +379,21 @@ public class WebDavStore extends Store {
Map<String, String> specialFoldersMap = dataset.getSpecialFolderToUrl();
String folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_INBOX_FOLDER));
if (folderName != null) {
mAccount.setAutoExpandFolderName(folderName);
mAccount.setInboxFolderName(folderName);
mStoreConfig.setAutoExpandFolderName(folderName);
mStoreConfig.setInboxFolderName(folderName);
}
folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_DRAFTS_FOLDER));
if (folderName != null)
mAccount.setDraftsFolderName(folderName);
mStoreConfig.setDraftsFolderName(folderName);
folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_TRASH_FOLDER));
if (folderName != null)
mAccount.setTrashFolderName(folderName);
mStoreConfig.setTrashFolderName(folderName);
folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_SPAM_FOLDER));
if (folderName != null)
mAccount.setSpamFolderName(folderName);
mStoreConfig.setSpamFolderName(folderName);
// K-9 Mail's outbox is a special local folder and different from Exchange/WebDAV's outbox.
/*
@ -405,7 +404,7 @@ public class WebDavStore extends Store {
folderName = getFolderName(specialFoldersMap.get(DAV_MAIL_SENT_FOLDER));
if (folderName != null)
mAccount.setSentFolderName(folderName);
mStoreConfig.setSentFolderName(folderName);
/**
* Next we get all the folders (including "special" ones)
@ -475,7 +474,7 @@ public class WebDavStore extends Store {
// Decodes the url-encoded folder name (i.e. "My%20folder" => "My Folder"
return UrlEncodingHelper.decodeUtf8(fullPathName);
return decodeUtf8(fullPathName);
}
return null;
@ -721,7 +720,7 @@ public class WebDavStore extends Store {
doFBA(null);
}
} catch (IOException ioe) {
Log.e(K9.LOG_TAG, "Error during authentication: " + ioe + "\nStack: " + processException(ioe));
Log.e(LOG_TAG, "Error during authentication: " + ioe + "\nStack: " + processException(ioe));
throw new MessagingException("Error during authentication", ioe);
}
@ -791,7 +790,7 @@ public class WebDavStore extends Store {
} catch (SSLException e) {
throw new CertificateValidationException(e.getMessage(), e);
} catch (IOException ioe) {
Log.e(K9.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
Log.e(LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
throw new MessagingException("IOException", ioe);
}
@ -893,7 +892,7 @@ public class WebDavStore extends Store {
response = httpClient.executeOverride(request, mContext);
authenticated = testAuthenticationResponse(response);
} catch (URISyntaxException e) {
Log.e(K9.LOG_TAG, "URISyntaxException caught " + e + "\nTrace: " + processException(e));
Log.e(LOG_TAG, "URISyntaxException caught " + e + "\nTrace: " + processException(e));
throw new MessagingException("URISyntaxException caught", e);
}
} else {
@ -987,7 +986,7 @@ public class WebDavStore extends Store {
}
}
} catch (URISyntaxException e) {
Log.e(K9.LOG_TAG, "URISyntaxException caught " + e + "\nTrace: " + processException(e));
Log.e(LOG_TAG, "URISyntaxException caught " + e + "\nTrace: " + processException(e));
throw new MessagingException("URISyntaxException caught", e);
}
}
@ -1024,10 +1023,10 @@ public class WebDavStore extends Store {
Scheme s = new Scheme("https", new WebDavSocketFactory(mHost, 443), 443);
reg.register(s);
} catch (NoSuchAlgorithmException nsa) {
Log.e(K9.LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa);
Log.e(LOG_TAG, "NoSuchAlgorithmException in getHttpClient: " + nsa);
throw new MessagingException("NoSuchAlgorithmException in getHttpClient: " + nsa);
} catch (KeyManagementException kme) {
Log.e(K9.LOG_TAG, "KeyManagementException in getHttpClient: " + kme);
Log.e(LOG_TAG, "KeyManagementException in getHttpClient: " + kme);
throw new MessagingException("KeyManagementException in getHttpClient: " + kme);
}
}
@ -1094,10 +1093,10 @@ public class WebDavStore extends Store {
istream = WebDavHttpClient.getUngzippedContent(entity);
}
} catch (UnsupportedEncodingException uee) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException: " + uee + "\nTrace: " + processException(uee));
Log.e(LOG_TAG, "UnsupportedEncodingException: " + uee + "\nTrace: " + processException(uee));
throw new MessagingException("UnsupportedEncodingException", uee);
} catch (IOException ioe) {
Log.e(K9.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
Log.e(LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
throw new MessagingException("IOException", ioe);
}
@ -1122,8 +1121,8 @@ public class WebDavStore extends Store {
boolean needsParsing)
throws MessagingException {
DataSet dataset = new DataSet();
if (K9.DEBUG && K9.DEBUG_PROTOCOL_WEBDAV) {
Log.v(K9.LOG_TAG, "processRequest url = '" + url + "', method = '" + method + "', messageBody = '"
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_WEBDAV) {
Log.v(LOG_TAG, "processRequest url = '" + url + "', method = '" + method + "', messageBody = '"
+ messageBody + "'");
}
@ -1155,10 +1154,10 @@ public class WebDavStore extends Store {
dataset = myHandler.getDataSet();
} catch (SAXException se) {
Log.e(K9.LOG_TAG, "SAXException in processRequest() " + se + "\nTrace: " + processException(se));
Log.e(LOG_TAG, "SAXException in processRequest() " + se + "\nTrace: " + processException(se));
throw new MessagingException("SAXException in processRequest() ", se);
} catch (ParserConfigurationException pce) {
Log.e(K9.LOG_TAG, "ParserConfigurationException in processRequest() " + pce + "\nTrace: "
Log.e(LOG_TAG, "ParserConfigurationException in processRequest() " + pce + "\nTrace: "
+ processException(pce));
throw new MessagingException("ParserConfigurationException in processRequest() ", pce);
}
@ -1166,10 +1165,10 @@ public class WebDavStore extends Store {
istream.close();
}
} catch (UnsupportedEncodingException uee) {
Log.e(K9.LOG_TAG, "UnsupportedEncodingException: " + uee + "\nTrace: " + processException(uee));
Log.e(LOG_TAG, "UnsupportedEncodingException: " + uee + "\nTrace: " + processException(uee));
throw new MessagingException("UnsupportedEncodingException in processRequest() ", uee);
} catch (IOException ioe) {
Log.e(K9.LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
Log.e(LOG_TAG, "IOException: " + ioe + "\nTrace: " + processException(ioe));
throw new MessagingException("IOException in processRequest() ", ioe);
}
@ -1196,7 +1195,7 @@ public class WebDavStore extends Store {
@Override
public void sendMessages(List<? extends Message> messages) throws MessagingException {
WebDavFolder tmpFolder = (WebDavStore.WebDavFolder) getFolder(mAccount.getDraftsFolderName());
WebDavFolder tmpFolder = (WebDavStore.WebDavFolder) getFolder(mStoreConfig.getDraftsFolderName());
try {
tmpFolder.open(Folder.OPEN_MODE_RW);
List<? extends Message> retMessages = tmpFolder.appendWebDavMessages(messages);
@ -1216,7 +1215,7 @@ public class WebDavStore extends Store {
/**
* A WebDav Folder
*/
class WebDavFolder extends Folder {
class WebDavFolder extends Folder<WebDavMessage> {
private String mName;
private String mFolderUrl;
private boolean mIsOpen = false;
@ -1229,7 +1228,7 @@ public class WebDavStore extends Store {
}
public WebDavFolder(WebDavStore nStore, String name) {
super(nStore.getAccount());
super();
store = nStore;
this.mName = name;
@ -1238,9 +1237,9 @@ public class WebDavStore extends Store {
String url = "";
for (int i = 0, count = urlParts.length; i < count; i++) {
if (i != 0) {
url = url + "/" + UrlEncodingHelper.encodeUtf8(urlParts[i]);
url = url + "/" + encodeUtf8(urlParts[i]);
} else {
url = UrlEncodingHelper.encodeUtf8(urlParts[i]);
url = encodeUtf8(urlParts[i]);
}
}
encodedName = url;
@ -1310,7 +1309,7 @@ public class WebDavStore extends Store {
headers.put("Brief", "t");
headers.put("If-Match", "*");
String action = (isMove ? "BMOVE" : "BCOPY");
Log.i(K9.LOG_TAG, "Moving " + messages.size() + " messages to " + destFolder.mFolderUrl);
Log.i(LOG_TAG, "Moving " + messages.size() + " messages to " + destFolder.mFolderUrl);
processRequest(mFolderUrl, action, messageBody, headers, false);
}
@ -1333,8 +1332,8 @@ public class WebDavStore extends Store {
if (dataset != null) {
messageCount = dataset.getMessageCount();
}
if (K9.DEBUG && K9.DEBUG_PROTOCOL_WEBDAV) {
Log.v(K9.LOG_TAG, "Counted messages and webdav returned: "+messageCount);
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_WEBDAV) {
Log.v(LOG_TAG, "Counted messages and webdav returned: "+messageCount);
}
return messageCount;
@ -1397,7 +1396,7 @@ public class WebDavStore extends Store {
}
@Override
public Message getMessage(String uid) throws MessagingException {
public WebDavMessage getMessage(String uid) throws MessagingException {
return new WebDavMessage(uid, this);
}
@ -1498,7 +1497,7 @@ public class WebDavStore extends Store {
}
@Override
public void fetch(List<? extends Message> messages, FetchProfile fp, MessageRetrievalListener listener)
public void fetch(List<? extends Message> messages, FetchProfile fp, MessageRetrievalListener<WebDavMessage> listener)
throws MessagingException {
if (messages == null ||
messages.isEmpty()) {
@ -1519,8 +1518,8 @@ public class WebDavStore extends Store {
}
if (fp.contains(FetchProfile.Item.BODY_SANE)) {
if (mAccount.getMaximumAutoDownloadMessageSize() > 0) {
fetchMessages(messages, listener, (mAccount.getMaximumAutoDownloadMessageSize() / 76));
if (mStoreConfig.getMaximumAutoDownloadMessageSize() > 0) {
fetchMessages(messages, listener, (mStoreConfig.getMaximumAutoDownloadMessageSize() / 76));
} else {
fetchMessages(messages, listener, -1);
}
@ -1533,7 +1532,7 @@ public class WebDavStore extends Store {
/**
* Fetches the full messages or up to lines lines and passes them to the message parser.
*/
private void fetchMessages(List<? extends Message> messages, MessageRetrievalListener listener, int lines)
private void fetchMessages(List<? extends Message> messages, MessageRetrievalListener<WebDavMessage> listener, int lines)
throws MessagingException {
WebDavHttpClient httpclient;
httpclient = getHttpClient();
@ -1561,7 +1560,7 @@ public class WebDavStore extends Store {
*/
if (wdMessage.getUrl().equals("")) {
wdMessage.setUrl(getMessageUrls(new String[] { wdMessage.getUid() }).get(wdMessage.getUid()));
Log.i(K9.LOG_TAG, "Fetching messages with UID = '" + wdMessage.getUid() + "', URL = '"
Log.i(LOG_TAG, "Fetching messages with UID = '" + wdMessage.getUid() + "', URL = '"
+ wdMessage.getUrl() + "'");
if (wdMessage.getUrl().equals("")) {
throw new MessagingException("Unable to get URL for message");
@ -1569,7 +1568,7 @@ public class WebDavStore extends Store {
}
try {
Log.i(K9.LOG_TAG, "Fetching message with UID = '" + wdMessage.getUid() + "', URL = '"
Log.i(LOG_TAG, "Fetching message with UID = '" + wdMessage.getUid() + "', URL = '"
+ wdMessage.getUrl() + "'");
HttpGet httpget = new HttpGet(new URI(wdMessage.getUrl()));
HttpResponse response;
@ -1594,8 +1593,8 @@ public class WebDavStore extends Store {
if (entity != null) {
InputStream istream = null;
StringBuilder buffer = new StringBuilder();
String tempText = "";
String resultText = "";
String tempText;
String resultText;
BufferedReader reader = null;
int currentLines = 0;
@ -1625,13 +1624,13 @@ public class WebDavStore extends Store {
}
} catch (IllegalArgumentException iae) {
Log.e(K9.LOG_TAG, "IllegalArgumentException caught " + iae + "\nTrace: " + processException(iae));
Log.e(LOG_TAG, "IllegalArgumentException caught " + iae + "\nTrace: " + processException(iae));
throw new MessagingException("IllegalArgumentException caught", iae);
} catch (URISyntaxException use) {
Log.e(K9.LOG_TAG, "URISyntaxException caught " + use + "\nTrace: " + processException(use));
Log.e(LOG_TAG, "URISyntaxException caught " + use + "\nTrace: " + processException(use));
throw new MessagingException("URISyntaxException caught", use);
} catch (IOException ioe) {
Log.e(K9.LOG_TAG, "Non-success response code loading message, response code was " + statusCode
Log.e(LOG_TAG, "Non-success response code loading message, response code was " + statusCode
+ "\nURL: " + wdMessage.getUrl() + "\nError: " + ioe.getMessage() + "\nTrace: "
+ processException(ioe));
throw new MessagingException("Failure code " + statusCode, ioe);
@ -1702,7 +1701,7 @@ public class WebDavStore extends Store {
try {
wdMessage.setFlagInternal(Flag.SEEN, uidToReadStatus.get(wdMessage.getUid()));
} catch (NullPointerException e) {
Log.v(K9.LOG_TAG,"Under some weird circumstances, setting the read status when syncing from webdav threw an NPE. Skipping.");
Log.v(LOG_TAG,"Under some weird circumstances, setting the read status when syncing from webdav threw an NPE. Skipping.");
}
if (listener != null) {
@ -1771,7 +1770,7 @@ public class WebDavStore extends Store {
wdMessage.setNewHeaders(envelope);
wdMessage.setFlagInternal(Flag.SEEN, envelope.getReadStatus());
} else {
Log.e(K9.LOG_TAG,"Asked to get metadata for a non-existent message: "+wdMessage.getUid());
Log.e(LOG_TAG,"Asked to get metadata for a non-existent message: "+wdMessage.getUid());
}
if (listener != null) {
@ -1881,9 +1880,9 @@ public class WebDavStore extends Store {
if (!messageURL.endsWith("/")) {
messageURL += "/";
}
messageURL += UrlEncodingHelper.encodeUtf8(message.getUid() + ":" + System.currentTimeMillis() + ".eml");
messageURL += encodeUtf8(message.getUid() + ":" + System.currentTimeMillis() + ".eml");
Log.i(K9.LOG_TAG, "Uploading message as " + messageURL);
Log.i(LOG_TAG, "Uploading message as " + messageURL);
httpmethod = new HttpGeneric(messageURL);
httpmethod.setMethod("PUT");
@ -1922,7 +1921,7 @@ public class WebDavStore extends Store {
@Override
public String getUidFromMessageId(Message message) throws MessagingException {
Log.e(K9.LOG_TAG,
Log.e(LOG_TAG,
"Unimplemented method getUidFromMessageId in WebDavStore.WebDavFolder could lead to duplicate messages "
+ " being uploaded to the Sent folder");
return null;
@ -1930,7 +1929,7 @@ public class WebDavStore extends Store {
@Override
public void setFlags(final Set<Flag> flags, boolean value) throws MessagingException {
Log.e(K9.LOG_TAG,
Log.e(LOG_TAG,
"Unimplemented method setFlags(Set<Flag>, boolean) breaks markAllMessagesAsRead and EmptyTrash");
// Try to make this efficient by not retrieving all of the messages
}
@ -1968,11 +1967,11 @@ public class WebDavStore extends Store {
* We have to decode, then encode the URL because Exchange likes to not properly encode all characters
*/
try {
end = UrlEncodingHelper.decodeUtf8(end);
end = UrlEncodingHelper.encodeUtf8(end);
end = decodeUtf8(end);
end = encodeUtf8(end);
end = end.replaceAll("\\+", "%20");
} catch (IllegalArgumentException iae) {
Log.e(K9.LOG_TAG, "IllegalArgumentException caught in setUrl: " + iae + "\nTrace: "
Log.e(LOG_TAG, "IllegalArgumentException caught in setUrl: " + iae + "\nTrace: "
+ processException(iae));
}
@ -2022,7 +2021,7 @@ public class WebDavStore extends Store {
@Override
public void delete(String trashFolderName) throws MessagingException {
WebDavFolder wdFolder = (WebDavFolder) getFolder();
Log.i(K9.LOG_TAG, "Deleting message by moving to " + trashFolderName);
Log.i(LOG_TAG, "Deleting message by moving to " + trashFolderName);
wdFolder.moveMessages(Collections.singletonList(this), wdFolder.getStore().getFolder(trashFolderName));
}
@ -2320,7 +2319,7 @@ public class WebDavStore extends Store {
Date parsedDate = dfInput.parse(date);
tempDate = dfOutput.format(parsedDate);
} catch (java.text.ParseException pe) {
Log.e(K9.LOG_TAG, "Error parsing date: " + pe + "\nTrace: " + processException(pe));
Log.e(LOG_TAG, "Error parsing date: " + pe + "\nTrace: " + processException(pe));
}
envelope.addHeader(header, tempDate);
} else {
@ -2359,8 +2358,8 @@ public class WebDavStore extends Store {
public HttpGeneric(final String uri) {
super();
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Starting uri = '" + uri + "'");
if (K9MailLib.isDebug()) {
Log.v(LOG_TAG, "Starting uri = '" + uri + "'");
}
String[] urlParts = uri.split("/");
@ -2373,12 +2372,12 @@ public class WebDavStore extends Store {
*/
try {
if (length > 3) {
end = UrlEncodingHelper.decodeUtf8(end);
end = UrlEncodingHelper.encodeUtf8(end);
end = decodeUtf8(end);
end = encodeUtf8(end);
end = end.replaceAll("\\+", "%20");
}
} catch (IllegalArgumentException iae) {
Log.e(K9.LOG_TAG, "IllegalArgumentException caught in HttpGeneric(String uri): " + iae + "\nTrace: "
Log.e(LOG_TAG, "IllegalArgumentException caught in HttpGeneric(String uri): " + iae + "\nTrace: "
+ processException(iae));
}
@ -2389,13 +2388,13 @@ public class WebDavStore extends Store {
url = urlParts[i];
}
}
if (K9.DEBUG && K9.DEBUG_PROTOCOL_WEBDAV) {
Log.v(K9.LOG_TAG, "url = '" + url + "' length = " + url.length()
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_WEBDAV) {
Log.v(LOG_TAG, "url = '" + url + "' length = " + url.length()
+ ", end = '" + end + "' length = " + end.length());
}
url = url + "/" + end;
Log.i(K9.LOG_TAG, "url = " + url);
Log.i(LOG_TAG, "url = " + url);
setURI(URI.create(url));
}
@ -2425,7 +2424,7 @@ public class WebDavStore extends Store {
* the License for the specific language governing permissions and limitations under the License.
*/
public static void modifyRequestToAcceptGzipResponse(HttpRequest request) {
Log.i(K9.LOG_TAG, "Requesting gzipped data");
Log.i(LOG_TAG, "Requesting gzipped data");
request.addHeader("Accept-Encoding", "gzip");
}
@ -2441,7 +2440,7 @@ public class WebDavStore extends Store {
if (contentEncoding == null)
return responseStream;
if (contentEncoding.contains("gzip")) {
Log.i(K9.LOG_TAG, "Response is gzipped");
Log.i(LOG_TAG, "Response is gzipped");
responseStream = new GZIPInputStream(responseStream);
}
return responseStream;

View File

@ -3,20 +3,18 @@ package com.fsck.k9.mail.transport;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.filter.LineWrapOutputStream;
import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.filter.SmtpDataStuffing;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.net.ssl.TrustedSocketFactory;
import com.fsck.k9.mail.internet.CharsetSupport;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mail.ssl.TrustedSocketFactory;
import javax.net.ssl.SSLException;
@ -28,6 +26,9 @@ import java.net.*;
import java.security.GeneralSecurityException;
import java.util.*;
import static com.fsck.k9.mail.K9MailLib.DEBUG_PROTOCOL_SMTP;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class SmtpTransport extends Transport {
public static final String TRANSPORT_TYPE = "SMTP";
@ -95,19 +96,19 @@ public class SmtpTransport extends Transport {
String[] userInfoParts = smtpUri.getUserInfo().split(":");
if (userInfoParts.length == 1) {
authType = AuthType.PLAIN;
username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
username = decodeUtf8(userInfoParts[0]);
} else if (userInfoParts.length == 2) {
authType = AuthType.PLAIN;
username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
username = decodeUtf8(userInfoParts[0]);
password = decodeUtf8(userInfoParts[1]);
} else if (userInfoParts.length == 3) {
// NOTE: In SmptTransport URIs, the authType comes last!
authType = AuthType.valueOf(userInfoParts[2]);
username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
username = decodeUtf8(userInfoParts[0]);
if (authType == AuthType.EXTERNAL) {
clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
clientCertificateAlias = decodeUtf8(userInfoParts[1]);
} else {
password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
password = decodeUtf8(userInfoParts[1]);
}
}
}
@ -124,16 +125,16 @@ public class SmtpTransport extends Transport {
*
* @return A SmtpTransport URI that holds the same information as the {@code server} parameter.
*
* @see Account#getTransportUri()
* @see com.fsck.k9.mail.store.StoreConfig#getTransportUri()
* @see SmtpTransport#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
String userEnc = (server.username != null) ?
UrlEncodingHelper.encodeUtf8(server.username) : "";
encodeUtf8(server.username) : "";
String passwordEnc = (server.password != null) ?
UrlEncodingHelper.encodeUtf8(server.password) : "";
encodeUtf8(server.password) : "";
String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
@ -149,7 +150,7 @@ public class SmtpTransport extends Transport {
break;
}
String userInfo = null;
String userInfo;
AuthType authType = server.authenticationType;
// NOTE: authType is append at last item, in contrast to ImapStore and Pop3Store!
if (authType != null) {
@ -183,10 +184,10 @@ public class SmtpTransport extends Transport {
private boolean m8bitEncodingAllowed;
private int mLargestAcceptableMessage;
public SmtpTransport(Account account) throws MessagingException {
public SmtpTransport(StoreConfig storeConfig) throws MessagingException {
ServerSettings settings;
try {
settings = decodeUri(account.getTransportUri());
settings = decodeUri(storeConfig.getTransportUri());
} catch (IllegalArgumentException e) {
throw new MessagingException("Error while decoding transport URI", e);
}
@ -305,8 +306,8 @@ public class SmtpTransport extends Transport {
try {
mLargestAcceptableMessage = Integer.parseInt(extensions.get("SIZE"));
} catch (Exception e) {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) {
Log.d(K9.LOG_TAG, "Tried to parse " + extensions.get("SIZE") + " and get an int", e);
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_SMTP) {
Log.d(LOG_TAG, "Tried to parse " + extensions.get("SIZE") + " and get an int", e);
}
}
}
@ -438,14 +439,14 @@ public class SmtpTransport extends Transport {
extensions.put(pair[0].toUpperCase(Locale.US), pair.length == 1 ? "" : pair[1]);
}
} catch (NegativeSmtpReplyException e) {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Server doesn't support the EHLO command. Trying HELO...");
if (K9MailLib.isDebug()) {
Log.v(LOG_TAG, "Server doesn't support the EHLO command. Trying HELO...");
}
try {
executeSimpleCommand("HELO " + host);
} catch (NegativeSmtpReplyException e2) {
Log.w(K9.LOG_TAG, "Server doesn't support the HELO command. Continuing anyway.");
Log.w(LOG_TAG, "Server doesn't support the HELO command. Continuing anyway.");
}
}
return extensions;
@ -465,7 +466,7 @@ public class SmtpTransport extends Transport {
new HashMap<String, List<String>>();
for (Address address : addresses) {
String addressString = address.getAddress();
String charset = MimeUtility.getCharsetFromAddress(addressString);
String charset = CharsetSupport.getCharsetFromAddress(addressString);
List<String> addressesOfCharset = charsetAddressesMap.get(charset);
if (addressesOfCharset == null) {
addressesOfCharset = new ArrayList<String>();
@ -495,7 +496,7 @@ public class SmtpTransport extends Transport {
}
// If the message has attachments and our server has told us about a limit on
// the size of messages, count the message's size before sending it
if (mLargestAcceptableMessage > 0 && ((LocalMessage)message).hasAttachments()) {
if (mLargestAcceptableMessage > 0 && message.hasAttachments()) {
if (message.calculateSize() > mLargestAcceptableMessage) {
MessagingException me = new MessagingException("Message too large for server");
//TODO this looks rather suspicious... shouldn't it be true?
@ -529,7 +530,7 @@ public class SmtpTransport extends Transport {
// "5xx text" -responses are permanent failures
String msg = e.getMessage();
if (msg != null && msg.startsWith("5")) {
Log.w(K9.LOG_TAG, "handling 5xx SMTP error code as a permanent failure");
Log.w(LOG_TAG, "handling 5xx SMTP error code as a permanent failure");
possibleSend = false;
}
@ -582,21 +583,21 @@ public class SmtpTransport extends Transport {
}
}
String ret = sb.toString();
if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP)
Log.d(K9.LOG_TAG, "SMTP <<< " + ret);
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_SMTP)
Log.d(LOG_TAG, "SMTP <<< " + ret);
return ret;
}
private void writeLine(String s, boolean sensitive) throws IOException {
if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) {
if (K9MailLib.isDebug() && DEBUG_PROTOCOL_SMTP) {
final String commandToLog;
if (sensitive && !K9.DEBUG_SENSITIVE) {
if (sensitive && !K9MailLib.isDebugSensitive()) {
commandToLog = "SMTP >>> *sensitive*";
} else {
commandToLog = "SMTP >>> " + s;
}
Log.d(K9.LOG_TAG, commandToLog);
Log.d(LOG_TAG, commandToLog);
}
byte[] data = s.concat("\r\n").getBytes();
@ -701,8 +702,8 @@ public class SmtpTransport extends Transport {
AuthenticationFailedException, IOException {
try {
executeSimpleCommand("AUTH LOGIN");
executeSimpleCommand(Utility.base64Encode(username), true);
executeSimpleCommand(Utility.base64Encode(password), true);
executeSimpleCommand(Base64.encode(username), true);
executeSimpleCommand(Base64.encode(password), true);
} catch (NegativeSmtpReplyException exception) {
if (exception.getReplyCode() == 535) {
// Authentication credentials invalid
@ -716,7 +717,7 @@ public class SmtpTransport extends Transport {
private void saslAuthPlain(String username, String password) throws MessagingException,
AuthenticationFailedException, IOException {
String data = Utility.base64Encode("\000" + username + "\000" + password);
String data = Base64.encode("\000" + username + "\000" + password);
try {
executeSimpleCommand("AUTH PLAIN " + data, true);
} catch (NegativeSmtpReplyException exception) {
@ -756,7 +757,7 @@ public class SmtpTransport extends Transport {
private void saslAuthExternal(String username) throws MessagingException, IOException {
executeSimpleCommand(
String.format("AUTH EXTERNAL %s",
Utility.base64Encode(username)), false);
Base64.encode(username)), false);
}
/**

View File

@ -3,16 +3,18 @@ package com.fsck.k9.mail.transport;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.mail.K9MailLib;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mail.store.WebDavStore;
import java.util.Collections;
import static com.fsck.k9.mail.K9MailLib.LOG_TAG;
public class WebDavTransport extends Transport {
public static final String TRANSPORT_TYPE = WebDavStore.STORE_TYPE;
@ -43,21 +45,17 @@ public class WebDavTransport extends Transport {
private WebDavStore store;
public WebDavTransport(Account account) throws MessagingException {
if (account.getRemoteStore() instanceof WebDavStore) {
store = (WebDavStore) account.getRemoteStore();
} else {
store = new WebDavStore(account);
}
public WebDavTransport(StoreConfig storeConfig) throws MessagingException {
store = new WebDavStore(storeConfig);
if (K9.DEBUG)
Log.d(K9.LOG_TAG, ">>> New WebDavTransport creation complete");
if (K9MailLib.isDebug())
Log.d(LOG_TAG, ">>> New WebDavTransport creation complete");
}
@Override
public void open() throws MessagingException {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, ">>> open called on WebDavTransport ");
if (K9MailLib.isDebug())
Log.d(LOG_TAG, ">>> open called on WebDavTransport ");
store.getHttpClient();
}

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.IOException;
import java.io.InputStream;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.IOException;
import java.io.InputStream;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.IOException;
import java.io.OutputStream;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.File;
import java.io.FileOutputStream;
@ -28,11 +28,11 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Account.MessageFormat;
import com.fsck.k9.activity.Search;
import com.fsck.k9.controller.MessageRemovalListener;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
@ -51,14 +51,12 @@ import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.internet.MimeUtility.ViewableContainer;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.local.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.local.LockableDatabase.WrappedException;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
import com.fsck.k9.provider.AttachmentProvider;
public class LocalFolder extends Folder implements Serializable {
public class LocalFolder extends Folder<LocalMessage> implements Serializable {
private static final long serialVersionUID = -1973296520918624767L;
@ -80,22 +78,19 @@ public class LocalFolder extends Folder implements Serializable {
private Integer mLastUid = null;
public LocalFolder(LocalStore localStore, String name) {
super(localStore.getAccount());
super();
this.localStore = localStore;
this.mName = name;
if (this.localStore.getAccount().getInboxFolderName().equals(getName())) {
if (getAccount().getInboxFolderName().equals(getName())) {
mSyncClass = FolderClass.FIRST_CLASS;
mPushClass = FolderClass.FIRST_CLASS;
mInTopGroup = true;
}
}
public LocalFolder(LocalStore localStore, long id) {
super(localStore.getAccount());
super();
this.localStore = localStore;
this.mFolderId = id;
}
@ -104,6 +99,23 @@ public class LocalFolder extends Folder implements Serializable {
return mFolderId;
}
public String getAccountUuid()
{
return getAccount().getUuid();
}
public boolean getSignatureUse() {
return getAccount().getSignatureUse();
}
public void setLastSelectedFolderName(String destFolderName) {
getAccount().setLastSelectedFolderName(destFolderName);
}
public boolean syncRemoteDeletions() {
return getAccount().syncRemoteDeletions();
}
@Override
public void open(final int mode) throws MessagingException {
@ -216,7 +228,7 @@ public class LocalFolder extends Folder implements Serializable {
@Override
public boolean create(FolderType type) throws MessagingException {
return create(type, mAccount.getDisplayCount());
return create(type, getAccount().getDisplayCount());
}
@Override
@ -517,25 +529,25 @@ public class LocalFolder extends Folder implements Serializable {
String id = getPrefId();
// there can be a lot of folders. For the defaults, let's not save prefs, saving space, except for INBOX
if (mDisplayClass == FolderClass.NO_CLASS && !mAccount.getInboxFolderName().equals(getName())) {
if (mDisplayClass == FolderClass.NO_CLASS && !getAccount().getInboxFolderName().equals(getName())) {
editor.remove(id + ".displayMode");
} else {
editor.putString(id + ".displayMode", mDisplayClass.name());
}
if (mSyncClass == FolderClass.INHERITED && !mAccount.getInboxFolderName().equals(getName())) {
if (mSyncClass == FolderClass.INHERITED && !getAccount().getInboxFolderName().equals(getName())) {
editor.remove(id + ".syncMode");
} else {
editor.putString(id + ".syncMode", mSyncClass.name());
}
if (mNotifyClass == FolderClass.INHERITED && !mAccount.getInboxFolderName().equals(getName())) {
if (mNotifyClass == FolderClass.INHERITED && !getAccount().getInboxFolderName().equals(getName())) {
editor.remove(id + ".notifyMode");
} else {
editor.putString(id + ".notifyMode", mNotifyClass.name());
}
if (mPushClass == FolderClass.SECOND_CLASS && !mAccount.getInboxFolderName().equals(getName())) {
if (mPushClass == FolderClass.SECOND_CLASS && !getAccount().getInboxFolderName().equals(getName())) {
editor.remove(id + ".pushMode");
} else {
editor.putString(id + ".pushMode", mPushClass.name());
@ -597,7 +609,7 @@ public class LocalFolder extends Folder implements Serializable {
}
@Override
public void fetch(final List<? extends Message> messages, final FetchProfile fp, final MessageRetrievalListener listener)
public void fetch(final List<? extends Message> messages, final FetchProfile fp, final MessageRetrievalListener<LocalMessage> listener)
throws MessagingException {
try {
this.localStore.database.execute(false, new DbCallback<Void>() {
@ -614,7 +626,7 @@ public class LocalFolder extends Folder implements Serializable {
try {
cursor = db.rawQuery("SELECT html_content, text_content, mime_type FROM messages "
+ "WHERE id = ?",
new String[] { Long.toString(localMessage.mId) });
new String[] { Long.toString(localMessage.getId()) });
cursor.moveToNext();
String htmlContent = cursor.getString(0);
String textContent = cursor.getString(1);
@ -629,7 +641,7 @@ public class LocalFolder extends Folder implements Serializable {
mp.addBodyPart(bp);
}
if (mAccount.getMessageFormat() != MessageFormat.TEXT) {
if (getAccount().getMessageFormat() != MessageFormat.TEXT) {
if (htmlContent != null) {
TextBody body = new TextBody(htmlContent);
MimeBodyPart bp = new MimeBodyPart(body, "text/html");
@ -700,7 +712,7 @@ public class LocalFolder extends Folder implements Serializable {
"content_disposition"
},
"message_id = ?",
new String[] { Long.toString(localMessage.mId) },
new String[] { Long.toString(localMessage.getId()) },
null,
null,
null);
@ -813,10 +825,10 @@ public class LocalFolder extends Folder implements Serializable {
* The messages whose headers should be loaded.
* @throws UnavailableStorageException
*/
void populateHeaders(final List<LocalMessage> messages) throws UnavailableStorageException {
void populateHeaders(final List<LocalMessage> messages) throws MessagingException {
this.localStore.database.execute(false, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, MessagingException {
Cursor cursor = null;
if (messages.isEmpty()) {
return null;
@ -1123,14 +1135,14 @@ public class LocalFolder extends Folder implements Serializable {
* @return The local version of the message. Never <code>null</code>.
* @throws MessagingException
*/
public Message storeSmallMessage(final Message message, final Runnable runnable) throws MessagingException {
return this.localStore.database.execute(true, new DbCallback<Message>() {
public LocalMessage storeSmallMessage(final Message message, final Runnable runnable) throws MessagingException {
return this.localStore.database.execute(true, new DbCallback<LocalMessage>() {
@Override
public Message doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
public LocalMessage doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
try {
appendMessages(Collections.singletonList(message));
final String uid = message.getUid();
final Message result = getMessage(uid);
final LocalMessage result = getMessage(uid);
runnable.run();
// Set a flag indicating this message has now be fully downloaded
result.setFlag(Flag.X_DOWNLOADED_FULL, true);
@ -1287,14 +1299,14 @@ public class LocalFolder extends Folder implements Serializable {
// draft messages because this will cause the values stored in
// the identity header to be wrong.
ViewableContainer container =
MimeUtility.extractPartsFromDraft(message);
LocalMessageExtractor.extractPartsFromDraft(message);
text = container.text;
html = container.html;
attachments = container.attachments;
} else {
ViewableContainer container =
MimeUtility.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
LocalMessageExtractor.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
attachments = container.attachments;
text = container.text;
@ -1400,7 +1412,7 @@ public class LocalFolder extends Folder implements Serializable {
message.buildMimeRepresentation();
ViewableContainer container =
MimeUtility.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
LocalMessageExtractor.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
List<Part> attachments = container.attachments;
String text = container.text;
@ -1439,12 +1451,12 @@ public class LocalFolder extends Folder implements Serializable {
message.isSet(Flag.FLAGGED) ? 1 : 0,
message.isSet(Flag.ANSWERED) ? 1 : 0,
message.isSet(Flag.FORWARDED) ? 1 : 0,
message.mId
message.getId()
});
for (int i = 0, count = attachments.size(); i < count; i++) {
Part attachment = attachments.get(i);
saveAttachment(message.mId, attachment, false);
saveAttachment(message.getId(), attachment, false);
}
saveHeaders(message.getId(), message);
} catch (Exception e) {
@ -1473,7 +1485,7 @@ public class LocalFolder extends Folder implements Serializable {
private void saveHeaders(final long id, final MimeMessage message) throws MessagingException {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, MessagingException {
deleteHeaders(id);
for (String name : message.getHeaderNames()) {
@ -1502,7 +1514,7 @@ public class LocalFolder extends Folder implements Serializable {
});
}
void deleteHeaders(final long id) throws UnavailableStorageException {
void deleteHeaders(final long id) throws MessagingException {
this.localStore.database.execute(false, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
@ -1637,7 +1649,7 @@ public class LocalFolder extends Folder implements Serializable {
File attachmentFile = new File(attachmentDirectory, Long.toString(attachmentId));
tempAttachmentFile.renameTo(attachmentFile);
contentUri = AttachmentProvider.getAttachmentUri(
mAccount,
getAccount(),
attachmentId);
if (MimeUtil.isMessage(attachment.getMimeType())) {
attachment.setBody(new LocalAttachmentMessageBody(
@ -1712,7 +1724,7 @@ public class LocalFolder extends Folder implements Serializable {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
db.update("messages", cv, "id = ?", new String[]
{ Long.toString(message.mId) });
{ Long.toString(message.getId()) });
return null;
}
});
@ -1829,7 +1841,7 @@ public class LocalFolder extends Folder implements Serializable {
setPushState(null);
setLastPush(0);
setLastChecked(0);
setVisibleLimit(mAccount.getDisplayCount());
setVisibleLimit(getAccount().getDisplayCount());
}
@Override
@ -1878,7 +1890,7 @@ public class LocalFolder extends Folder implements Serializable {
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
Cursor attachmentsCursor = null;
try {
String accountUuid = mAccount.getUuid();
String accountUuid = getAccountUuid();
Context context = LocalFolder.this.localStore.mApplication;
// Get attachment IDs
@ -2039,7 +2051,7 @@ public class LocalFolder extends Folder implements Serializable {
// Append the first message ID from the "In-Reply-To" header line
String[] inReplyToArray = message.getHeader("In-Reply-To");
String inReplyTo = null;
String inReplyTo;
if (inReplyToArray != null && inReplyToArray.length > 0) {
inReplyTo = Utility.extractMessageId(inReplyToArray[0]);
if (inReplyTo != null) {
@ -2194,4 +2206,8 @@ public class LocalFolder extends Folder implements Serializable {
throw(MessagingException) e.getCause();
}
}
private Account getAccount() {
return localStore.getAccount();
}
}

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.IOException;
import java.io.OutputStream;
@ -12,23 +12,25 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.local.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.local.LockableDatabase.WrappedException;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
public class LocalMessage extends MimeMessage {
protected MessageReference mReference;
private final LocalStore localStore;
long mId;
private long mId;
private int mAttachmentCount;
private String mSubject;
@ -50,8 +52,7 @@ public class LocalMessage extends MimeMessage {
this.mFolder = folder;
}
void populateFromGetMessageCursor(Cursor cursor)
throws MessagingException {
void populateFromGetMessageCursor(Cursor cursor) throws MessagingException {
final String subject = cursor.getString(0);
this.setSubject(subject == null ? "" : subject);
@ -129,7 +130,7 @@ public class LocalMessage extends MimeMessage {
}
} else {
// We successfully found an HTML part; do the necessary character set decoding.
text = MimeUtility.getTextFromPart(part);
text = MessageExtractor.getTextFromPart(this);
}
return text;
}
@ -156,7 +157,7 @@ public class LocalMessage extends MimeMessage {
}
super.setReplyTo(mReplyTo);
super.setSentDate(this.getSentDate());
super.setSentDate(this.getSentDate(), K9.hideTimeZone());
super.setRecipients(RecipientType.TO, mTo);
super.setRecipients(RecipientType.CC, mCc);
super.setRecipients(RecipientType.BCC, mBcc);
@ -189,6 +190,12 @@ public class LocalMessage extends MimeMessage {
mMessageDirty = true;
}
@Override
public void setUid(String uid) {
super.setUid(uid);
this.mReference = null;
}
@Override
public boolean hasAttachments() {
return (mAttachmentCount > 0);
@ -492,44 +499,44 @@ public class LocalMessage extends MimeMessage {
db.delete("threads", "message_id = ?", idArg);
}
private void loadHeaders() throws UnavailableStorageException {
private void loadHeaders() throws MessagingException {
List<LocalMessage> messages = new ArrayList<LocalMessage>();
messages.add(this);
mHeadersLoaded = true; // set true before calling populate headers to stop recursion
((LocalFolder) mFolder).populateHeaders(messages);
getFolder().populateHeaders(messages);
}
@Override
public void addHeader(String name, String value) throws UnavailableStorageException {
public void addHeader(String name, String value) throws MessagingException {
if (!mHeadersLoaded)
loadHeaders();
super.addHeader(name, value);
}
@Override
public void setHeader(String name, String value) throws UnavailableStorageException {
public void setHeader(String name, String value) throws MessagingException {
if (!mHeadersLoaded)
loadHeaders();
super.setHeader(name, value);
}
@Override
public String[] getHeader(String name) throws UnavailableStorageException {
public String[] getHeader(String name) throws MessagingException {
if (!mHeadersLoaded)
loadHeaders();
return super.getHeader(name);
}
@Override
public void removeHeader(String name) throws UnavailableStorageException {
public void removeHeader(String name) throws MessagingException {
if (!mHeadersLoaded)
loadHeaders();
super.removeHeader(name);
}
@Override
public Set<String> getHeaderNames() throws UnavailableStorageException {
public Set<String> getHeaderNames() throws MessagingException {
if (!mHeadersLoaded)
loadHeaders();
return super.getHeaderNames();
@ -557,4 +564,56 @@ public class LocalMessage extends MimeMessage {
public long getRootId() {
return mRootId;
}
}
public Account getAccount() {
return localStore.getAccount();
}
public MessageReference makeMessageReference() {
if (mReference == null) {
mReference = new MessageReference();
mReference.folderName = getFolder().getName();
mReference.uid = mUid;
mReference.accountUuid = getFolder().getAccountUuid();
}
return mReference;
}
@Override
protected void copy(MimeMessage destination) {
super.copy(destination);
if (destination instanceof LocalMessage) {
((LocalMessage)destination).mReference = mReference;
}
}
@Override
public LocalFolder getFolder() {
return (LocalFolder) super.getFolder();
}
public String getUri() {
return "email://messages/" + getAccount().getAccountNumber() + "/" + getFolder().getName() + "/" + getUid();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
final LocalMessage that = (LocalMessage) o;
return !(getAccountUuid() != null ? !getAccountUuid().equals(that.getAccountUuid()) : that.getAccountUuid() != null);
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (getAccountUuid() != null ? getAccountUuid().hashCode() : 0);
return result;
}
private String getAccountUuid() {
return getAccount().getUuid();
}
}

View File

@ -0,0 +1,470 @@
package com.fsck.k9.mailstore;
import android.content.Context;
import com.fsck.k9.R;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.mail.internet.MessageExtractor;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.Viewable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter;
import static com.fsck.k9.mail.internet.Viewable.Alternative;
import static com.fsck.k9.mail.internet.Viewable.Html;
import static com.fsck.k9.mail.internet.Viewable.MessageHeader;
import static com.fsck.k9.mail.internet.Viewable.Text;
import static com.fsck.k9.mail.internet.Viewable.Textual;
class LocalMessageExtractor {
private static final String TEXT_DIVIDER =
"------------------------------------------------------------------------";
private static final int TEXT_DIVIDER_LENGTH = TEXT_DIVIDER.length();
private static final String FILENAME_PREFIX = "----- ";
private static final int FILENAME_PREFIX_LENGTH = FILENAME_PREFIX.length();
private static final String FILENAME_SUFFIX = " ";
private static final int FILENAME_SUFFIX_LENGTH = FILENAME_SUFFIX.length();
private LocalMessageExtractor() {}
/**
* Extract the viewable textual parts of a message and return the rest as attachments.
*
* @param context A {@link android.content.Context} instance that will be used to get localized strings.
* @return A {@link ViewableContainer} instance containing the textual parts of the message as
* plain text and HTML, and a list of message parts considered attachments.
*
* @throws com.fsck.k9.mail.MessagingException
* In case of an error.
*/
public static ViewableContainer extractTextAndAttachments(Context context, Message message) throws MessagingException {
try {
List<Part> attachments = new ArrayList<Part>();
// Collect all viewable parts
List<Viewable> viewables = MessageExtractor.getViewables(message, attachments);
/*
* Convert the tree of viewable parts into text and HTML
*/
// Used to suppress the divider for the first viewable part
boolean hideDivider = true;
StringBuilder text = new StringBuilder();
StringBuilder html = new StringBuilder();
for (Viewable viewable : viewables) {
if (viewable instanceof Textual) {
// This is either a text/plain or text/html part. Fill the variables 'text' and
// 'html', converting between plain text and HTML as necessary.
text.append(buildText(viewable, !hideDivider));
html.append(buildHtml(viewable, !hideDivider));
hideDivider = false;
} else if (viewable instanceof MessageHeader) {
MessageHeader header = (MessageHeader) viewable;
Part containerPart = header.getContainerPart();
Message innerMessage = header.getMessage();
addTextDivider(text, containerPart, !hideDivider);
addMessageHeaderText(context, text, innerMessage);
addHtmlDivider(html, containerPart, !hideDivider);
addMessageHeaderHtml(context, html, innerMessage);
hideDivider = true;
} else if (viewable instanceof Alternative) {
// Handle multipart/alternative contents
Alternative alternative = (Alternative) viewable;
/*
* We made sure at least one of text/plain or text/html is present when
* creating the Alternative object. If one part is not present we convert the
* other one to make sure 'text' and 'html' always contain the same text.
*/
List<Viewable> textAlternative = alternative.getText().isEmpty() ?
alternative.getHtml() : alternative.getText();
List<Viewable> htmlAlternative = alternative.getHtml().isEmpty() ?
alternative.getText() : alternative.getHtml();
// Fill the 'text' variable
boolean divider = !hideDivider;
for (Viewable textViewable : textAlternative) {
text.append(buildText(textViewable, divider));
divider = true;
}
// Fill the 'html' variable
divider = !hideDivider;
for (Viewable htmlViewable : htmlAlternative) {
html.append(buildHtml(htmlViewable, divider));
divider = true;
}
hideDivider = false;
}
}
return new ViewableContainer(text.toString(), html.toString(), attachments);
} catch (Exception e) {
throw new MessagingException("Couldn't extract viewable parts", e);
}
}
public static ViewableContainer extractPartsFromDraft(Message message)
throws MessagingException {
Body body = message.getBody();
if (message.isMimeType("multipart/mixed") && body instanceof MimeMultipart) {
MimeMultipart multipart = (MimeMultipart) body;
ViewableContainer container;
int count = multipart.getCount();
if (count >= 1) {
// The first part is either a text/plain or a multipart/alternative
BodyPart firstPart = multipart.getBodyPart(0);
container = extractTextual(firstPart);
// The rest should be attachments
for (int i = 1; i < count; i++) {
BodyPart bodyPart = multipart.getBodyPart(i);
container.attachments.add(bodyPart);
}
} else {
container = new ViewableContainer("", "", new ArrayList<Part>());
}
return container;
}
return extractTextual(message);
}
/**
* Use the contents of a {@link com.fsck.k9.mail.internet.Viewable} to create the HTML to be displayed.
*
* <p>
* This will use {@link com.fsck.k9.helper.HtmlConverter#textToHtml(String)} to convert plain text parts
* to HTML if necessary.
* </p>
*
* @param viewable
* The viewable part to build the HTML from.
* @param prependDivider
* {@code true}, if the HTML divider should be inserted as first element.
* {@code false}, otherwise.
*
* @return The contents of the supplied viewable instance as HTML.
*/
private static StringBuilder buildHtml(Viewable viewable, boolean prependDivider)
{
StringBuilder html = new StringBuilder();
if (viewable instanceof Textual) {
Part part = ((Textual)viewable).getPart();
addHtmlDivider(html, part, prependDivider);
String t = MessageExtractor.getTextFromPart(part);
if (t == null) {
t = "";
} else if (viewable instanceof Text) {
t = HtmlConverter.textToHtml(t);
}
html.append(t);
} else if (viewable instanceof Alternative) {
// That's odd - an Alternative as child of an Alternative; go ahead and try to use the
// text/html child; fall-back to the text/plain part.
Alternative alternative = (Alternative) viewable;
List<Viewable> htmlAlternative = alternative.getHtml().isEmpty() ?
alternative.getText() : alternative.getHtml();
boolean divider = prependDivider;
for (Viewable htmlViewable : htmlAlternative) {
html.append(buildHtml(htmlViewable, divider));
divider = true;
}
}
return html;
}
private static StringBuilder buildText(Viewable viewable, boolean prependDivider)
{
StringBuilder text = new StringBuilder();
if (viewable instanceof Textual) {
Part part = ((Textual)viewable).getPart();
addTextDivider(text, part, prependDivider);
String t = MessageExtractor.getTextFromPart(part);
if (t == null) {
t = "";
} else if (viewable instanceof Html) {
t = HtmlConverter.htmlToText(t);
}
text.append(t);
} else if (viewable instanceof Alternative) {
// That's odd - an Alternative as child of an Alternative; go ahead and try to use the
// text/plain child; fall-back to the text/html part.
Alternative alternative = (Alternative) viewable;
List<Viewable> textAlternative = alternative.getText().isEmpty() ?
alternative.getHtml() : alternative.getText();
boolean divider = prependDivider;
for (Viewable textViewable : textAlternative) {
text.append(buildText(textViewable, divider));
divider = true;
}
}
return text;
}
/**
* Add an HTML divider between two HTML message parts.
*
* @param html
* The {@link StringBuilder} to append the divider to.
* @param part
* The message part that will follow after the divider. This is used to extract the
* part's name.
* @param prependDivider
* {@code true}, if the divider should be appended. {@code false}, otherwise.
*/
private static void addHtmlDivider(StringBuilder html, Part part, boolean prependDivider) {
if (prependDivider) {
String filename = getPartName(part);
html.append("<p style=\"margin-top: 2.5em; margin-bottom: 1em; border-bottom: 1px solid #000\">");
html.append(filename);
html.append("</p>");
}
}
/**
* Get the name of the message part.
*
* @param part
* The part to get the name for.
*
* @return The (file)name of the part if available. An empty string, otherwise.
*/
private static String getPartName(Part part) {
try {
String disposition = part.getDisposition();
if (disposition != null) {
String name = getHeaderParameter(disposition, "filename");
return (name == null) ? "" : name;
}
}
catch (MessagingException e) { /* ignore */ }
return "";
}
/**
* Add a plain text divider between two plain text message parts.
*
* @param text
* The {@link StringBuilder} to append the divider to.
* @param part
* The message part that will follow after the divider. This is used to extract the
* part's name.
* @param prependDivider
* {@code true}, if the divider should be appended. {@code false}, otherwise.
*/
private static void addTextDivider(StringBuilder text, Part part, boolean prependDivider) {
if (prependDivider) {
String filename = getPartName(part);
text.append("\r\n\r\n");
int len = filename.length();
if (len > 0) {
if (len > TEXT_DIVIDER_LENGTH - FILENAME_PREFIX_LENGTH - FILENAME_SUFFIX_LENGTH) {
filename = filename.substring(0, TEXT_DIVIDER_LENGTH - FILENAME_PREFIX_LENGTH -
FILENAME_SUFFIX_LENGTH - 3) + "...";
}
text.append(FILENAME_PREFIX);
text.append(filename);
text.append(FILENAME_SUFFIX);
text.append(TEXT_DIVIDER.substring(0, TEXT_DIVIDER_LENGTH -
FILENAME_PREFIX_LENGTH - filename.length() - FILENAME_SUFFIX_LENGTH));
} else {
text.append(TEXT_DIVIDER);
}
text.append("\r\n\r\n");
}
}
/**
* Extract important header values from a message to display inline (plain text version).
*
* @param context
* A {@link android.content.Context} instance that will be used to get localized strings.
* @param text
* The {@link StringBuilder} that will receive the (plain text) output.
* @param message
* The message to extract the header values from.
*
* @throws com.fsck.k9.mail.MessagingException
* In case of an error.
*/
private static void addMessageHeaderText(Context context, StringBuilder text, Message message)
throws MessagingException {
// From: <sender>
Address[] from = message.getFrom();
if (from != null && from.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_from));
text.append(' ');
text.append(Address.toString(from));
text.append("\r\n");
}
// To: <recipients>
Address[] to = message.getRecipients(Message.RecipientType.TO);
if (to != null && to.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_to));
text.append(' ');
text.append(Address.toString(to));
text.append("\r\n");
}
// Cc: <recipients>
Address[] cc = message.getRecipients(Message.RecipientType.CC);
if (cc != null && cc.length > 0) {
text.append(context.getString(R.string.message_compose_quote_header_cc));
text.append(' ');
text.append(Address.toString(cc));
text.append("\r\n");
}
// Date: <date>
Date date = message.getSentDate();
if (date != null) {
text.append(context.getString(R.string.message_compose_quote_header_send_date));
text.append(' ');
text.append(date.toString());
text.append("\r\n");
}
// Subject: <subject>
String subject = message.getSubject();
text.append(context.getString(R.string.message_compose_quote_header_subject));
text.append(' ');
if (subject == null) {
text.append(context.getString(R.string.general_no_subject));
} else {
text.append(subject);
}
text.append("\r\n\r\n");
}
/**
* Extract important header values from a message to display inline (HTML version).
*
* @param context
* A {@link android.content.Context} instance that will be used to get localized strings.
* @param html
* The {@link StringBuilder} that will receive the (HTML) output.
* @param message
* The message to extract the header values from.
*
* @throws com.fsck.k9.mail.MessagingException
* In case of an error.
*/
private static void addMessageHeaderHtml(Context context, StringBuilder html, Message message)
throws MessagingException {
html.append("<table style=\"border: 0\">");
// From: <sender>
Address[] from = message.getFrom();
if (from != null && from.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_from),
Address.toString(from));
}
// To: <recipients>
Address[] to = message.getRecipients(Message.RecipientType.TO);
if (to != null && to.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_to),
Address.toString(to));
}
// Cc: <recipients>
Address[] cc = message.getRecipients(Message.RecipientType.CC);
if (cc != null && cc.length > 0) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_cc),
Address.toString(cc));
}
// Date: <date>
Date date = message.getSentDate();
if (date != null) {
addTableRow(html, context.getString(R.string.message_compose_quote_header_send_date),
date.toString());
}
// Subject: <subject>
String subject = message.getSubject();
addTableRow(html, context.getString(R.string.message_compose_quote_header_subject),
(subject == null) ? context.getString(R.string.general_no_subject) : subject);
html.append("</table>");
}
/**
* Output an HTML table two column row with some hardcoded style.
*
* @param html
* The {@link StringBuilder} that will receive the output.
* @param header
* The string to be put in the {@code TH} element.
* @param value
* The string to be put in the {@code TD} element.
*/
private static void addTableRow(StringBuilder html, String header, String value) {
html.append("<tr><th style=\"text-align: left; vertical-align: top;\">");
html.append(header);
html.append("</th>");
html.append("<td>");
html.append(value);
html.append("</td></tr>");
}
private static ViewableContainer extractTextual(Part part) throws MessagingException {
String text = "";
String html = "";
List<Part> attachments = new ArrayList<Part>();
Body firstBody = part.getBody();
if (part.isMimeType("text/plain")) {
String bodyText = MessageExtractor.getTextFromPart(part);
if (bodyText != null) {
text = bodyText;
html = HtmlConverter.textToHtml(text);
}
} else if (part.isMimeType("multipart/alternative") &&
firstBody instanceof MimeMultipart) {
MimeMultipart multipart = (MimeMultipart) firstBody;
for (BodyPart bodyPart : multipart.getBodyParts()) {
String bodyText = MessageExtractor.getTextFromPart(bodyPart);
if (bodyText != null) {
if (text.isEmpty() && bodyPart.isMimeType("text/plain")) {
text = bodyText;
} else if (html.isEmpty() && bodyPart.isMimeType("text/html")) {
html = bodyText;
}
}
}
}
return new ViewableContainer(text, html, attachments);
}
}

View File

@ -1,15 +1,5 @@
package com.fsck.k9.mail.store.local;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
package com.fsck.k9.mailstore;
import android.app.Application;
import android.content.ContentResolver;
@ -21,22 +11,20 @@ import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.local.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.local.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mailstore.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.search.LocalSearch;
@ -44,6 +32,18 @@ import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.Searchfield;
import com.fsck.k9.search.SqlQueryBuilder;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* <pre>
* Implements a SQLite database backed local store for Messages.
@ -56,6 +56,18 @@ public class LocalStore extends Store implements Serializable {
static final String[] EMPTY_STRING_ARRAY = new String[0];
static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/**
* Lock objects indexed by account UUID.
*
* @see #getInstance(Account, Application)
*/
private static ConcurrentMap<String, Object> sAccountLocks = new ConcurrentHashMap<String, Object>();
/**
* Local stores indexed by UUID because the Uri may change due to migration to/from SD-card.
*/
private static ConcurrentMap<String, LocalStore> sLocalStores = new ConcurrentHashMap<String, LocalStore>();
/*
* a String containing the columns getMessages expects to work with
* in the correct order.
@ -138,6 +150,7 @@ public class LocalStore extends Store implements Serializable {
LockableDatabase database;
private ContentResolver mContentResolver;
private final Account mAccount;
/**
* local://localhost/path/to/database/uuid.db
@ -147,7 +160,7 @@ public class LocalStore extends Store implements Serializable {
* @throws UnavailableStorageException if not {@link StorageProvider#isReady(Context)}
*/
public LocalStore(final Account account, final Application application) throws MessagingException {
super(account);
mAccount = account;
database = new LockableDatabase(application, account.getUuid(), new StoreSchemaDefinition(this));
mApplication = application;
@ -158,15 +171,66 @@ public class LocalStore extends Store implements Serializable {
database.open();
}
/**
* Get an instance of a local mail store.
*
* @throws UnavailableStorageException
* if not {@link StorageProvider#isReady(Context)}
*/
public static LocalStore getInstance(Account account, Application application)
throws MessagingException {
String accountUuid = account.getUuid();
// Create new per-account lock object if necessary
sAccountLocks.putIfAbsent(accountUuid, new Object());
// Get the account's lock object
Object lock = sAccountLocks.get(accountUuid);
// Use per-account locks so DatabaseUpgradeService always knows which account database is
// currently upgraded.
synchronized (lock) {
LocalStore store = sLocalStores.get(accountUuid);
if (store == null) {
// Creating a LocalStore instance will create or upgrade the database if
// necessary. This could take some time.
store = new LocalStore(account, application);
sLocalStores.put(accountUuid, store);
}
return store;
}
}
public static void removeAccount(Account account) {
try {
removeInstance(account);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to reset local store for account " + account.getUuid(), e);
}
}
private static void removeInstance(Account account) {
String accountUuid = account.getUuid();
sLocalStores.remove(accountUuid);
}
public void switchLocalStorage(final String newStorageProviderId) throws MessagingException {
database.switchProvider(newStorageProviderId);
}
protected Account getAccount() {
return mAccount;
}
protected SharedPreferences getPreferences() {
return Preferences.getPreferences(mApplication).getPreferences();
}
public long getSize() throws UnavailableStorageException {
public long getSize() throws MessagingException {
final StorageManager storageManager = StorageManager.getInstance(mApplication);
@ -409,7 +473,7 @@ public class LocalStore extends Store implements Serializable {
});
}
public void resetVisibleLimits(int visibleLimit) throws UnavailableStorageException {
public void resetVisibleLimits(int visibleLimit) throws MessagingException {
final ContentValues cv = new ContentValues();
cv.put("visible_limit", Integer.toString(visibleLimit));
database.execute(false, new DbCallback<Void>() {
@ -421,7 +485,7 @@ public class LocalStore extends Store implements Serializable {
});
}
public List<PendingCommand> getPendingCommands() throws UnavailableStorageException {
public List<PendingCommand> getPendingCommands() throws MessagingException {
return database.execute(false, new DbCallback<List<PendingCommand>>() {
@Override
public List<PendingCommand> doDbWork(final SQLiteDatabase db) throws WrappedException {
@ -454,7 +518,7 @@ public class LocalStore extends Store implements Serializable {
});
}
public void addPendingCommand(PendingCommand command) throws UnavailableStorageException {
public void addPendingCommand(PendingCommand command) throws MessagingException {
for (int i = 0; i < command.arguments.length; i++) {
command.arguments[i] = UrlEncodingHelper.encodeUtf8(command.arguments[i]);
}
@ -470,7 +534,7 @@ public class LocalStore extends Store implements Serializable {
});
}
public void removePendingCommand(final PendingCommand command) throws UnavailableStorageException {
public void removePendingCommand(final PendingCommand command) throws MessagingException {
database.execute(false, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
@ -480,7 +544,7 @@ public class LocalStore extends Store implements Serializable {
});
}
public void removePendingCommands() throws UnavailableStorageException {
public void removePendingCommands() throws MessagingException {
database.execute(false, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
@ -612,7 +676,7 @@ public class LocalStore extends Store implements Serializable {
return searchForMessages(null, search);
}
public AttachmentInfo getAttachmentInfo(final String attachmentId) throws UnavailableStorageException {
public AttachmentInfo getAttachmentInfo(final String attachmentId) throws MessagingException {
return database.execute(false, new DbCallback<AttachmentInfo>() {
@Override
public AttachmentInfo doDbWork(final SQLiteDatabase db) throws WrappedException {
@ -653,7 +717,7 @@ public class LocalStore extends Store implements Serializable {
public String type;
}
public void createFolders(final List<LocalFolder> foldersToCreate, final int visibleLimit) throws UnavailableStorageException {
public void createFolders(final List<LocalFolder> foldersToCreate, final int visibleLimit) throws MessagingException {
database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException {

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import com.fsck.k9.mail.internet.TextBody;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.File;
import java.util.concurrent.locks.Lock;
@ -16,8 +16,6 @@ import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.helper.FileHelper;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.UnavailableStorageException;
public class LockableDatabase {
@ -35,9 +33,10 @@ public class LockableDatabase {
* <code>null</code>.
* @return Any relevant data. Can be <code>null</code>.
* @throws WrappedException
* @throws com.fsck.k9.mail.store.UnavailableStorageException
* @throws com.fsck.k9.mail.MessagingException
* @throws com.fsck.k9.mailstore.UnavailableStorageException
*/
T doDbWork(SQLiteDatabase db) throws WrappedException, UnavailableStorageException;
T doDbWork(SQLiteDatabase db) throws WrappedException, MessagingException;
}
public static interface SchemaDefinition {
@ -272,7 +271,7 @@ public class LockableDatabase {
* @return Whatever {@link DbCallback#doDbWork(SQLiteDatabase)} returns.
* @throws UnavailableStorageException
*/
public <T> T execute(final boolean transactional, final DbCallback<T> callback) throws UnavailableStorageException {
public <T> T execute(final boolean transactional, final DbCallback<T> callback) throws MessagingException {
lockRead();
final boolean doTransaction = transactional && inTransaction.get() == null;
try {

View File

@ -1,4 +1,4 @@
package com.fsck.k9.controller;
package com.fsck.k9.mailstore;
import com.fsck.k9.mail.Message;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store;
package com.fsck.k9.mailstore;
import java.io.File;
import java.io.IOException;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.util.ArrayList;
import java.util.List;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.ByteArrayInputStream;
import java.io.File;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
import java.io.IOException;
import java.io.OutputStream;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store.local;
package com.fsck.k9.mailstore;
class ThreadInfo {
public final long threadId;

View File

@ -1,4 +1,4 @@
package com.fsck.k9.mail.store;
package com.fsck.k9.mailstore;
import com.fsck.k9.mail.MessagingException;

View File

@ -0,0 +1,34 @@
package com.fsck.k9.mailstore;
import com.fsck.k9.mail.Part;
import java.util.List;
/**
* Store viewable text of a message as plain text and HTML, and the parts considered
* attachments.
*
* @see LocalMessageExtractor#extractTextAndAttachments(android.content.Context, com.fsck.k9.mail.Message)
*/
class ViewableContainer {
/**
* The viewable text of the message in plain text.
*/
public final String text;
/**
* The viewable text of the message in HTML.
*/
public final String html;
/**
* The parts of the message considered attachments (everything not viewable).
*/
public final List<Part> attachments;
public ViewableContainer(String text, String html, List<Part> attachments) {
this.text = text;
this.html = html;
this.attachments = attachments;
}
}

View File

@ -13,7 +13,7 @@ import com.fsck.k9.Account.SortType;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.Account.FolderMode;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mailstore.StorageManager;
import com.fsck.k9.preferences.Settings.*;
public class AccountSettings {

View File

@ -25,9 +25,9 @@ import android.util.Xml;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.preferences.Settings.InvalidSettingValueException;
import com.fsck.k9.preferences.Settings.SettingsDescription;
@ -223,7 +223,7 @@ public class SettingsExporter {
// Write incoming server settings
ServerSettings incoming = Store.decodeStoreUri(account.getStoreUri());
ServerSettings incoming = RemoteStore.decodeStoreUri(account.getStoreUri());
serializer.startTag(null, INCOMING_SERVER_ELEMENT);
serializer.attribute(null, TYPE_ATTRIBUTE, incoming.type);

View File

@ -22,12 +22,12 @@ import com.fsck.k9.Account;
import com.fsck.k9.Identity;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.filter.Base64;
import com.fsck.k9.mail.store.RemoteStore;
import com.fsck.k9.mail.store.WebDavStore;
import com.fsck.k9.preferences.Settings.InvalidSettingValueException;
@ -376,8 +376,8 @@ public class SettingsImporter {
// Write incoming server settings (storeUri)
ServerSettings incoming = new ImportedServerSettings(account.incoming);
String storeUri = Store.createStoreUri(incoming);
putString(editor, accountKeyPrefix + Account.STORE_URI_KEY, Utility.base64Encode(storeUri));
String storeUri = RemoteStore.createStoreUri(incoming);
putString(editor, accountKeyPrefix + Account.STORE_URI_KEY, Base64.encode(storeUri));
// Mark account as disabled if the AuthType isn't EXTERNAL and the
// settings file didn't contain a password
@ -393,7 +393,7 @@ public class SettingsImporter {
// Write outgoing server settings (transportUri)
ServerSettings outgoing = new ImportedServerSettings(account.outgoing);
String transportUri = Transport.createTransportUri(outgoing);
putString(editor, accountKeyPrefix + Account.TRANSPORT_URI_KEY, Utility.base64Encode(transportUri));
putString(editor, accountKeyPrefix + Account.TRANSPORT_URI_KEY, Base64.encode(transportUri));
/*
* Mark account as disabled if the settings file contained a

View File

@ -11,9 +11,9 @@ import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.filter.Base64;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -54,8 +54,8 @@ public class Storage implements SharedPreferences {
String[] uuids = accountUuids.split(",");
for (String uuid : uuids) {
try {
String storeUriStr = Utility.base64Decode(readValue(mDb, uuid + ".storeUri"));
String transportUriStr = Utility.base64Decode(readValue(mDb, uuid + ".transportUri"));
String storeUriStr = Base64.decode(readValue(mDb, uuid + ".storeUri"));
String transportUriStr = Base64.decode(readValue(mDb, uuid + ".transportUri"));
URI uri = new URI(transportUriStr);
String newUserInfo = null;
@ -77,7 +77,7 @@ public class Storage implements SharedPreferences {
if (newUserInfo != null) {
URI newUri = new URI(uri.getScheme(), newUserInfo, uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
String newTransportUriStr = Utility.base64Encode(newUri.toString());
String newTransportUriStr = Base64.encode(newUri.toString());
writeValue(mDb, uuid + ".transportUri", newTransportUriStr);
}
@ -121,7 +121,7 @@ public class Storage implements SharedPreferences {
if (newUserInfo != null) {
URI newUri = new URI(uri.getScheme(), newUserInfo, uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
String newStoreUriStr = Utility.base64Encode(newUri.toString());
String newStoreUriStr = Base64.encode(newUri.toString());
writeValue(mDb, uuid + ".storeUri", newStoreUriStr);
}
} catch (Exception e) {

View File

@ -15,9 +15,9 @@ import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.local.LocalStore.AttachmentInfo;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.mailstore.LocalStore.AttachmentInfo;
import com.fsck.k9.mailstore.StorageManager;
import java.io.*;
import java.util.List;
@ -215,7 +215,7 @@ public class AttachmentProvider extends ContentProvider {
final AttachmentInfo attachmentInfo;
try {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
attachmentInfo = LocalStore.getLocalInstance(account, K9.app).getAttachmentInfo(id);
attachmentInfo = LocalStore.getInstance(account, K9.app).getAttachmentInfo(id);
} catch (MessagingException e) {
Log.e(K9.LOG_TAG, "Unable to retrieve attachment info from local store for ID: " + id, e);
return null;
@ -269,7 +269,7 @@ public class AttachmentProvider extends ContentProvider {
final Account account = Preferences.getPreferences(getContext()).getAccount(dbName);
try {
final LocalStore localStore = LocalStore.getLocalInstance(account, K9.app);
final LocalStore localStore = LocalStore.getInstance(account, K9.app);
AttachmentInfo attachmentInfo = localStore.getAttachmentInfo(id);
if (FORMAT_VIEW.equals(format) && mimeType != null) {

View File

@ -10,11 +10,11 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.cache.EmailProviderCacheCursor;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.local.LockableDatabase;
import com.fsck.k9.mail.store.local.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.local.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mailstore.LockableDatabase;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
import com.fsck.k9.mailstore.UnavailableStorageException;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.search.SqlQueryBuilder;
import android.content.ContentProvider;
@ -357,6 +357,8 @@ public class EmailProvider extends ContentProvider {
});
} catch (UnavailableStorageException e) {
throw new RuntimeException("Storage not available", e);
} catch (MessagingException e) {
throw new RuntimeException("messaging exception", e);
}
}
@ -427,6 +429,8 @@ public class EmailProvider extends ContentProvider {
});
} catch (UnavailableStorageException e) {
throw new RuntimeException("Storage not available", e);
} catch (MessagingException e) {
throw new RuntimeException("messaging exception", e);
}
}
@ -532,7 +536,10 @@ public class EmailProvider extends ContentProvider {
});
} catch (UnavailableStorageException e) {
throw new RuntimeException("Storage not available", e);
} catch (MessagingException e) {
throw new RuntimeException("messaging exception", e);
}
}
private Cursor getAccountStats(String accountUuid, String[] columns,
@ -594,7 +601,10 @@ public class EmailProvider extends ContentProvider {
});
} catch (UnavailableStorageException e) {
throw new RuntimeException("Storage not available", e);
} catch (MessagingException e) {
throw new RuntimeException("messaging exception", e);
}
}
private Account getAccount(String accountUuid) {

View File

@ -32,8 +32,9 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalMessage;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.search.SearchAccount;
import java.lang.ref.WeakReference;
@ -200,9 +201,9 @@ public class MessageProvider extends ContentProvider {
public static class DeleteUriExtractor implements FieldExtractor<MessageInfoHolder, String> {
@Override
public String getField(final MessageInfoHolder source) {
final Message message = source.message;
final LocalMessage message = source.message;
return CONTENT_URI + "/delete_message/"
+ message.getFolder().getAccount().getAccountNumber() + "/"
+ message.getAccount().getAccountNumber() + "/"
+ message.getFolder().getName() + "/" + message.getUid();
}
}
@ -221,21 +222,21 @@ public class MessageProvider extends ContentProvider {
public static class AccountExtractor implements FieldExtractor<MessageInfoHolder, String> {
@Override
public String getField(final MessageInfoHolder source) {
return source.message.getFolder().getAccount().getDescription();
return source.message.getAccount().getDescription();
}
}
public static class AccountColorExtractor implements FieldExtractor<MessageInfoHolder, Integer> {
@Override
public Integer getField(final MessageInfoHolder source) {
return source.message.getFolder().getAccount().getChipColor();
return source.message.getAccount().getChipColor();
}
}
public static class AccountNumberExtractor implements FieldExtractor<MessageInfoHolder, Integer> {
@Override
public Integer getField(final MessageInfoHolder source) {
return source.message.getFolder().getAccount().getAccountNumber();
return source.message.getAccount().getAccountNumber();
}
}
@ -910,18 +911,18 @@ public class MessageProvider extends ContentProvider {
@Override
public void listLocalMessagesAddMessages(final Account account,
final String folderName, final List<Message> messages) {
final String folderName, final List<LocalMessage> messages) {
// cache fields into local variables for faster access on JVM without JIT
final MessageHelper helper = mMessageHelper;
final List<MessageInfoHolder> holders = mHolders;
final Context context = getContext();
for (final Message message : messages) {
for (final LocalMessage message : messages) {
final MessageInfoHolder messageInfoHolder = new MessageInfoHolder();
final Folder messageFolder = message.getFolder();
final Account messageAccount = messageFolder.getAccount();
final Account messageAccount = messageInfoHolder.message.getAccount();
helper.populate(messageInfoHolder, message, new FolderInfoHolder(context,
messageFolder, messageAccount), messageAccount);
@ -1038,9 +1039,9 @@ public class MessageProvider extends ContentProvider {
}
// get localstore parameter
Message msg = null;
LocalMessage msg = null;
try {
Folder lf = LocalStore.getLocalInstance(myAccount, K9.app).getFolder(folderName);
LocalFolder lf = LocalStore.getInstance(myAccount, K9.app).getFolder(folderName);
int msgCount = lf.getMessageCount();
if (K9.DEBUG) {
Log.d(K9.LOG_TAG, "folder msg count = " + msgCount);

View File

@ -5,8 +5,8 @@ import java.util.List;
import com.fsck.k9.Account;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield;

View File

@ -17,7 +17,7 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.activity.UpgradeDatabases;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mailstore.UnavailableStorageException;
/**
* Service used to upgrade the accounts' databases and/or track the progress of the upgrade.

View File

@ -10,9 +10,8 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.activity.MessageCompose;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mailstore.LocalMessage;
import android.app.PendingIntent;
import android.content.Context;
@ -89,10 +88,10 @@ public class NotificationActionService extends CoreService {
List<MessageReference> refs =
intent.getParcelableArrayListExtra(EXTRA_MESSAGE_LIST);
List<Message> messages = new ArrayList<Message>();
List<LocalMessage> messages = new ArrayList<LocalMessage>();
for (MessageReference ref : refs) {
Message m = ref.restoreToLocalMessage(this);
LocalMessage m = ref.restoreToLocalMessage(this);
if (m != null) {
messages.add(m);
}
@ -103,10 +102,10 @@ public class NotificationActionService extends CoreService {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "NotificationActionService initiating reply");
MessageReference ref = (MessageReference) intent.getParcelableExtra(EXTRA_MESSAGE);
Message message = ref.restoreToLocalMessage(this);
MessageReference ref = intent.getParcelableExtra(EXTRA_MESSAGE);
LocalMessage message = ref.restoreToLocalMessage(this);
if (message != null) {
Intent i = MessageCompose.getActionReplyIntent(this, account, message, false, null);
Intent i = MessageCompose.getActionReplyIntent(this, message, false, null);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
} else {

View File

@ -7,7 +7,7 @@ import android.net.Uri;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mailstore.StorageManager;
/**
* That BroadcastReceiver is only interested in UNMOUNT events.

View File

@ -7,7 +7,7 @@ import android.net.Uri;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mailstore.StorageManager;
/**
* That BroadcastReceiver is only interested in MOUNT events.

View File

@ -43,7 +43,7 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
import com.fsck.k9.mailstore.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider;
import org.apache.commons.io.IOUtils;

View File

@ -217,9 +217,9 @@ public class MessageHeader extends LinearLayout implements OnClickListener {
public void populate(final Message message, final Account account) throws MessagingException {
final Contacts contacts = K9.showContactName() ? mContacts : null;
final CharSequence from = Address.toFriendly(message.getFrom(), contacts);
final CharSequence to = Address.toFriendly(message.getRecipients(Message.RecipientType.TO), contacts);
final CharSequence cc = Address.toFriendly(message.getRecipients(Message.RecipientType.CC), contacts);
final CharSequence from = MessageHelper.toFriendly(message.getFrom(), contacts);
final CharSequence to = MessageHelper.toFriendly(message.getRecipients(Message.RecipientType.TO), contacts);
final CharSequence cc = MessageHelper.toFriendly(message.getRecipients(Message.RecipientType.CC), contacts);
Address[] fromAddrs = message.getFrom();
Address[] toAddrs = message.getRecipients(Message.RecipientType.TO);

Some files were not shown because too many files have changed in this diff Show More