package com.fsck.k9; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Color; import android.net.Uri; import timber.log.Timber; import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.NetworkType; import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Folder.FolderClass; 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.preferences.StorageEditor; import com.fsck.k9.preferences.Storage; import com.fsck.k9.provider.EmailProvider; import com.fsck.k9.provider.EmailProvider.StatsColumns; import com.fsck.k9.search.ConditionsTreeNode; import com.fsck.k9.search.LocalSearch; 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.mail.ssl.LocalKeyStore; import com.fsck.k9.view.ColorChip; import com.larswerkman.colorpicker.ColorPicker; import static com.fsck.k9.Preferences.getEnumStringPref; /** * 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, StoreConfig { /** * Default value for the inbox folder (never changes for POP3 and IMAP) */ private static final String INBOX = "INBOX"; /** * This local folder is used to store messages to be sent. */ public static final String OUTBOX = "K9MAIL_INTERNAL_OUTBOX"; public enum Expunge { EXPUNGE_IMMEDIATELY, EXPUNGE_MANUALLY, EXPUNGE_ON_POLL } public enum DeletePolicy { NEVER(0), SEVEN_DAYS(1), ON_DELETE(2), MARK_AS_READ(3); public final int setting; DeletePolicy(int setting) { this.setting = setting; } public String preferenceString() { return Integer.toString(setting); } public static DeletePolicy fromInt(int initialSetting) { for (DeletePolicy policy: values()) { if (policy.setting == initialSetting) { return policy; } } throw new IllegalArgumentException("DeletePolicy " + initialSetting + " unknown"); } } public static final MessageFormat DEFAULT_MESSAGE_FORMAT = MessageFormat.HTML; public static final boolean DEFAULT_MESSAGE_FORMAT_AUTO = false; public static final boolean DEFAULT_MESSAGE_READ_RECEIPT = false; public static final QuoteStyle DEFAULT_QUOTE_STYLE = QuoteStyle.PREFIX; public static final String DEFAULT_QUOTE_PREFIX = ">"; public static final boolean DEFAULT_QUOTED_TEXT_SHOWN = true; public static final boolean DEFAULT_REPLY_AFTER_QUOTE = false; public static final boolean DEFAULT_STRIP_SIGNATURE = true; public static final int DEFAULT_REMOTE_SEARCH_NUM_RESULTS = 25; public static final String ACCOUNT_DESCRIPTION_KEY = "description"; public static final String STORE_URI_KEY = "storeUri"; public static final String TRANSPORT_URI_KEY = "transportUri"; public static final String IDENTITY_NAME_KEY = "name"; public static final String IDENTITY_EMAIL_KEY = "email"; public static final String IDENTITY_DESCRIPTION_KEY = "description"; /* * http://developer.android.com/design/style/color.html * Note: Order does matter, it's the order in which they will be picked. */ private static final Integer[] PREDEFINED_COLORS = new Integer[] { Color.parseColor("#0099CC"), // blue Color.parseColor("#669900"), // green Color.parseColor("#FF8800"), // orange Color.parseColor("#CC0000"), // red Color.parseColor("#9933CC") // purple }; public enum SortType { SORT_DATE(R.string.sort_earliest_first, R.string.sort_latest_first, false), SORT_ARRIVAL(R.string.sort_earliest_first, R.string.sort_latest_first, false), SORT_SUBJECT(R.string.sort_subject_alpha, R.string.sort_subject_re_alpha, true), SORT_SENDER(R.string.sort_sender_alpha, R.string.sort_sender_re_alpha, true), SORT_UNREAD(R.string.sort_unread_first, R.string.sort_unread_last, true), SORT_FLAGGED(R.string.sort_flagged_first, R.string.sort_flagged_last, true), SORT_ATTACHMENT(R.string.sort_attach_first, R.string.sort_unattached_first, true); private int ascendingToast; private int descendingToast; private boolean defaultAscending; SortType(int ascending, int descending, boolean ndefaultAscending) { ascendingToast = ascending; descendingToast = descending; defaultAscending = ndefaultAscending; } public int getToast(boolean ascending) { return (ascending) ? ascendingToast : descendingToast; } public boolean isDefaultAscending() { return defaultAscending; } } public static final SortType DEFAULT_SORT_TYPE = SortType.SORT_DATE; public static final boolean DEFAULT_SORT_ASCENDING = false; public static final long NO_OPENPGP_KEY = 0; private DeletePolicy deletePolicy = DeletePolicy.NEVER; private final String accountUuid; private String storeUri; /** * Storage provider ID, used to locate and manage the underlying DB/file * storage */ private String localStorageProviderId; private String transportUri; private String description; private String alwaysBcc; private int automaticCheckIntervalMinutes; private int displayCount; private int chipColor; private long latestOldMessageSeenTime; private boolean notifyNewMail; private FolderMode folderNotifyNewMailMode; private boolean notifySelfNewMail; private boolean notifyContactsMailOnly; private String inboxFolderName; private String draftsFolderName; private String sentFolderName; private String trashFolderName; private String archiveFolderName; private String spamFolderName; private String autoExpandFolderName; private FolderMode folderDisplayMode; private FolderMode folderSyncMode; private FolderMode folderPushMode; private FolderMode folderTargetMode; private int accountNumber; private boolean pushPollOnConnect; private boolean notifySync; private SortType sortType; private Map<SortType, Boolean> sortAscending = new HashMap<>(); private ShowPictures showPictures; private boolean isSignatureBeforeQuotedText; private Expunge expungePolicy = Expunge.EXPUNGE_IMMEDIATELY; private int maxPushFolders; private int idleRefreshMinutes; private boolean goToUnreadMessageSearch; private final Map<NetworkType, Boolean> compressionMap = new ConcurrentHashMap<>(); private Searchable searchableFolders; private boolean subscribedFoldersOnly; private int maximumPolledMessageAge; private int maximumAutoDownloadMessageSize; // Tracks if we have sent a notification for this account for // current set of fetched messages private boolean ringNotified; private MessageFormat messageFormat; private boolean messageFormatAuto; private boolean messageReadReceipt; private QuoteStyle quoteStyle; private String quotePrefix; private boolean defaultQuotedTextShown; private boolean replyAfterQuote; private boolean stripSignature; private boolean syncRemoteDeletions; private long pgpCryptoKey; private boolean markMessageAsReadOnView; private boolean alwaysShowCcBcc; private boolean allowRemoteSearch; private boolean remoteSearchFullText; private int remoteSearchNumResults; private ColorChip unreadColorChip; private ColorChip readColorChip; private ColorChip flaggedUnreadColorChip; private ColorChip flaggedReadColorChip; /** * Indicates whether this account is enabled, i.e. ready for use, or not. * * <p> * Right now newly imported accounts are disabled if the settings file didn't contain a * password for the incoming and/or outgoing server. * </p> */ private boolean isEnabled; /** * Name of the folder that was last selected for a copy or move operation. * * Note: For now this value isn't persisted. So it will be reset when * K-9 Mail is restarted. */ private String lastSelectedFolderName = null; private List<Identity> identities; private NotificationSetting notificationSetting = new NotificationSetting(); public enum FolderMode { NONE, ALL, FIRST_CLASS, FIRST_AND_SECOND_CLASS, NOT_SECOND_CLASS } public enum ShowPictures { NEVER, ALWAYS, ONLY_FROM_CONTACTS } public enum Searchable { ALL, DISPLAYABLE, NONE } public enum QuoteStyle { PREFIX, HEADER } public enum MessageFormat { TEXT, HTML, AUTO } protected Account(Context context) { accountUuid = UUID.randomUUID().toString(); localStorageProviderId = StorageManager.getInstance(context).getDefaultProviderId(); automaticCheckIntervalMinutes = -1; idleRefreshMinutes = 24; pushPollOnConnect = true; displayCount = K9.DEFAULT_VISIBLE_LIMIT; accountNumber = -1; notifyNewMail = true; folderNotifyNewMailMode = FolderMode.ALL; notifySync = true; notifySelfNewMail = true; notifyContactsMailOnly = false; folderDisplayMode = FolderMode.NOT_SECOND_CLASS; folderSyncMode = FolderMode.FIRST_CLASS; folderPushMode = FolderMode.FIRST_CLASS; folderTargetMode = FolderMode.NOT_SECOND_CLASS; sortType = DEFAULT_SORT_TYPE; sortAscending.put(DEFAULT_SORT_TYPE, DEFAULT_SORT_ASCENDING); showPictures = ShowPictures.NEVER; isSignatureBeforeQuotedText = false; expungePolicy = Expunge.EXPUNGE_IMMEDIATELY; autoExpandFolderName = INBOX; inboxFolderName = INBOX; maxPushFolders = 10; chipColor = pickColor(context); goToUnreadMessageSearch = false; subscribedFoldersOnly = false; maximumPolledMessageAge = -1; maximumAutoDownloadMessageSize = 32768; messageFormat = DEFAULT_MESSAGE_FORMAT; messageFormatAuto = DEFAULT_MESSAGE_FORMAT_AUTO; messageReadReceipt = DEFAULT_MESSAGE_READ_RECEIPT; quoteStyle = DEFAULT_QUOTE_STYLE; quotePrefix = DEFAULT_QUOTE_PREFIX; defaultQuotedTextShown = DEFAULT_QUOTED_TEXT_SHOWN; replyAfterQuote = DEFAULT_REPLY_AFTER_QUOTE; stripSignature = DEFAULT_STRIP_SIGNATURE; syncRemoteDeletions = true; pgpCryptoKey = NO_OPENPGP_KEY; allowRemoteSearch = false; remoteSearchFullText = false; remoteSearchNumResults = DEFAULT_REMOTE_SEARCH_NUM_RESULTS; isEnabled = true; markMessageAsReadOnView = true; alwaysShowCcBcc = false; searchableFolders = Searchable.ALL; identities = new ArrayList<>(); Identity identity = new Identity(); identity.setSignatureUse(true); identity.setSignature(context.getString(R.string.default_signature)); identity.setDescription(context.getString(R.string.default_identity_description)); identities.add(identity); notificationSetting = new NotificationSetting(); notificationSetting.setVibrate(false); notificationSetting.setVibratePattern(0); notificationSetting.setVibrateTimes(5); notificationSetting.setRingEnabled(true); notificationSetting.setRingtone("content://settings/system/notification_sound"); notificationSetting.setLedColor(chipColor); cacheChips(); } /* * Pick a nice Android guidelines color if we haven't used them all yet. */ private int pickColor(Context context) { List<Account> accounts = Preferences.getPreferences(context).getAccounts(); List<Integer> availableColors = new ArrayList<>(PREDEFINED_COLORS.length); Collections.addAll(availableColors, PREDEFINED_COLORS); for (Account account : accounts) { Integer color = account.getChipColor(); if (availableColors.contains(color)) { availableColors.remove(color); if (availableColors.isEmpty()) { break; } } } return (availableColors.isEmpty()) ? ColorPicker.getRandomColor() : availableColors.get(0); } protected Account(Preferences preferences, String uuid) { this.accountUuid = uuid; loadAccount(preferences); } /** * Load stored settings for this account. */ private synchronized void loadAccount(Preferences preferences) { Storage storage = preferences.getStorage(); storeUri = Base64.decode(storage.getString(accountUuid + ".storeUri", null)); localStorageProviderId = storage.getString( accountUuid + ".localStorageProvider", StorageManager.getInstance(K9.app).getDefaultProviderId()); transportUri = Base64.decode(storage.getString(accountUuid + ".transportUri", null)); description = storage.getString(accountUuid + ".description", null); alwaysBcc = storage.getString(accountUuid + ".alwaysBcc", alwaysBcc); automaticCheckIntervalMinutes = storage.getInt(accountUuid + ".automaticCheckIntervalMinutes", -1); idleRefreshMinutes = storage.getInt(accountUuid + ".idleRefreshMinutes", 24); pushPollOnConnect = storage.getBoolean(accountUuid + ".pushPollOnConnect", true); displayCount = storage.getInt(accountUuid + ".displayCount", K9.DEFAULT_VISIBLE_LIMIT); if (displayCount < 0) { displayCount = K9.DEFAULT_VISIBLE_LIMIT; } latestOldMessageSeenTime = storage.getLong(accountUuid + ".latestOldMessageSeenTime", 0); notifyNewMail = storage.getBoolean(accountUuid + ".notifyNewMail", false); folderNotifyNewMailMode = getEnumStringPref(storage, accountUuid + ".folderNotifyNewMailMode", FolderMode.ALL); notifySelfNewMail = storage.getBoolean(accountUuid + ".notifySelfNewMail", true); notifyContactsMailOnly = storage.getBoolean(accountUuid + ".notifyContactsMailOnly", false); notifySync = storage.getBoolean(accountUuid + ".notifyMailCheck", false); deletePolicy = DeletePolicy.fromInt(storage.getInt(accountUuid + ".deletePolicy", DeletePolicy.NEVER.setting)); inboxFolderName = storage.getString(accountUuid + ".inboxFolderName", INBOX); draftsFolderName = storage.getString(accountUuid + ".draftsFolderName", "Drafts"); sentFolderName = storage.getString(accountUuid + ".sentFolderName", "Sent"); trashFolderName = storage.getString(accountUuid + ".trashFolderName", "Trash"); archiveFolderName = storage.getString(accountUuid + ".archiveFolderName", "Archive"); spamFolderName = storage.getString(accountUuid + ".spamFolderName", "Spam"); expungePolicy = getEnumStringPref(storage, accountUuid + ".expungePolicy", Expunge.EXPUNGE_IMMEDIATELY); syncRemoteDeletions = storage.getBoolean(accountUuid + ".syncRemoteDeletions", true); maxPushFolders = storage.getInt(accountUuid + ".maxPushFolders", 10); goToUnreadMessageSearch = storage.getBoolean(accountUuid + ".goToUnreadMessageSearch", false); subscribedFoldersOnly = storage.getBoolean(accountUuid + ".subscribedFoldersOnly", false); maximumPolledMessageAge = storage.getInt(accountUuid + ".maximumPolledMessageAge", -1); maximumAutoDownloadMessageSize = storage.getInt(accountUuid + ".maximumAutoDownloadMessageSize", 32768); messageFormat = getEnumStringPref(storage, accountUuid + ".messageFormat", DEFAULT_MESSAGE_FORMAT); messageFormatAuto = storage.getBoolean(accountUuid + ".messageFormatAuto", DEFAULT_MESSAGE_FORMAT_AUTO); if (messageFormatAuto && messageFormat == MessageFormat.TEXT) { messageFormat = MessageFormat.AUTO; } messageReadReceipt = storage.getBoolean(accountUuid + ".messageReadReceipt", DEFAULT_MESSAGE_READ_RECEIPT); quoteStyle = getEnumStringPref(storage, accountUuid + ".quoteStyle", DEFAULT_QUOTE_STYLE); quotePrefix = storage.getString(accountUuid + ".quotePrefix", DEFAULT_QUOTE_PREFIX); defaultQuotedTextShown = storage.getBoolean(accountUuid + ".defaultQuotedTextShown", DEFAULT_QUOTED_TEXT_SHOWN); replyAfterQuote = storage.getBoolean(accountUuid + ".replyAfterQuote", DEFAULT_REPLY_AFTER_QUOTE); stripSignature = storage.getBoolean(accountUuid + ".stripSignature", DEFAULT_STRIP_SIGNATURE); for (NetworkType type : NetworkType.values()) { Boolean useCompression = storage.getBoolean(accountUuid + ".useCompression." + type, true); compressionMap.put(type, useCompression); } autoExpandFolderName = storage.getString(accountUuid + ".autoExpandFolderName", INBOX); accountNumber = storage.getInt(accountUuid + ".accountNumber", 0); chipColor = storage.getInt(accountUuid + ".chipColor", ColorPicker.getRandomColor()); sortType = getEnumStringPref(storage, accountUuid + ".sortTypeEnum", SortType.SORT_DATE); sortAscending.put(sortType, storage.getBoolean(accountUuid + ".sortAscending", false)); showPictures = getEnumStringPref(storage, accountUuid + ".showPicturesEnum", ShowPictures.NEVER); notificationSetting.setVibrate(storage.getBoolean(accountUuid + ".vibrate", false)); notificationSetting.setVibratePattern(storage.getInt(accountUuid + ".vibratePattern", 0)); notificationSetting.setVibrateTimes(storage.getInt(accountUuid + ".vibrateTimes", 5)); notificationSetting.setRingEnabled(storage.getBoolean(accountUuid + ".ring", true)); notificationSetting.setRingtone(storage.getString(accountUuid + ".ringtone", "content://settings/system/notification_sound")); notificationSetting.setLed(storage.getBoolean(accountUuid + ".led", true)); notificationSetting.setLedColor(storage.getInt(accountUuid + ".ledColor", chipColor)); folderDisplayMode = getEnumStringPref(storage, accountUuid + ".folderDisplayMode", FolderMode.NOT_SECOND_CLASS); folderSyncMode = getEnumStringPref(storage, accountUuid + ".folderSyncMode", FolderMode.FIRST_CLASS); folderPushMode = getEnumStringPref(storage, accountUuid + ".folderPushMode", FolderMode.FIRST_CLASS); folderTargetMode = getEnumStringPref(storage, accountUuid + ".folderTargetMode", FolderMode.NOT_SECOND_CLASS); searchableFolders = getEnumStringPref(storage, accountUuid + ".searchableFolders", Searchable.ALL); isSignatureBeforeQuotedText = storage.getBoolean(accountUuid + ".signatureBeforeQuotedText", false); identities = loadIdentities(storage); pgpCryptoKey = storage.getLong(accountUuid + ".cryptoKey", NO_OPENPGP_KEY); allowRemoteSearch = storage.getBoolean(accountUuid + ".allowRemoteSearch", false); remoteSearchFullText = storage.getBoolean(accountUuid + ".remoteSearchFullText", false); remoteSearchNumResults = storage.getInt(accountUuid + ".remoteSearchNumResults", DEFAULT_REMOTE_SEARCH_NUM_RESULTS); isEnabled = storage.getBoolean(accountUuid + ".enabled", true); markMessageAsReadOnView = storage.getBoolean(accountUuid + ".markMessageAsReadOnView", true); alwaysShowCcBcc = storage.getBoolean(accountUuid + ".alwaysShowCcBcc", false); cacheChips(); // Use email address as account description if necessary if (description == null) { description = getEmail(); } } protected synchronized void delete(Preferences preferences) { deleteCertificates(); // Get the list of account UUIDs String[] uuids = preferences.getStorage().getString("accountUuids", "").split(","); // Create a list of all account UUIDs excluding this account List<String> newUuids = new ArrayList<>(uuids.length); for (String uuid : uuids) { if (!uuid.equals(accountUuid)) { newUuids.add(uuid); } } StorageEditor editor = preferences.getStorage().edit(); // Only change the 'accountUuids' value if this account's UUID was listed before if (newUuids.size() < uuids.length) { String accountUuids = Utility.combine(newUuids.toArray(), ','); editor.putString("accountUuids", accountUuids); } editor.remove(accountUuid + ".storeUri"); editor.remove(accountUuid + ".transportUri"); editor.remove(accountUuid + ".description"); editor.remove(accountUuid + ".name"); editor.remove(accountUuid + ".email"); editor.remove(accountUuid + ".alwaysBcc"); editor.remove(accountUuid + ".automaticCheckIntervalMinutes"); editor.remove(accountUuid + ".pushPollOnConnect"); editor.remove(accountUuid + ".idleRefreshMinutes"); editor.remove(accountUuid + ".lastAutomaticCheckTime"); editor.remove(accountUuid + ".latestOldMessageSeenTime"); editor.remove(accountUuid + ".notifyNewMail"); editor.remove(accountUuid + ".notifySelfNewMail"); editor.remove(accountUuid + ".deletePolicy"); editor.remove(accountUuid + ".draftsFolderName"); editor.remove(accountUuid + ".sentFolderName"); editor.remove(accountUuid + ".trashFolderName"); editor.remove(accountUuid + ".archiveFolderName"); editor.remove(accountUuid + ".spamFolderName"); editor.remove(accountUuid + ".autoExpandFolderName"); editor.remove(accountUuid + ".accountNumber"); editor.remove(accountUuid + ".vibrate"); editor.remove(accountUuid + ".vibratePattern"); editor.remove(accountUuid + ".vibrateTimes"); editor.remove(accountUuid + ".ring"); editor.remove(accountUuid + ".ringtone"); editor.remove(accountUuid + ".folderDisplayMode"); editor.remove(accountUuid + ".folderSyncMode"); editor.remove(accountUuid + ".folderPushMode"); editor.remove(accountUuid + ".folderTargetMode"); editor.remove(accountUuid + ".signatureBeforeQuotedText"); editor.remove(accountUuid + ".expungePolicy"); editor.remove(accountUuid + ".syncRemoteDeletions"); editor.remove(accountUuid + ".maxPushFolders"); editor.remove(accountUuid + ".searchableFolders"); editor.remove(accountUuid + ".chipColor"); editor.remove(accountUuid + ".led"); editor.remove(accountUuid + ".ledColor"); editor.remove(accountUuid + ".goToUnreadMessageSearch"); editor.remove(accountUuid + ".subscribedFoldersOnly"); editor.remove(accountUuid + ".maximumPolledMessageAge"); editor.remove(accountUuid + ".maximumAutoDownloadMessageSize"); editor.remove(accountUuid + ".messageFormatAuto"); editor.remove(accountUuid + ".quoteStyle"); editor.remove(accountUuid + ".quotePrefix"); editor.remove(accountUuid + ".sortTypeEnum"); editor.remove(accountUuid + ".sortAscending"); editor.remove(accountUuid + ".showPicturesEnum"); editor.remove(accountUuid + ".replyAfterQuote"); editor.remove(accountUuid + ".stripSignature"); editor.remove(accountUuid + ".cryptoApp"); // this is no longer set, but cleans up legacy values editor.remove(accountUuid + ".cryptoAutoSignature"); editor.remove(accountUuid + ".cryptoAutoEncrypt"); editor.remove(accountUuid + ".cryptoApp"); editor.remove(accountUuid + ".cryptoKey"); editor.remove(accountUuid + ".cryptoSupportSignOnly"); editor.remove(accountUuid + ".enabled"); editor.remove(accountUuid + ".markMessageAsReadOnView"); editor.remove(accountUuid + ".alwaysShowCcBcc"); editor.remove(accountUuid + ".allowRemoteSearch"); editor.remove(accountUuid + ".remoteSearchFullText"); editor.remove(accountUuid + ".remoteSearchNumResults"); editor.remove(accountUuid + ".defaultQuotedTextShown"); editor.remove(accountUuid + ".displayCount"); editor.remove(accountUuid + ".inboxFolderName"); editor.remove(accountUuid + ".localStorageProvider"); editor.remove(accountUuid + ".messageFormat"); editor.remove(accountUuid + ".messageReadReceipt"); editor.remove(accountUuid + ".notifyMailCheck"); for (NetworkType type : NetworkType.values()) { editor.remove(accountUuid + ".useCompression." + type.name()); } deleteIdentities(preferences.getStorage(), editor); // TODO: Remove preference settings that may exist for individual // folders in the account. editor.commit(); } private static int findNewAccountNumber(List<Integer> accountNumbers) { int newAccountNumber = -1; Collections.sort(accountNumbers); for (int accountNumber : accountNumbers) { if (accountNumber > newAccountNumber + 1) { break; } newAccountNumber = accountNumber; } newAccountNumber++; return newAccountNumber; } private static List<Integer> getExistingAccountNumbers(Preferences preferences) { List<Account> accounts = preferences.getAccounts(); List<Integer> accountNumbers = new ArrayList<>(accounts.size()); for (Account a : accounts) { accountNumbers.add(a.getAccountNumber()); } return accountNumbers; } public static int generateAccountNumber(Preferences preferences) { List<Integer> accountNumbers = getExistingAccountNumbers(preferences); return findNewAccountNumber(accountNumbers); } public void move(Preferences preferences, boolean moveUp) { String[] uuids = preferences.getStorage().getString("accountUuids", "").split(","); StorageEditor editor = preferences.getStorage().edit(); String[] newUuids = new String[uuids.length]; if (moveUp) { for (int i = 0; i < uuids.length; i++) { if (i > 0 && uuids[i].equals(accountUuid)) { newUuids[i] = newUuids[i-1]; newUuids[i-1] = accountUuid; } else { newUuids[i] = uuids[i]; } } } else { for (int i = uuids.length - 1; i >= 0; i--) { if (i < uuids.length - 1 && uuids[i].equals(accountUuid)) { newUuids[i] = newUuids[i+1]; newUuids[i+1] = accountUuid; } else { newUuids[i] = uuids[i]; } } } String accountUuids = Utility.combine(newUuids, ','); editor.putString("accountUuids", accountUuids); editor.commit(); preferences.loadAccounts(); } public synchronized void save(Preferences preferences) { StorageEditor editor = preferences.getStorage().edit(); if (!preferences.getStorage().getString("accountUuids", "").contains(accountUuid)) { /* * When the account is first created we assign it a unique account number. The * account number will be unique to that account for the lifetime of the account. * So, we get all the existing account numbers, sort them ascending, loop through * the list and check if the number is greater than 1 + the previous number. If so * we use the previous number + 1 as the account number. This refills gaps. * accountNumber starts as -1 on a newly created account. It must be -1 for this * algorithm to work. * * I bet there is a much smarter way to do this. Anyone like to suggest it? */ List<Account> accounts = preferences.getAccounts(); int[] accountNumbers = new int[accounts.size()]; for (int i = 0; i < accounts.size(); i++) { accountNumbers[i] = accounts.get(i).getAccountNumber(); } Arrays.sort(accountNumbers); for (int accountNumber : accountNumbers) { if (accountNumber > this.accountNumber + 1) { break; } this.accountNumber = accountNumber; } accountNumber++; String accountUuids = preferences.getStorage().getString("accountUuids", ""); accountUuids += (accountUuids.length() != 0 ? "," : "") + accountUuid; editor.putString("accountUuids", accountUuids); } editor.putString(accountUuid + ".storeUri", Base64.encode(storeUri)); editor.putString(accountUuid + ".localStorageProvider", localStorageProviderId); editor.putString(accountUuid + ".transportUri", Base64.encode(transportUri)); editor.putString(accountUuid + ".description", description); editor.putString(accountUuid + ".alwaysBcc", alwaysBcc); editor.putInt(accountUuid + ".automaticCheckIntervalMinutes", automaticCheckIntervalMinutes); editor.putInt(accountUuid + ".idleRefreshMinutes", idleRefreshMinutes); editor.putBoolean(accountUuid + ".pushPollOnConnect", pushPollOnConnect); editor.putInt(accountUuid + ".displayCount", displayCount); editor.putLong(accountUuid + ".latestOldMessageSeenTime", latestOldMessageSeenTime); editor.putBoolean(accountUuid + ".notifyNewMail", notifyNewMail); editor.putString(accountUuid + ".folderNotifyNewMailMode", folderNotifyNewMailMode.name()); editor.putBoolean(accountUuid + ".notifySelfNewMail", notifySelfNewMail); editor.putBoolean(accountUuid + ".notifyContactsMailOnly", notifyContactsMailOnly); editor.putBoolean(accountUuid + ".notifyMailCheck", notifySync); editor.putInt(accountUuid + ".deletePolicy", deletePolicy.setting); editor.putString(accountUuid + ".inboxFolderName", inboxFolderName); editor.putString(accountUuid + ".draftsFolderName", draftsFolderName); editor.putString(accountUuid + ".sentFolderName", sentFolderName); editor.putString(accountUuid + ".trashFolderName", trashFolderName); editor.putString(accountUuid + ".archiveFolderName", archiveFolderName); editor.putString(accountUuid + ".spamFolderName", spamFolderName); editor.putString(accountUuid + ".autoExpandFolderName", autoExpandFolderName); editor.putInt(accountUuid + ".accountNumber", accountNumber); editor.putString(accountUuid + ".sortTypeEnum", sortType.name()); editor.putBoolean(accountUuid + ".sortAscending", sortAscending.get(sortType)); editor.putString(accountUuid + ".showPicturesEnum", showPictures.name()); editor.putString(accountUuid + ".folderDisplayMode", folderDisplayMode.name()); editor.putString(accountUuid + ".folderSyncMode", folderSyncMode.name()); editor.putString(accountUuid + ".folderPushMode", folderPushMode.name()); editor.putString(accountUuid + ".folderTargetMode", folderTargetMode.name()); editor.putBoolean(accountUuid + ".signatureBeforeQuotedText", this.isSignatureBeforeQuotedText); editor.putString(accountUuid + ".expungePolicy", expungePolicy.name()); editor.putBoolean(accountUuid + ".syncRemoteDeletions", syncRemoteDeletions); editor.putInt(accountUuid + ".maxPushFolders", maxPushFolders); editor.putString(accountUuid + ".searchableFolders", searchableFolders.name()); editor.putInt(accountUuid + ".chipColor", chipColor); editor.putBoolean(accountUuid + ".goToUnreadMessageSearch", goToUnreadMessageSearch); editor.putBoolean(accountUuid + ".subscribedFoldersOnly", subscribedFoldersOnly); editor.putInt(accountUuid + ".maximumPolledMessageAge", maximumPolledMessageAge); editor.putInt(accountUuid + ".maximumAutoDownloadMessageSize", maximumAutoDownloadMessageSize); if (MessageFormat.AUTO.equals(messageFormat)) { // saving MessageFormat.AUTO as is to the database will cause downgrades to crash on // startup, so we save as MessageFormat.TEXT instead with a separate flag for auto. editor.putString(accountUuid + ".messageFormat", Account.MessageFormat.TEXT.name()); messageFormatAuto = true; } else { editor.putString(accountUuid + ".messageFormat", messageFormat.name()); messageFormatAuto = false; } editor.putBoolean(accountUuid + ".messageFormatAuto", messageFormatAuto); editor.putBoolean(accountUuid + ".messageReadReceipt", messageReadReceipt); editor.putString(accountUuid + ".quoteStyle", quoteStyle.name()); editor.putString(accountUuid + ".quotePrefix", quotePrefix); editor.putBoolean(accountUuid + ".defaultQuotedTextShown", defaultQuotedTextShown); editor.putBoolean(accountUuid + ".replyAfterQuote", replyAfterQuote); editor.putBoolean(accountUuid + ".stripSignature", stripSignature); editor.putLong(accountUuid + ".cryptoKey", pgpCryptoKey); editor.putBoolean(accountUuid + ".allowRemoteSearch", allowRemoteSearch); editor.putBoolean(accountUuid + ".remoteSearchFullText", remoteSearchFullText); editor.putInt(accountUuid + ".remoteSearchNumResults", remoteSearchNumResults); editor.putBoolean(accountUuid + ".enabled", isEnabled); editor.putBoolean(accountUuid + ".markMessageAsReadOnView", markMessageAsReadOnView); editor.putBoolean(accountUuid + ".alwaysShowCcBcc", alwaysShowCcBcc); editor.putBoolean(accountUuid + ".vibrate", notificationSetting.isVibrateEnabled()); editor.putInt(accountUuid + ".vibratePattern", notificationSetting.getVibratePattern()); editor.putInt(accountUuid + ".vibrateTimes", notificationSetting.getVibrateTimes()); editor.putBoolean(accountUuid + ".ring", notificationSetting.isRingEnabled()); editor.putString(accountUuid + ".ringtone", notificationSetting.getRingtone()); editor.putBoolean(accountUuid + ".led", notificationSetting.isLedEnabled()); editor.putInt(accountUuid + ".ledColor", notificationSetting.getLedColor()); for (NetworkType type : NetworkType.values()) { Boolean useCompression = compressionMap.get(type); if (useCompression != null) { editor.putBoolean(accountUuid + ".useCompression." + type, useCompression); } } saveIdentities(preferences.getStorage(), editor); editor.commit(); } private void resetVisibleLimits() { try { getLocalStore().resetVisibleLimits(getDisplayCount()); } catch (MessagingException e) { Timber.e(e, "Unable to reset visible limits"); } } /** * @return <code>null</code> if not available * @throws MessagingException * @see {@link #isAvailable(Context)} */ public AccountStats getStats(Context context) throws MessagingException { if (!isAvailable(context)) { return null; } AccountStats stats = new AccountStats(); ContentResolver cr = context.getContentResolver(); Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + getUuid() + "/stats"); String[] projection = { StatsColumns.UNREAD_COUNT, StatsColumns.FLAGGED_COUNT }; // Create LocalSearch instance to exclude special folders (Trash, Drafts, Spam, Outbox, // Sent) and limit the search to displayable folders. LocalSearch search = new LocalSearch(); excludeSpecialFolders(search); limitToDisplayableFolders(search); // Use the LocalSearch instance to create a WHERE clause to query the content provider StringBuilder query = new StringBuilder(); List<String> queryArgs = new ArrayList<>(); ConditionsTreeNode conditions = search.getConditions(); SqlQueryBuilder.buildWhereClause(this, conditions, query, queryArgs); String selection = query.toString(); String[] selectionArgs = queryArgs.toArray(new String[0]); Cursor cursor = cr.query(uri, projection, selection, selectionArgs, null); try { if (cursor != null && cursor.moveToFirst()) { stats.unreadMessageCount = cursor.getInt(0); stats.flaggedMessageCount = cursor.getInt(1); } } finally { Utility.closeQuietly(cursor); } LocalStore localStore = getLocalStore(); if (K9.measureAccounts()) { stats.size = localStore.getSize(); } return stats; } public synchronized void setChipColor(int color) { chipColor = color; cacheChips(); } private synchronized void cacheChips() { readColorChip = new ColorChip(chipColor, true, ColorChip.CIRCULAR); unreadColorChip = new ColorChip(chipColor, false, ColorChip.CIRCULAR); flaggedReadColorChip = new ColorChip(chipColor, true, ColorChip.STAR); flaggedUnreadColorChip = new ColorChip(chipColor, false, ColorChip.STAR); } public synchronized int getChipColor() { return chipColor; } public ColorChip generateColorChip(boolean messageRead, boolean messageFlagged) { ColorChip chip; if (messageRead) { if (messageFlagged) { chip = flaggedReadColorChip; } else { chip = readColorChip; } } else { if (messageFlagged) { chip = flaggedUnreadColorChip; } else { chip = unreadColorChip; } } return chip; } @Override public String getUuid() { return accountUuid; } public synchronized String getStoreUri() { return storeUri; } public synchronized void setStoreUri(String storeUri) { this.storeUri = storeUri; } public synchronized String getTransportUri() { return transportUri; } public synchronized void setTransportUri(String transportUri) { this.transportUri = transportUri; } @Override public synchronized String getDescription() { return description; } @Override public synchronized void setDescription(String description) { this.description = description; } public synchronized String getName() { return identities.get(0).getName(); } public synchronized void setName(String name) { identities.get(0).setName(name); } public synchronized boolean getSignatureUse() { return identities.get(0).getSignatureUse(); } public synchronized void setSignatureUse(boolean signatureUse) { identities.get(0).setSignatureUse(signatureUse); } public synchronized String getSignature() { return identities.get(0).getSignature(); } public synchronized void setSignature(String signature) { identities.get(0).setSignature(signature); } @Override public synchronized String getEmail() { return identities.get(0).getEmail(); } @Override public synchronized void setEmail(String email) { identities.get(0).setEmail(email); } public synchronized String getAlwaysBcc() { return alwaysBcc; } public synchronized void setAlwaysBcc(String alwaysBcc) { this.alwaysBcc = alwaysBcc; } /* Have we sent a new mail notification on this account */ public boolean isRingNotified() { return ringNotified; } public void setRingNotified(boolean ringNotified) { this.ringNotified = ringNotified; } public String getLocalStorageProviderId() { return localStorageProviderId; } public void setLocalStorageProviderId(String id) { if (!localStorageProviderId.equals(id)) { boolean successful = false; try { switchLocalStorage(id); successful = true; } catch (MessagingException e) { Timber.e(e, "Switching local storage provider from %s to %s failed.", localStorageProviderId, id); } // if migration to/from SD-card failed once, it will fail again. if (!successful) { return; } localStorageProviderId = id; } } /** * Returns -1 for never. */ public synchronized int getAutomaticCheckIntervalMinutes() { return automaticCheckIntervalMinutes; } /** * @param automaticCheckIntervalMinutes or -1 for never. */ public synchronized boolean setAutomaticCheckIntervalMinutes(int automaticCheckIntervalMinutes) { int oldInterval = this.automaticCheckIntervalMinutes; this.automaticCheckIntervalMinutes = automaticCheckIntervalMinutes; return (oldInterval != automaticCheckIntervalMinutes); } public synchronized int getDisplayCount() { return displayCount; } public synchronized void setDisplayCount(int displayCount) { if (displayCount != -1) { this.displayCount = displayCount; } else { this.displayCount = K9.DEFAULT_VISIBLE_LIMIT; } resetVisibleLimits(); } public synchronized long getLatestOldMessageSeenTime() { return latestOldMessageSeenTime; } public synchronized void setLatestOldMessageSeenTime(long latestOldMessageSeenTime) { this.latestOldMessageSeenTime = latestOldMessageSeenTime; } public synchronized boolean isNotifyNewMail() { return notifyNewMail; } public synchronized void setNotifyNewMail(boolean notifyNewMail) { this.notifyNewMail = notifyNewMail; } public synchronized FolderMode getFolderNotifyNewMailMode() { return folderNotifyNewMailMode; } public synchronized void setFolderNotifyNewMailMode(FolderMode folderNotifyNewMailMode) { this.folderNotifyNewMailMode = folderNotifyNewMailMode; } public synchronized DeletePolicy getDeletePolicy() { return deletePolicy; } public synchronized void setDeletePolicy(DeletePolicy deletePolicy) { this.deletePolicy = deletePolicy; } public boolean isSpecialFolder(String folderName) { return (folderName != null && (folderName.equalsIgnoreCase(getInboxFolderName()) || folderName.equals(getTrashFolderName()) || folderName.equals(getDraftsFolderName()) || folderName.equals(getArchiveFolderName()) || folderName.equals(getSpamFolderName()) || folderName.equals(getOutboxFolderName()) || folderName.equals(getSentFolderName()) || folderName.equals(getErrorFolderName()))); } public synchronized String getDraftsFolderName() { return draftsFolderName; } public synchronized void setDraftsFolderName(String name) { draftsFolderName = name; } /** * Checks if this account has a drafts folder set. * @return true if account has a drafts folder set. */ public synchronized boolean hasDraftsFolder() { return !K9.FOLDER_NONE.equalsIgnoreCase(draftsFolderName); } public synchronized String getSentFolderName() { return sentFolderName; } public synchronized String getErrorFolderName() { return K9.ERROR_FOLDER_NAME; } public synchronized void setSentFolderName(String name) { sentFolderName = name; } /** * Checks if this account has a sent folder set. * @return true if account has a sent folder set. */ public synchronized boolean hasSentFolder() { return !K9.FOLDER_NONE.equalsIgnoreCase(sentFolderName); } public synchronized String getTrashFolderName() { return trashFolderName; } public synchronized void setTrashFolderName(String name) { trashFolderName = name; } /** * Checks if this account has a trash folder set. * @return true if account has a trash folder set. */ public synchronized boolean hasTrashFolder() { return !K9.FOLDER_NONE.equalsIgnoreCase(trashFolderName); } public synchronized String getArchiveFolderName() { return archiveFolderName; } public synchronized void setArchiveFolderName(String archiveFolderName) { this.archiveFolderName = archiveFolderName; } /** * Checks if this account has an archive folder set. * @return true if account has an archive folder set. */ public synchronized boolean hasArchiveFolder() { return !K9.FOLDER_NONE.equalsIgnoreCase(archiveFolderName); } public synchronized String getSpamFolderName() { return spamFolderName; } public synchronized void setSpamFolderName(String name) { spamFolderName = name; } /** * Checks if this account has a spam folder set. * @return true if account has a spam folder set. */ public synchronized boolean hasSpamFolder() { return !K9.FOLDER_NONE.equalsIgnoreCase(spamFolderName); } public synchronized String getOutboxFolderName() { return OUTBOX; } public synchronized String getAutoExpandFolderName() { return autoExpandFolderName; } public synchronized void setAutoExpandFolderName(String name) { autoExpandFolderName = name; } public synchronized int getAccountNumber() { return accountNumber; } public synchronized FolderMode getFolderDisplayMode() { return folderDisplayMode; } public synchronized boolean setFolderDisplayMode(FolderMode displayMode) { FolderMode oldDisplayMode = folderDisplayMode; folderDisplayMode = displayMode; return oldDisplayMode != displayMode; } public synchronized FolderMode getFolderSyncMode() { return folderSyncMode; } public synchronized boolean setFolderSyncMode(FolderMode syncMode) { FolderMode oldSyncMode = folderSyncMode; folderSyncMode = syncMode; if (syncMode == FolderMode.NONE && oldSyncMode != FolderMode.NONE) { return true; } if (syncMode != FolderMode.NONE && oldSyncMode == FolderMode.NONE) { return true; } return false; } public synchronized FolderMode getFolderPushMode() { return folderPushMode; } public synchronized boolean setFolderPushMode(FolderMode pushMode) { FolderMode oldPushMode = folderPushMode; folderPushMode = pushMode; return pushMode != oldPushMode; } public synchronized boolean isShowOngoing() { return notifySync; } public synchronized void setShowOngoing(boolean showOngoing) { this.notifySync = showOngoing; } public synchronized SortType getSortType() { return sortType; } public synchronized void setSortType(SortType sortType) { this.sortType = sortType; } public synchronized boolean isSortAscending(SortType sortType) { if (sortAscending.get(sortType) == null) { sortAscending.put(sortType, sortType.isDefaultAscending()); } return sortAscending.get(sortType); } public synchronized void setSortAscending(SortType sortType, boolean sortAscending) { this.sortAscending.put(sortType, sortAscending); } public synchronized ShowPictures getShowPictures() { return showPictures; } public synchronized void setShowPictures(ShowPictures showPictures) { this.showPictures = showPictures; } public synchronized FolderMode getFolderTargetMode() { return folderTargetMode; } public synchronized void setFolderTargetMode(FolderMode folderTargetMode) { this.folderTargetMode = folderTargetMode; } public synchronized boolean isSignatureBeforeQuotedText() { return isSignatureBeforeQuotedText; } public synchronized void setSignatureBeforeQuotedText(boolean mIsSignatureBeforeQuotedText) { this.isSignatureBeforeQuotedText = mIsSignatureBeforeQuotedText; } public synchronized boolean isNotifySelfNewMail() { return notifySelfNewMail; } public synchronized void setNotifySelfNewMail(boolean notifySelfNewMail) { this.notifySelfNewMail = notifySelfNewMail; } public synchronized boolean isNotifyContactsMailOnly() { return notifyContactsMailOnly; } public synchronized void setNotifyContactsMailOnly(boolean notifyContactsMailOnly) { this.notifyContactsMailOnly = notifyContactsMailOnly; } public synchronized Expunge getExpungePolicy() { return expungePolicy; } public synchronized void setExpungePolicy(Expunge expungePolicy) { this.expungePolicy = expungePolicy; } public synchronized int getMaxPushFolders() { return maxPushFolders; } public synchronized boolean setMaxPushFolders(int maxPushFolders) { int oldMaxPushFolders = this.maxPushFolders; this.maxPushFolders = maxPushFolders; return oldMaxPushFolders != maxPushFolders; } public LocalStore getLocalStore() throws MessagingException { return LocalStore.getInstance(this, K9.app); } public Store getRemoteStore() throws MessagingException { return RemoteStore.getInstance(K9.app, this); } // It'd be great if this actually went into the store implementation // to get this, but that's expensive and not easily accessible // during initialization public boolean isSearchByDateCapable() { return (getStoreUri().startsWith("imap")); } @Override public synchronized String toString() { return description; } public synchronized void setCompression(NetworkType networkType, boolean useCompression) { compressionMap.put(networkType, useCompression); } public synchronized boolean useCompression(NetworkType networkType) { Boolean useCompression = compressionMap.get(networkType); if (useCompression == null) { return true; } return useCompression; } @Override public boolean equals(Object o) { if (o instanceof Account) { return ((Account)o).accountUuid.equals(accountUuid); } return super.equals(o); } @Override public int hashCode() { return accountUuid.hashCode(); } private synchronized List<Identity> loadIdentities(Storage storage) { List<Identity> newIdentities = new ArrayList<>(); int ident = 0; boolean gotOne; do { gotOne = false; String name = storage.getString(accountUuid + "." + IDENTITY_NAME_KEY + "." + ident, null); String email = storage.getString(accountUuid + "." + IDENTITY_EMAIL_KEY + "." + ident, null); boolean signatureUse = storage.getBoolean(accountUuid + ".signatureUse." + ident, true); String signature = storage.getString(accountUuid + ".signature." + ident, null); String description = storage.getString(accountUuid + "." + IDENTITY_DESCRIPTION_KEY + "." + ident, null); final String replyTo = storage.getString(accountUuid + ".replyTo." + ident, null); if (email != null) { Identity identity = new Identity(); identity.setName(name); identity.setEmail(email); identity.setSignatureUse(signatureUse); identity.setSignature(signature); identity.setDescription(description); identity.setReplyTo(replyTo); newIdentities.add(identity); gotOne = true; } ident++; } while (gotOne); if (newIdentities.isEmpty()) { String name = storage.getString(accountUuid + ".name", null); String email = storage.getString(accountUuid + ".email", null); boolean signatureUse = storage.getBoolean(accountUuid + ".signatureUse", true); String signature = storage.getString(accountUuid + ".signature", null); Identity identity = new Identity(); identity.setName(name); identity.setEmail(email); identity.setSignatureUse(signatureUse); identity.setSignature(signature); identity.setDescription(email); newIdentities.add(identity); } return newIdentities; } private synchronized void deleteIdentities(Storage storage, StorageEditor editor) { int ident = 0; boolean gotOne; do { gotOne = false; String email = storage.getString(accountUuid + "." + IDENTITY_EMAIL_KEY + "." + ident, null); if (email != null) { editor.remove(accountUuid + "." + IDENTITY_NAME_KEY + "." + ident); editor.remove(accountUuid + "." + IDENTITY_EMAIL_KEY + "." + ident); editor.remove(accountUuid + ".signatureUse." + ident); editor.remove(accountUuid + ".signature." + ident); editor.remove(accountUuid + "." + IDENTITY_DESCRIPTION_KEY + "." + ident); editor.remove(accountUuid + ".replyTo." + ident); gotOne = true; } ident++; } while (gotOne); } private synchronized void saveIdentities(Storage storage, StorageEditor editor) { deleteIdentities(storage, editor); int ident = 0; for (Identity identity : identities) { editor.putString(accountUuid + "." + IDENTITY_NAME_KEY + "." + ident, identity.getName()); editor.putString(accountUuid + "." + IDENTITY_EMAIL_KEY + "." + ident, identity.getEmail()); editor.putBoolean(accountUuid + ".signatureUse." + ident, identity.getSignatureUse()); editor.putString(accountUuid + ".signature." + ident, identity.getSignature()); editor.putString(accountUuid + "." + IDENTITY_DESCRIPTION_KEY + "." + ident, identity.getDescription()); editor.putString(accountUuid + ".replyTo." + ident, identity.getReplyTo()); ident++; } } public synchronized List<Identity> getIdentities() { return identities; } public synchronized void setIdentities(List<Identity> newIdentities) { identities = new ArrayList<>(newIdentities); } public synchronized Identity getIdentity(int i) { if (i < identities.size()) { return identities.get(i); } throw new IllegalArgumentException("Identity with index " + i + " not found"); } public boolean isAnIdentity(Address[] addrs) { if (addrs == null) { return false; } for (Address addr : addrs) { if (findIdentity(addr) != null) { return true; } } return false; } public boolean isAnIdentity(Address addr) { return findIdentity(addr) != null; } public synchronized Identity findIdentity(Address addr) { for (Identity identity : identities) { String email = identity.getEmail(); if (email != null && email.equalsIgnoreCase(addr.getAddress())) { return identity; } } return null; } public synchronized Searchable getSearchableFolders() { return searchableFolders; } public synchronized void setSearchableFolders(Searchable searchableFolders) { this.searchableFolders = searchableFolders; } public synchronized int getIdleRefreshMinutes() { return idleRefreshMinutes; } public synchronized void setIdleRefreshMinutes(int idleRefreshMinutes) { this.idleRefreshMinutes = idleRefreshMinutes; } public synchronized boolean isPushPollOnConnect() { return pushPollOnConnect; } public synchronized void setPushPollOnConnect(boolean pushPollOnConnect) { this.pushPollOnConnect = pushPollOnConnect; } /** * Are we storing out localStore on the SD-card instead of the local device * memory?<br/> * Only to be called during initial account-setup!<br/> * Side-effect: changes {@link #localStorageProviderId}. * * @param newStorageProviderId * Never <code>null</code>. * @throws MessagingException */ private void switchLocalStorage(final String newStorageProviderId) throws MessagingException { if (!localStorageProviderId.equals(newStorageProviderId)) { getLocalStore().switchLocalStorage(newStorageProviderId); } } public synchronized boolean goToUnreadMessageSearch() { return goToUnreadMessageSearch; } public synchronized void setGoToUnreadMessageSearch(boolean goToUnreadMessageSearch) { this.goToUnreadMessageSearch = goToUnreadMessageSearch; } public synchronized boolean subscribedFoldersOnly() { return subscribedFoldersOnly; } public synchronized void setSubscribedFoldersOnly(boolean subscribedFoldersOnly) { this.subscribedFoldersOnly = subscribedFoldersOnly; } public synchronized int getMaximumPolledMessageAge() { return maximumPolledMessageAge; } public synchronized void setMaximumPolledMessageAge(int maximumPolledMessageAge) { this.maximumPolledMessageAge = maximumPolledMessageAge; } public synchronized int getMaximumAutoDownloadMessageSize() { return maximumAutoDownloadMessageSize; } public synchronized void setMaximumAutoDownloadMessageSize(int maximumAutoDownloadMessageSize) { this.maximumAutoDownloadMessageSize = maximumAutoDownloadMessageSize; } public Date getEarliestPollDate() { int age = getMaximumPolledMessageAge(); if (age >= 0) { Calendar now = Calendar.getInstance(); now.set(Calendar.HOUR_OF_DAY, 0); now.set(Calendar.MINUTE, 0); now.set(Calendar.SECOND, 0); now.set(Calendar.MILLISECOND, 0); if (age < 28) { now.add(Calendar.DATE, age * -1); } else switch (age) { case 28: now.add(Calendar.MONTH, -1); break; case 56: now.add(Calendar.MONTH, -2); break; case 84: now.add(Calendar.MONTH, -3); break; case 168: now.add(Calendar.MONTH, -6); break; case 365: now.add(Calendar.YEAR, -1); break; } return now.getTime(); } return null; } public MessageFormat getMessageFormat() { return messageFormat; } public void setMessageFormat(MessageFormat messageFormat) { this.messageFormat = messageFormat; } public synchronized boolean isMessageReadReceiptAlways() { return messageReadReceipt; } public synchronized void setMessageReadReceipt(boolean messageReadReceipt) { this.messageReadReceipt = messageReadReceipt; } public QuoteStyle getQuoteStyle() { return quoteStyle; } public void setQuoteStyle(QuoteStyle quoteStyle) { this.quoteStyle = quoteStyle; } public synchronized String getQuotePrefix() { return quotePrefix; } public synchronized void setQuotePrefix(String quotePrefix) { this.quotePrefix = quotePrefix; } public synchronized boolean isDefaultQuotedTextShown() { return defaultQuotedTextShown; } public synchronized void setDefaultQuotedTextShown(boolean shown) { defaultQuotedTextShown = shown; } public synchronized boolean isReplyAfterQuote() { return replyAfterQuote; } public synchronized void setReplyAfterQuote(boolean replyAfterQuote) { this.replyAfterQuote = replyAfterQuote; } public synchronized boolean isStripSignature() { return stripSignature; } public synchronized void setStripSignature(boolean stripSignature) { this.stripSignature = stripSignature; } public long getCryptoKey() { return pgpCryptoKey; } public void setCryptoKey(long keyId) { pgpCryptoKey = keyId; } public boolean allowRemoteSearch() { return allowRemoteSearch; } public void setAllowRemoteSearch(boolean val) { allowRemoteSearch = val; } public int getRemoteSearchNumResults() { return remoteSearchNumResults; } public void setRemoteSearchNumResults(int val) { remoteSearchNumResults = (val >= 0 ? val : 0); } public String getInboxFolderName() { return inboxFolderName; } public void setInboxFolderName(String name) { this.inboxFolderName = name; } public synchronized boolean syncRemoteDeletions() { return syncRemoteDeletions; } public synchronized void setSyncRemoteDeletions(boolean syncRemoteDeletions) { this.syncRemoteDeletions = syncRemoteDeletions; } public synchronized String getLastSelectedFolderName() { return lastSelectedFolderName; } public synchronized void setLastSelectedFolderName(String folderName) { lastSelectedFolderName = folderName; } public synchronized NotificationSetting getNotificationSetting() { return notificationSetting; } /** * @return <code>true</code> if our {@link StorageProvider} is ready. (e.g. * card inserted) */ public boolean isAvailable(Context context) { String localStorageProviderId = getLocalStorageProviderId(); boolean storageProviderIsInternalMemory = localStorageProviderId == null; return storageProviderIsInternalMemory || StorageManager.getInstance(context).isReady(localStorageProviderId); } public synchronized boolean isEnabled() { return isEnabled; } public synchronized void setEnabled(boolean enabled) { isEnabled = enabled; } public synchronized boolean isMarkMessageAsReadOnView() { return markMessageAsReadOnView; } public synchronized void setMarkMessageAsReadOnView(boolean value) { markMessageAsReadOnView = value; } public synchronized boolean isAlwaysShowCcBcc() { return alwaysShowCcBcc; } public synchronized void setAlwaysShowCcBcc(boolean show) { alwaysShowCcBcc = show; } public boolean isRemoteSearchFullText() { return false; // Temporarily disabled //return remoteSearchFullText; } public void setRemoteSearchFullText(boolean val) { remoteSearchFullText = val; } /** * Modify the supplied {@link LocalSearch} instance to limit the search to displayable folders. * * <p> * This method uses the current folder display mode to decide what folders to include/exclude. * </p> * * @param search * The {@code LocalSearch} instance to modify. * * @see #getFolderDisplayMode() */ public void limitToDisplayableFolders(LocalSearch search) { final Account.FolderMode displayMode = getFolderDisplayMode(); switch (displayMode) { case FIRST_CLASS: { // Count messages in the INBOX and non-special first class folders search.and(SearchField.DISPLAY_CLASS, FolderClass.FIRST_CLASS.name(), Attribute.EQUALS); break; } case FIRST_AND_SECOND_CLASS: { // Count messages in the INBOX and non-special first and second class folders search.and(SearchField.DISPLAY_CLASS, FolderClass.FIRST_CLASS.name(), Attribute.EQUALS); // TODO: Create a proper interface for creating arbitrary condition trees SearchCondition searchCondition = new SearchCondition(SearchField.DISPLAY_CLASS, Attribute.EQUALS, FolderClass.SECOND_CLASS.name()); ConditionsTreeNode root = search.getConditions(); if (root.mRight != null) { root.mRight.or(searchCondition); } else { search.or(searchCondition); } break; } case NOT_SECOND_CLASS: { // Count messages in the INBOX and non-special non-second-class folders search.and(SearchField.DISPLAY_CLASS, FolderClass.SECOND_CLASS.name(), Attribute.NOT_EQUALS); break; } default: case ALL: { // Count messages in the INBOX and non-special folders break; } } } /** * Modify the supplied {@link LocalSearch} instance to exclude special folders. * * <p> * Currently the following folders are excluded: * <ul> * <li>Trash</li> * <li>Drafts</li> * <li>Spam</li> * <li>Outbox</li> * <li>Sent</li> * </ul> * The Inbox will always be included even if one of the special folders is configured to point * to the Inbox. * </p> * * @param search * The {@code LocalSearch} instance to modify. */ public void excludeSpecialFolders(LocalSearch search) { excludeSpecialFolder(search, getTrashFolderName()); excludeSpecialFolder(search, getDraftsFolderName()); excludeSpecialFolder(search, getSpamFolderName()); excludeSpecialFolder(search, getOutboxFolderName()); excludeSpecialFolder(search, getSentFolderName()); excludeSpecialFolder(search, getErrorFolderName()); search.or(new SearchCondition(SearchField.FOLDER, Attribute.EQUALS, getInboxFolderName())); } /** * Modify the supplied {@link LocalSearch} instance to exclude "unwanted" folders. * * <p> * Currently the following folders are excluded: * <ul> * <li>Trash</li> * <li>Spam</li> * <li>Outbox</li> * </ul> * The Inbox will always be included even if one of the special folders is configured to point * to the Inbox. * </p> * * @param search * The {@code LocalSearch} instance to modify. */ public void excludeUnwantedFolders(LocalSearch search) { excludeSpecialFolder(search, getTrashFolderName()); excludeSpecialFolder(search, getSpamFolderName()); excludeSpecialFolder(search, getOutboxFolderName()); search.or(new SearchCondition(SearchField.FOLDER, Attribute.EQUALS, getInboxFolderName())); } private void excludeSpecialFolder(LocalSearch search, String folderName) { if (folderName != null && !K9.FOLDER_NONE.equals(folderName)) { search.and(SearchField.FOLDER, folderName, Attribute.NOT_EQUALS); } } /** * Add a new certificate for the incoming or outgoing server to the local key store. */ public void addCertificate(CheckDirection direction, X509Certificate certificate) throws CertificateException { Uri uri; if (direction == CheckDirection.INCOMING) { uri = Uri.parse(getStoreUri()); } else { uri = Uri.parse(getTransportUri()); } LocalKeyStore localKeyStore = LocalKeyStore.getInstance(); localKeyStore.addCertificate(uri.getHost(), uri.getPort(), certificate); } /** * Examine the existing settings for an account. If the old host/port is different from the * new host/port, then try and delete any (possibly non-existent) certificate stored for the * old host/port. */ public void deleteCertificate(String newHost, int newPort, CheckDirection direction) { Uri uri; if (direction == CheckDirection.INCOMING) { uri = Uri.parse(getStoreUri()); } else { uri = Uri.parse(getTransportUri()); } String oldHost = uri.getHost(); int oldPort = uri.getPort(); if (oldPort == -1) { // This occurs when a new account is created return; } if (!newHost.equals(oldHost) || newPort != oldPort) { LocalKeyStore localKeyStore = LocalKeyStore.getInstance(); localKeyStore.deleteCertificate(oldHost, oldPort); } } /** * Examine the settings for the account and attempt to delete (possibly non-existent) * certificates for the incoming and outgoing servers. */ private void deleteCertificates() { LocalKeyStore localKeyStore = LocalKeyStore.getInstance(); String storeUri = getStoreUri(); if (storeUri != null) { Uri uri = Uri.parse(storeUri); localKeyStore.deleteCertificate(uri.getHost(), uri.getPort()); } String transportUri = getTransportUri(); if (transportUri != null) { Uri uri = Uri.parse(transportUri); localKeyStore.deleteCertificate(uri.getHost(), uri.getPort()); } } }