diff --git a/app/core/src/main/java/com/fsck/k9/Account.java b/app/core/src/main/java/com/fsck/k9/Account.java deleted file mode 100644 index ba7c7ce6f..000000000 --- a/app/core/src/main/java/com/fsck/k9/Account.java +++ /dev/null @@ -1,1108 +0,0 @@ - -package com.fsck.k9; - - -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import com.fsck.k9.backend.api.SyncConfig.ExpungePolicy; -import com.fsck.k9.mail.Address; -import com.fsck.k9.mail.NetworkType; -import com.fsck.k9.mail.ServerSettings; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - - -/** - * Account stores all of the settings for a single account defined by the user. Each account is defined by a UUID. - */ -public class Account implements BaseAccount { - /** - * Fixed name of outbox - not actually displayed. - */ - public static final String OUTBOX_NAME = "Outbox"; - - public enum Expunge { - EXPUNGE_IMMEDIATELY, - EXPUNGE_MANUALLY, - EXPUNGE_ON_POLL; - - public ExpungePolicy toBackendExpungePolicy() { - switch (this) { - case EXPUNGE_IMMEDIATELY: return ExpungePolicy.IMMEDIATELY; - case EXPUNGE_MANUALLY: return ExpungePolicy.MANUALLY; - case EXPUNGE_ON_POLL: return ExpungePolicy.ON_POLL; - } - - throw new AssertionError("Unhandled case: " + this.name()); - } - } - - 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 static DeletePolicy fromInt(int initialSetting) { - for (DeletePolicy policy: values()) { - if (policy.setting == initialSetting) { - return policy; - } - } - throw new IllegalArgumentException("DeletePolicy " + initialSetting + " unknown"); - } - } - - public enum SortType { - SORT_DATE(false), - SORT_ARRIVAL(false), - SORT_SUBJECT(true), - SORT_SENDER(true), - SORT_UNREAD(true), - SORT_FLAGGED(true), - SORT_ATTACHMENT(true); - - private boolean defaultAscending; - - SortType(boolean defaultAscending) { - this.defaultAscending = defaultAscending; - } - - 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; - public static final int UNASSIGNED_ACCOUNT_NUMBER = -1; - - public static final int INTERVAL_MINUTES_NEVER = -1; - public static final int DEFAULT_SYNC_INTERVAL = 60; - - private DeletePolicy deletePolicy = DeletePolicy.NEVER; - - private final String accountUuid; - private ServerSettings incomingServerSettings; - private ServerSettings outgoingServerSettings; - - /** - * Storage provider ID, used to locate and manage the underlying DB/file - * storage - */ - private String localStorageProviderId; - private String name; - private String alwaysBcc; - private int automaticCheckIntervalMinutes; - private int displayCount; - private int chipColor; - private boolean notifyNewMail; - private FolderMode folderNotifyNewMailMode; - private boolean notifySelfNewMail; - private boolean notifyContactsMailOnly; - private boolean ignoreChatMessages; - private String legacyInboxFolder; - private String importedDraftsFolder; - private String importedSentFolder; - private String importedTrashFolder; - private String importedArchiveFolder; - private String importedSpamFolder; - private Long inboxFolderId; - private Long outboxFolderId; - private Long draftsFolderId; - private Long sentFolderId; - private Long trashFolderId; - private Long archiveFolderId; - private Long spamFolderId; - private SpecialFolderSelection draftsFolderSelection; - private SpecialFolderSelection sentFolderSelection; - private SpecialFolderSelection trashFolderSelection; - private SpecialFolderSelection archiveFolderSelection; - private SpecialFolderSelection spamFolderSelection; - private String importedAutoExpandFolder; - private Long autoExpandFolderId; - private FolderMode folderDisplayMode; - private FolderMode folderSyncMode; - private FolderMode folderPushMode; - private FolderMode folderTargetMode; - private int accountNumber; - private boolean notifySync; - private SortType sortType; - private Map sortAscending = new HashMap<>(); - private ShowPictures showPictures; - private boolean isSignatureBeforeQuotedText; - private Expunge expungePolicy = Expunge.EXPUNGE_IMMEDIATELY; - private int maxPushFolders; - private int idleRefreshMinutes; - private final Map compressionMap = new ConcurrentHashMap<>(); - private Searchable searchableFolders; - private boolean subscribedFoldersOnly; - private int maximumPolledMessageAge; - private int maximumAutoDownloadMessageSize; - 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 String openPgpProvider; - private long openPgpKey; - private boolean autocryptPreferEncryptMutual; - private boolean openPgpHideSignOnly; - private boolean openPgpEncryptSubject; - private boolean openPgpEncryptAllDrafts; - private boolean markMessageAsReadOnView; - private boolean markMessageAsReadOnDelete; - private boolean alwaysShowCcBcc; - private boolean remoteSearchFullText; - private int remoteSearchNumResults; - private boolean uploadSentMessages; - private long lastSyncTime; - private long lastFolderListRefreshTime; - private boolean isFinishedSetup = false; - private int messagesNotificationChannelVersion; - - private boolean changedVisibleLimits = false; - - /** - * Database ID 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 Long lastSelectedFolderId = null; - - private List identities; - - private final NotificationSetting notificationSetting = new NotificationSetting(); - - public enum FolderMode { - NONE, ALL, FIRST_CLASS, FIRST_AND_SECOND_CLASS, NOT_SECOND_CLASS - } - - public enum SpecialFolderSelection { - AUTOMATIC, - MANUAL - } - - 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 - } - - - public Account(String uuid) { - this.accountUuid = uuid; - } - - public synchronized void setChipColor(int color) { - chipColor = color; - } - - public synchronized int getChipColor() { - return chipColor; - } - - @Override - public String getUuid() { - return accountUuid; - } - - public synchronized ServerSettings getIncomingServerSettings() { - return incomingServerSettings; - } - - public synchronized void setIncomingServerSettings(ServerSettings incomingServerSettings) { - this.incomingServerSettings = incomingServerSettings; - } - - public synchronized ServerSettings getOutgoingServerSettings() { - return outgoingServerSettings; - } - - public synchronized void setOutgoingServerSettings(ServerSettings outgoingServerSettings) { - this.outgoingServerSettings = outgoingServerSettings; - } - - @Override - public synchronized String getName() { - return name; - } - - public synchronized void setName(String name) { - if (name == null || name.isEmpty()) { - this.name = null; - } else { - this.name = name; - } - } - - public String getDisplayName() { - return name != null ? name : getEmail(); - } - - public synchronized String getSenderName() { - return identities.get(0).getName(); - } - - public synchronized void setSenderName(String name) { - Identity newIdentity = identities.get(0).withName(name); - identities.set(0, newIdentity); - } - - public synchronized boolean getSignatureUse() { - return identities.get(0).getSignatureUse(); - } - - public synchronized void setSignatureUse(boolean signatureUse) { - Identity newIdentity = identities.get(0).withSignatureUse(signatureUse); - identities.set(0, newIdentity); - } - - public synchronized String getSignature() { - return identities.get(0).getSignature(); - } - - public synchronized void setSignature(String signature) { - Identity newIdentity = identities.get(0).withSignature(signature); - identities.set(0, newIdentity); - } - - @Override - public synchronized String getEmail() { - return identities.get(0).getEmail(); - } - - public synchronized void setEmail(String email) { - Identity newIdentity = identities.get(0).withEmail(email); - identities.set(0, newIdentity); - } - - public synchronized String getAlwaysBcc() { - return alwaysBcc; - } - - public synchronized void setAlwaysBcc(String alwaysBcc) { - this.alwaysBcc = alwaysBcc; - } - - public String getLocalStorageProviderId() { - return localStorageProviderId; - } - - public void setLocalStorageProviderId(String id) { - 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 (this.displayCount == displayCount) { - return; - } - - if (displayCount != -1) { - this.displayCount = displayCount; - } else { - this.displayCount = K9.DEFAULT_VISIBLE_LIMIT; - } - - changedVisibleLimits = true; - } - - 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 synchronized String getImportedDraftsFolder() { - return importedDraftsFolder; - } - - public synchronized void setImportedDraftsFolder(String folderServerId) { - importedDraftsFolder = folderServerId; - } - - @Nullable - public synchronized Long getDraftsFolderId() { - return draftsFolderId; - } - - public synchronized void setDraftsFolderId(@Nullable Long folderId) { - draftsFolderId = folderId; - } - - public synchronized void setDraftsFolderId(@Nullable Long folderId, SpecialFolderSelection selection) { - draftsFolderId = folderId; - draftsFolderSelection = selection; - } - - public synchronized boolean hasDraftsFolder() { - return draftsFolderId != null; - } - - public synchronized String getImportedSentFolder() { - return importedSentFolder; - } - - public synchronized void setImportedSentFolder(String folderServerId) { - importedSentFolder = folderServerId; - } - - @Nullable - public synchronized Long getSentFolderId() { - return sentFolderId; - } - - public synchronized void setSentFolderId(@Nullable Long folderId) { - sentFolderId = folderId; - } - - public synchronized void setSentFolderId(@Nullable Long folderId, SpecialFolderSelection selection) { - sentFolderId = folderId; - sentFolderSelection = selection; - } - - public synchronized boolean hasSentFolder() { - return sentFolderId != null; - } - - public synchronized String getImportedTrashFolder() { - return importedTrashFolder; - } - - public synchronized void setImportedTrashFolder(String folderServerId) { - importedTrashFolder = folderServerId; - } - - @Nullable - public synchronized Long getTrashFolderId() { - return trashFolderId; - } - - public synchronized void setTrashFolderId(@Nullable Long folderId) { - trashFolderId = folderId; - } - - public synchronized void setTrashFolderId(@Nullable Long folderId, SpecialFolderSelection selection) { - trashFolderId = folderId; - trashFolderSelection = selection; - } - - public synchronized boolean hasTrashFolder() { - return trashFolderId != null; - } - - public synchronized String getImportedArchiveFolder() { - return importedArchiveFolder; - } - - public synchronized void setImportedArchiveFolder(String archiveFolder) { - this.importedArchiveFolder = archiveFolder; - } - - @Nullable - public synchronized Long getArchiveFolderId() { - return archiveFolderId; - } - - public synchronized void setArchiveFolderId(@Nullable Long folderId) { - archiveFolderId = folderId; - } - - public synchronized void setArchiveFolderId(@Nullable Long folderId, SpecialFolderSelection selection) { - this.archiveFolderId = folderId; - archiveFolderSelection = selection; - } - - public synchronized boolean hasArchiveFolder() { - return archiveFolderId != null; - } - - public synchronized String getImportedSpamFolder() { - return importedSpamFolder; - } - - public synchronized void setImportedSpamFolder(String folderServerId) { - importedSpamFolder = folderServerId; - } - - @Nullable - public synchronized Long getSpamFolderId() { - return spamFolderId; - } - - public synchronized void setSpamFolderId(@Nullable Long folderId) { - spamFolderId = folderId; - } - - public synchronized void setSpamFolderId(@Nullable Long folderId, SpecialFolderSelection selection) { - spamFolderId = folderId; - spamFolderSelection = selection; - } - - public synchronized boolean hasSpamFolder() { - return spamFolderId != null; - } - - @NotNull - public SpecialFolderSelection getDraftsFolderSelection() { - return draftsFolderSelection; - } - - @NotNull - public synchronized SpecialFolderSelection getSentFolderSelection() { - return sentFolderSelection; - } - - @NotNull - public synchronized SpecialFolderSelection getTrashFolderSelection() { - return trashFolderSelection; - } - - @NotNull - public synchronized SpecialFolderSelection getArchiveFolderSelection() { - return archiveFolderSelection; - } - - @NotNull - public synchronized SpecialFolderSelection getSpamFolderSelection() { - return spamFolderSelection; - } - - @Nullable - public synchronized Long getOutboxFolderId() { - return outboxFolderId; - } - - public synchronized void setOutboxFolderId(@Nullable Long folderId) { - outboxFolderId = folderId; - } - - public synchronized String getImportedAutoExpandFolder() { - return importedAutoExpandFolder; - } - - public synchronized void setImportedAutoExpandFolder(String name) { - importedAutoExpandFolder = name; - } - - @Nullable - public synchronized Long getAutoExpandFolderId() { - return autoExpandFolderId; - } - - public synchronized void setAutoExpandFolderId(@Nullable Long folderId) { - autoExpandFolderId = folderId; - } - - public synchronized int getAccountNumber() { - return accountNumber; - } - - public synchronized void setAccountNumber(int accountNumber) { - this.accountNumber = 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; - } - return syncMode != FolderMode.NONE && oldSyncMode == FolderMode.NONE; - } - - public synchronized FolderMode getFolderPushMode() { - return folderPushMode; - } - - public synchronized void setFolderPushMode(FolderMode pushMode) { - folderPushMode = pushMode; - } - - public synchronized boolean isNotifySync() { - return notifySync; - } - - public synchronized void setNotifySync(boolean notifySync) { - this.notifySync = notifySync; - } - - public synchronized int getMessagesNotificationChannelVersion() { - return messagesNotificationChannelVersion; - } - - public synchronized void setMessagesNotificationChannelVersion(int notificationChannelVersion) { - messagesNotificationChannelVersion = notificationChannelVersion; - } - - public synchronized void incrementMessagesNotificationChannelVersion() { - messagesNotificationChannelVersion++; - } - - 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 boolean isIgnoreChatMessages() { - return ignoreChatMessages; - } - - public synchronized void setIgnoreChatMessages(boolean ignoreChatMessages) { - this.ignoreChatMessages = ignoreChatMessages; - } - - 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; - } - - @NonNull - @Override - public synchronized String toString() { - if (K9.isSensitiveDebugLoggingEnabled()) { - return getDisplayName(); - } else { - return accountUuid; - } - } - - 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; - } - - public Map getCompressionMap() { - return Collections.unmodifiableMap(compressionMap); - } - - @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(); - } - - public synchronized List getIdentities() { - return identities; - } - - public synchronized void setIdentities(List 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 isSubscribedFoldersOnly() { - 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 isMessageFormatAuto() { - return messageFormatAuto; - } - - public synchronized void setMessageFormatAuto(boolean messageFormatAuto) { - this.messageFormatAuto = messageFormatAuto; - } - - public synchronized boolean isMessageReadReceipt() { - 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 boolean isOpenPgpProviderConfigured() { - return !TextUtils.isEmpty(openPgpProvider); - } - - @Nullable - public String getOpenPgpProvider() { - if (TextUtils.isEmpty(openPgpProvider)) { - return null; - } - return openPgpProvider; - } - - public void setOpenPgpProvider(String openPgpProvider) { - this.openPgpProvider = openPgpProvider; - } - - public long getOpenPgpKey() { - return openPgpKey; - } - - public void setOpenPgpKey(long keyId) { - openPgpKey = keyId; - } - - public boolean hasOpenPgpKey() { - return openPgpKey != NO_OPENPGP_KEY; - } - - public boolean getAutocryptPreferEncryptMutual() { - return autocryptPreferEncryptMutual; - } - - public void setAutocryptPreferEncryptMutual(boolean autocryptPreferEncryptMutual) { - this.autocryptPreferEncryptMutual = autocryptPreferEncryptMutual; - } - - public boolean isOpenPgpHideSignOnly() { - return openPgpHideSignOnly; - } - - public void setOpenPgpHideSignOnly(boolean openPgpHideSignOnly) { - this.openPgpHideSignOnly = openPgpHideSignOnly; - } - - public boolean isOpenPgpEncryptSubject() { - return openPgpEncryptSubject; - } - - public void setOpenPgpEncryptSubject(boolean openPgpEncryptSubject) { - this.openPgpEncryptSubject = openPgpEncryptSubject; - } - - public boolean isOpenPgpEncryptAllDrafts() { - return openPgpEncryptAllDrafts; - } - - public void setOpenPgpEncryptAllDrafts(boolean openPgpEncryptAllDrafts) { - this.openPgpEncryptAllDrafts = openPgpEncryptAllDrafts; - } - - public int getRemoteSearchNumResults() { - return remoteSearchNumResults; - } - - public void setRemoteSearchNumResults(int val) { - remoteSearchNumResults = (val >= 0 ? val : 0); - } - - public boolean isUploadSentMessages() { - return uploadSentMessages; - } - - public void setUploadSentMessages(boolean uploadSentMessages) { - this.uploadSentMessages = uploadSentMessages; - } - - public String getLegacyInboxFolder() { - return legacyInboxFolder; - } - - void setLegacyInboxFolder(String name) { - this.legacyInboxFolder = name; - } - - @Nullable - public synchronized Long getInboxFolderId() { - return inboxFolderId; - } - - public synchronized void setInboxFolderId(@Nullable Long folderId) { - inboxFolderId = folderId; - } - - public synchronized boolean isSyncRemoteDeletions() { - return syncRemoteDeletions; - } - - public synchronized void setSyncRemoteDeletions(boolean syncRemoteDeletions) { - this.syncRemoteDeletions = syncRemoteDeletions; - } - - public synchronized Long getLastSelectedFolderId() { - return lastSelectedFolderId; - } - - public synchronized void setLastSelectedFolderId(long folderId) { - lastSelectedFolderId = folderId; - } - - public synchronized NotificationSetting getNotificationSetting() { - return notificationSetting; - } - - public synchronized boolean isMarkMessageAsReadOnView() { - return markMessageAsReadOnView; - } - - public synchronized void setMarkMessageAsReadOnView(boolean value) { - markMessageAsReadOnView = value; - } - - public synchronized boolean isMarkMessageAsReadOnDelete() { - return markMessageAsReadOnDelete; - } - - public synchronized void setMarkMessageAsReadOnDelete(boolean value) { - markMessageAsReadOnDelete = 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; - } - - public synchronized long getLastSyncTime() { - return lastSyncTime; - } - - public synchronized void setLastSyncTime(long lastSyncTime) { - this.lastSyncTime = lastSyncTime; - } - - public synchronized long getLastFolderListRefreshTime() { - return lastFolderListRefreshTime; - } - - public synchronized void setLastFolderListRefreshTime(long lastFolderListRefreshTime) { - this.lastFolderListRefreshTime = lastFolderListRefreshTime; - } - - boolean isChangedVisibleLimits() { - return changedVisibleLimits; - } - - void resetChangeMarkers() { - changedVisibleLimits = false; - } - - public synchronized boolean isFinishedSetup() { - return isFinishedSetup; - } - - public void markSetupFinished() { - isFinishedSetup = true; - } -} diff --git a/app/core/src/main/java/com/fsck/k9/Account.kt b/app/core/src/main/java/com/fsck/k9/Account.kt new file mode 100644 index 000000000..b32e628e7 --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/Account.kt @@ -0,0 +1,703 @@ +package com.fsck.k9 + +import com.fsck.k9.backend.api.SyncConfig.ExpungePolicy +import com.fsck.k9.mail.Address +import com.fsck.k9.mail.NetworkType +import com.fsck.k9.mail.ServerSettings +import java.util.Calendar +import java.util.Date +import java.util.concurrent.ConcurrentHashMap + +/** + * Account stores all of the settings for a single account defined by the user. Each account is defined by a UUID. + */ +class Account(override val uuid: String) : BaseAccount { + @get:Synchronized + @set:Synchronized + var deletePolicy = DeletePolicy.NEVER + + @get:Synchronized + @set:Synchronized + private var internalIncomingServerSettings: ServerSettings? = null + + @get:Synchronized + @set:Synchronized + private var internalOutgoingServerSettings: ServerSettings? = null + + var incomingServerSettings: ServerSettings + get() = internalIncomingServerSettings ?: error("Incoming server settings not set yet") + set(value) { + internalIncomingServerSettings = value + } + + var outgoingServerSettings: ServerSettings + get() = internalOutgoingServerSettings ?: error("Outgoing server settings not set yet") + set(value) { + internalOutgoingServerSettings = value + } + + /** + * Storage provider ID, used to locate and manage the underlying DB/file storage. + */ + @get:Synchronized + @set:Synchronized + var localStorageProviderId: String? = null + + @get:Synchronized + @set:Synchronized + override var name: String? = null + set(value) { + field = value?.takeIf { it.isNotEmpty() } + } + + @get:Synchronized + @set:Synchronized + var alwaysBcc: String? = null + + /** + * -1 for never. + */ + @get:Synchronized + @set:Synchronized + var automaticCheckIntervalMinutes = 0 + + @get:Synchronized + @set:Synchronized + var displayCount = 0 + set(value) { + if (field != value) { + field = value.takeIf { it != -1 } ?: K9.DEFAULT_VISIBLE_LIMIT + isChangedVisibleLimits = true + } + } + + @get:Synchronized + @set:Synchronized + var chipColor = 0 + + @get:Synchronized + @set:Synchronized + var isNotifyNewMail = false + + @get:Synchronized + @set:Synchronized + var folderNotifyNewMailMode = FolderMode.ALL + + @get:Synchronized + @set:Synchronized + var isNotifySelfNewMail = false + + @get:Synchronized + @set:Synchronized + var isNotifyContactsMailOnly = false + + @get:Synchronized + @set:Synchronized + var isIgnoreChatMessages = false + + @get:Synchronized + @set:Synchronized + var legacyInboxFolder: String? = null + + @get:Synchronized + @set:Synchronized + var importedDraftsFolder: String? = null + + @get:Synchronized + @set:Synchronized + var importedSentFolder: String? = null + + @get:Synchronized + @set:Synchronized + var importedTrashFolder: String? = null + + @get:Synchronized + @set:Synchronized + var importedArchiveFolder: String? = null + + @get:Synchronized + @set:Synchronized + var importedSpamFolder: String? = null + + @get:Synchronized + @set:Synchronized + var inboxFolderId: Long? = null + + @get:Synchronized + @set:Synchronized + var outboxFolderId: Long? = null + + @get:Synchronized + @set:Synchronized + var draftsFolderId: Long? = null + + @get:Synchronized + @set:Synchronized + var sentFolderId: Long? = null + + @get:Synchronized + @set:Synchronized + var trashFolderId: Long? = null + + @get:Synchronized + @set:Synchronized + var archiveFolderId: Long? = null + + @get:Synchronized + @set:Synchronized + var spamFolderId: Long? = null + + @get:Synchronized + var draftsFolderSelection = SpecialFolderSelection.AUTOMATIC + private set + + @get:Synchronized + var sentFolderSelection = SpecialFolderSelection.AUTOMATIC + private set + + @get:Synchronized + var trashFolderSelection = SpecialFolderSelection.AUTOMATIC + private set + + @get:Synchronized + var archiveFolderSelection = SpecialFolderSelection.AUTOMATIC + private set + + @get:Synchronized + var spamFolderSelection = SpecialFolderSelection.AUTOMATIC + private set + + @get:Synchronized + @set:Synchronized + var importedAutoExpandFolder: String? = null + + @get:Synchronized + @set:Synchronized + var autoExpandFolderId: Long? = null + + @get:Synchronized + @set:Synchronized + var folderDisplayMode = FolderMode.NOT_SECOND_CLASS + + @get:Synchronized + @set:Synchronized + var folderSyncMode = FolderMode.FIRST_CLASS + + @get:Synchronized + @set:Synchronized + var folderPushMode = FolderMode.NONE + + @get:Synchronized + @set:Synchronized + var folderTargetMode = FolderMode.NOT_SECOND_CLASS + + @get:Synchronized + @set:Synchronized + var accountNumber = 0 + + @get:Synchronized + @set:Synchronized + var isNotifySync = false + + @get:Synchronized + @set:Synchronized + var sortType: SortType = SortType.SORT_DATE + + private val sortAscending: MutableMap = mutableMapOf() + + @get:Synchronized + @set:Synchronized + var showPictures = ShowPictures.NEVER + + @get:Synchronized + @set:Synchronized + var isSignatureBeforeQuotedText = false + + @get:Synchronized + @set:Synchronized + var expungePolicy = Expunge.EXPUNGE_IMMEDIATELY + + @get:Synchronized + @set:Synchronized + var maxPushFolders = 0 + + @get:Synchronized + @set:Synchronized + var idleRefreshMinutes = 0 + + private val compressionMap: MutableMap = ConcurrentHashMap() + + @get:Synchronized + @set:Synchronized + var searchableFolders = Searchable.ALL + + @get:Synchronized + @set:Synchronized + var isSubscribedFoldersOnly = false + + @get:Synchronized + @set:Synchronized + var maximumPolledMessageAge = 0 + + @get:Synchronized + @set:Synchronized + var maximumAutoDownloadMessageSize = 0 + + @get:Synchronized + @set:Synchronized + var messageFormat = MessageFormat.HTML + + @get:Synchronized + @set:Synchronized + var isMessageFormatAuto = false + + @get:Synchronized + @set:Synchronized + var isMessageReadReceipt = false + + @get:Synchronized + @set:Synchronized + var quoteStyle = QuoteStyle.PREFIX + + @get:Synchronized + @set:Synchronized + var quotePrefix: String? = null + + @get:Synchronized + @set:Synchronized + var isDefaultQuotedTextShown = false + + @get:Synchronized + @set:Synchronized + var isReplyAfterQuote = false + + @get:Synchronized + @set:Synchronized + var isStripSignature = false + + @get:Synchronized + @set:Synchronized + var isSyncRemoteDeletions = false + + @get:Synchronized + @set:Synchronized + var openPgpProvider: String? = null + set(value) { + field = value?.takeIf { it.isNotEmpty() } + } + + @get:Synchronized + @set:Synchronized + var openPgpKey: Long = 0 + + @get:Synchronized + @set:Synchronized + var autocryptPreferEncryptMutual = false + + @get:Synchronized + @set:Synchronized + var isOpenPgpHideSignOnly = false + + @get:Synchronized + @set:Synchronized + var isOpenPgpEncryptSubject = false + + @get:Synchronized + @set:Synchronized + var isOpenPgpEncryptAllDrafts = false + + @get:Synchronized + @set:Synchronized + var isMarkMessageAsReadOnView = false + + @get:Synchronized + @set:Synchronized + var isMarkMessageAsReadOnDelete = false + + @get:Synchronized + @set:Synchronized + var isAlwaysShowCcBcc = false + + // Temporarily disabled + @get:Synchronized + @set:Synchronized + var isRemoteSearchFullText = false + get() = false + + @get:Synchronized + @set:Synchronized + var remoteSearchNumResults = 0 + set(value) { + field = value.coerceAtLeast(0) + } + + @get:Synchronized + @set:Synchronized + var isUploadSentMessages = false + + @get:Synchronized + @set:Synchronized + var lastSyncTime: Long = 0 + + @get:Synchronized + @set:Synchronized + var lastFolderListRefreshTime: Long = 0 + + @get:Synchronized + var isFinishedSetup = false + private set + + @get:Synchronized + @set:Synchronized + var messagesNotificationChannelVersion = 0 + + @get:Synchronized + @set:Synchronized + var isChangedVisibleLimits = false + private set + + /** + * Database ID 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. + */ + @get:Synchronized + var lastSelectedFolderId: Long? = null + private set + + @get:Synchronized + @set:Synchronized + var identities: MutableList = mutableListOf() + set(value) { + field = value.toMutableList() + } + + @get:Synchronized + var notificationSettings = NotificationSettings() + private set + + val displayName: String + get() = name ?: email + + @get:Synchronized + @set:Synchronized + override var email: String + get() = identities[0].email!! + set(email) { + val newIdentity = identities[0].withEmail(email) + identities[0] = newIdentity + } + + @get:Synchronized + @set:Synchronized + var senderName: String? + get() = identities[0].name + set(name) { + val newIdentity = identities[0].withName(name) + identities[0] = newIdentity + } + + @get:Synchronized + @set:Synchronized + var signatureUse: Boolean + get() = identities[0].signatureUse + set(signatureUse) { + val newIdentity = identities[0].withSignatureUse(signatureUse) + identities[0] = newIdentity + } + + @get:Synchronized + @set:Synchronized + var signature: String? + get() = identities[0].signature + set(signature) { + val newIdentity = identities[0].withSignature(signature) + identities[0] = newIdentity + } + + /** + * @param automaticCheckIntervalMinutes or -1 for never. + */ + @Synchronized + fun updateAutomaticCheckIntervalMinutes(automaticCheckIntervalMinutes: Int): Boolean { + val oldInterval = this.automaticCheckIntervalMinutes + this.automaticCheckIntervalMinutes = automaticCheckIntervalMinutes + + return oldInterval != automaticCheckIntervalMinutes + } + + @Synchronized + fun setDraftsFolderId(folderId: Long?, selection: SpecialFolderSelection) { + draftsFolderId = folderId + draftsFolderSelection = selection + } + + @Synchronized + fun hasDraftsFolder(): Boolean { + return draftsFolderId != null + } + + @Synchronized + fun setSentFolderId(folderId: Long?, selection: SpecialFolderSelection) { + sentFolderId = folderId + sentFolderSelection = selection + } + + @Synchronized + fun hasSentFolder(): Boolean { + return sentFolderId != null + } + + @Synchronized + fun setTrashFolderId(folderId: Long?, selection: SpecialFolderSelection) { + trashFolderId = folderId + trashFolderSelection = selection + } + + @Synchronized + fun hasTrashFolder(): Boolean { + return trashFolderId != null + } + + @Synchronized + fun setArchiveFolderId(folderId: Long?, selection: SpecialFolderSelection) { + archiveFolderId = folderId + archiveFolderSelection = selection + } + + @Synchronized + fun hasArchiveFolder(): Boolean { + return archiveFolderId != null + } + + @Synchronized + fun setSpamFolderId(folderId: Long?, selection: SpecialFolderSelection) { + spamFolderId = folderId + spamFolderSelection = selection + } + + @Synchronized + fun hasSpamFolder(): Boolean { + return spamFolderId != null + } + + @Synchronized + fun updateFolderSyncMode(syncMode: FolderMode): Boolean { + val oldSyncMode = folderSyncMode + folderSyncMode = syncMode + + return (oldSyncMode == FolderMode.NONE && syncMode != FolderMode.NONE) || + (oldSyncMode != FolderMode.NONE && syncMode == FolderMode.NONE) + } + + @Synchronized + fun incrementMessagesNotificationChannelVersion() { + messagesNotificationChannelVersion++ + } + + @Synchronized + fun isSortAscending(sortType: SortType): Boolean { + return sortAscending.getOrPut(sortType) { sortType.isDefaultAscending } + } + + @Synchronized + fun setSortAscending(sortType: SortType, sortAscending: Boolean) { + this.sortAscending[sortType] = sortAscending + } + + @Synchronized + fun setCompression(networkType: NetworkType, useCompression: Boolean) { + compressionMap[networkType] = useCompression + } + + @Synchronized + fun useCompression(networkType: NetworkType): Boolean { + return compressionMap[networkType] ?: return true + } + + fun getCompressionMap(): Map { + return compressionMap.toMap() + } + + @Synchronized + fun replaceIdentities(identities: List) { + this.identities = identities.toMutableList() + } + + @Synchronized + fun getIdentity(index: Int): Identity { + if (index !in identities.indices) error("Identity with index $index not found") + + return identities[index] + } + + fun isAnIdentity(addresses: Array
?): Boolean { + if (addresses == null) return false + + return addresses.any { address -> isAnIdentity(address) } + } + + fun isAnIdentity(address: Address): Boolean { + return findIdentity(address) != null + } + + @Synchronized + fun findIdentity(address: Address): Identity? { + return identities.find { identity -> + identity.email.equals(address.address, ignoreCase = true) + } + } + + val earliestPollDate: Date? + get() { + val age = maximumPolledMessageAge.takeIf { it >= 0 } ?: return null + + val now = Calendar.getInstance() + now[Calendar.HOUR_OF_DAY] = 0 + now[Calendar.MINUTE] = 0 + now[Calendar.SECOND] = 0 + now[Calendar.MILLISECOND] = 0 + + if (age < 28) { + now.add(Calendar.DATE, age * -1) + } else when (age) { + 28 -> now.add(Calendar.MONTH, -1) + 56 -> now.add(Calendar.MONTH, -2) + 84 -> now.add(Calendar.MONTH, -3) + 168 -> now.add(Calendar.MONTH, -6) + 365 -> now.add(Calendar.YEAR, -1) + } + + return now.time + } + + val isOpenPgpProviderConfigured: Boolean + get() = openPgpProvider != null + + @Synchronized + fun hasOpenPgpKey(): Boolean { + return openPgpKey != NO_OPENPGP_KEY + } + + @Synchronized + fun setLastSelectedFolderId(folderId: Long) { + lastSelectedFolderId = folderId + } + + @Synchronized + fun resetChangeMarkers() { + isChangedVisibleLimits = false + } + + @Synchronized + fun markSetupFinished() { + isFinishedSetup = true + } + + @Synchronized + fun updateNotificationSettings(block: (oldNotificationSettings: NotificationSettings) -> NotificationSettings) { + notificationSettings = block(notificationSettings) + } + + override fun toString(): String { + return if (K9.isSensitiveDebugLoggingEnabled) displayName else uuid + } + + override fun equals(other: Any?): Boolean { + return if (other is Account) { + other.uuid == uuid + } else { + super.equals(other) + } + } + + override fun hashCode(): Int { + return uuid.hashCode() + } + + enum class FolderMode { + NONE, + ALL, + FIRST_CLASS, + FIRST_AND_SECOND_CLASS, + NOT_SECOND_CLASS + } + + enum class SpecialFolderSelection { + AUTOMATIC, + MANUAL + } + + enum class ShowPictures { + NEVER, + ALWAYS, + ONLY_FROM_CONTACTS + } + + enum class Searchable { + ALL, + DISPLAYABLE, + NONE + } + + enum class QuoteStyle { + PREFIX, + HEADER + } + + enum class MessageFormat { + TEXT, + HTML, + AUTO + } + + enum class Expunge { + EXPUNGE_IMMEDIATELY, + EXPUNGE_MANUALLY, + EXPUNGE_ON_POLL; + + fun toBackendExpungePolicy(): ExpungePolicy = when (this) { + EXPUNGE_IMMEDIATELY -> ExpungePolicy.IMMEDIATELY + EXPUNGE_MANUALLY -> ExpungePolicy.MANUALLY + EXPUNGE_ON_POLL -> ExpungePolicy.ON_POLL + } + } + + enum class DeletePolicy(@JvmField val setting: Int) { + NEVER(0), + SEVEN_DAYS(1), + ON_DELETE(2), + MARK_AS_READ(3); + + companion object { + fun fromInt(initialSetting: Int): DeletePolicy { + return values().find { it.setting == initialSetting } ?: error("DeletePolicy $initialSetting unknown") + } + } + } + + enum class SortType(val isDefaultAscending: Boolean) { + SORT_DATE(false), + SORT_ARRIVAL(false), + SORT_SUBJECT(true), + SORT_SENDER(true), + SORT_UNREAD(true), + SORT_FLAGGED(true), + SORT_ATTACHMENT(true); + } + + companion object { + /** + * Fixed name of outbox - not actually displayed. + */ + const val OUTBOX_NAME = "Outbox" + + @JvmField + val DEFAULT_SORT_TYPE = SortType.SORT_DATE + const val DEFAULT_SORT_ASCENDING = false + const val NO_OPENPGP_KEY: Long = 0 + const val UNASSIGNED_ACCOUNT_NUMBER = -1 + const val INTERVAL_MINUTES_NEVER = -1 + const val DEFAULT_SYNC_INTERVAL = 60 + } +} diff --git a/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt b/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt index 13d81b3de..39a79c2cb 100644 --- a/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt +++ b/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt @@ -1,19 +1,19 @@ package com.fsck.k9 -import com.fsck.k9.Account.DEFAULT_SORT_ASCENDING -import com.fsck.k9.Account.DEFAULT_SORT_TYPE -import com.fsck.k9.Account.DEFAULT_SYNC_INTERVAL +import com.fsck.k9.Account.Companion.DEFAULT_SORT_ASCENDING +import com.fsck.k9.Account.Companion.DEFAULT_SORT_TYPE +import com.fsck.k9.Account.Companion.DEFAULT_SYNC_INTERVAL +import com.fsck.k9.Account.Companion.NO_OPENPGP_KEY +import com.fsck.k9.Account.Companion.UNASSIGNED_ACCOUNT_NUMBER import com.fsck.k9.Account.DeletePolicy import com.fsck.k9.Account.Expunge import com.fsck.k9.Account.FolderMode import com.fsck.k9.Account.MessageFormat -import com.fsck.k9.Account.NO_OPENPGP_KEY import com.fsck.k9.Account.QuoteStyle import com.fsck.k9.Account.Searchable import com.fsck.k9.Account.ShowPictures import com.fsck.k9.Account.SortType import com.fsck.k9.Account.SpecialFolderSelection -import com.fsck.k9.Account.UNASSIGNED_ACCOUNT_NUMBER import com.fsck.k9.helper.Utility import com.fsck.k9.mail.NetworkType import com.fsck.k9.mailstore.StorageManager @@ -137,16 +137,17 @@ class AccountPreferenceSerializer( showPictures = getEnumStringPref(storage, "$accountUuid.showPicturesEnum", ShowPictures.NEVER) - notificationSetting.isVibrateEnabled = storage.getBoolean("$accountUuid.vibrate", false) - notificationSetting.vibratePattern = storage.getInt("$accountUuid.vibratePattern", 0) - notificationSetting.vibrateTimes = storage.getInt("$accountUuid.vibrateTimes", 5) - notificationSetting.isRingEnabled = storage.getBoolean("$accountUuid.ring", true) - notificationSetting.ringtone = storage.getString( - "$accountUuid.ringtone", - "content://settings/system/notification_sound" - ) - notificationSetting.setLed(storage.getBoolean("$accountUuid.led", true)) - notificationSetting.ledColor = storage.getInt("$accountUuid.ledColor", chipColor) + updateNotificationSettings { + NotificationSettings( + isRingEnabled = storage.getBoolean("$accountUuid.ring", true), + ringtone = storage.getString("$accountUuid.ringtone", DEFAULT_RINGTONE_URI), + isLedEnabled = storage.getBoolean("$accountUuid.led", true), + ledColor = storage.getInt("$accountUuid.ledColor", chipColor), + isVibrateEnabled = storage.getBoolean("$accountUuid.vibrate", false), + vibratePattern = VibratePattern.deserialize(storage.getInt("$accountUuid.vibratePattern", 0)), + vibrateTimes = storage.getInt("$accountUuid.vibrateTimes", 5) + ) + } folderDisplayMode = getEnumStringPref(storage, "$accountUuid.folderDisplayMode", FolderMode.NOT_SECOND_CLASS) @@ -159,7 +160,7 @@ class AccountPreferenceSerializer( searchableFolders = getEnumStringPref(storage, "$accountUuid.searchableFolders", Searchable.ALL) isSignatureBeforeQuotedText = storage.getBoolean("$accountUuid.signatureBeforeQuotedText", false) - identities = loadIdentities(accountUuid, storage) + replaceIdentities(loadIdentities(accountUuid, storage)) openPgpProvider = storage.getString("$accountUuid.openPgpProvider", "") openPgpKey = storage.getLong("$accountUuid.cryptoKey", NO_OPENPGP_KEY) @@ -322,17 +323,18 @@ class AccountPreferenceSerializer( editor.putBoolean("$accountUuid.markMessageAsReadOnDelete", isMarkMessageAsReadOnDelete) editor.putBoolean("$accountUuid.alwaysShowCcBcc", isAlwaysShowCcBcc) - editor.putBoolean("$accountUuid.vibrate", notificationSetting.isVibrateEnabled) - editor.putInt("$accountUuid.vibratePattern", notificationSetting.vibratePattern) - editor.putInt("$accountUuid.vibrateTimes", notificationSetting.vibrateTimes) - editor.putBoolean("$accountUuid.ring", notificationSetting.isRingEnabled) - editor.putString("$accountUuid.ringtone", notificationSetting.ringtone) - editor.putBoolean("$accountUuid.led", notificationSetting.isLedEnabled) - editor.putInt("$accountUuid.ledColor", notificationSetting.ledColor) + editor.putBoolean("$accountUuid.vibrate", notificationSettings.isVibrateEnabled) + editor.putInt("$accountUuid.vibratePattern", notificationSettings.vibratePattern.serialize()) + editor.putInt("$accountUuid.vibrateTimes", notificationSettings.vibrateTimes) + editor.putBoolean("$accountUuid.ring", notificationSettings.isRingEnabled) + editor.putString("$accountUuid.ringtone", notificationSettings.ringtone) + editor.putBoolean("$accountUuid.led", notificationSettings.isLedEnabled) + editor.putInt("$accountUuid.ledColor", notificationSettings.ledColor) editor.putLong("$accountUuid.lastSyncTime", lastSyncTime) editor.putLong("$accountUuid.lastFolderListRefreshTime", lastFolderListRefreshTime) editor.putBoolean("$accountUuid.isFinishedSetup", isFinishedSetup) + val compressionMap = getCompressionMap() for (type in NetworkType.values()) { val useCompression = compressionMap[type] if (useCompression != null) { @@ -603,13 +605,16 @@ class AccountPreferenceSerializer( ) identities.add(identity) - with(notificationSetting) { - isVibrateEnabled = false - vibratePattern = 0 - vibrateTimes = 5 - isRingEnabled = true - ringtone = "content://settings/system/notification_sound" - ledColor = chipColor + updateNotificationSettings { + NotificationSettings( + isRingEnabled = true, + ringtone = DEFAULT_RINGTONE_URI, + isLedEnabled = false, + ledColor = chipColor, + isVibrateEnabled = false, + vibratePattern = VibratePattern.Default, + vibrateTimes = 5 + ) } resetChangeMarkers() @@ -639,5 +644,6 @@ class AccountPreferenceSerializer( const val DEFAULT_REPLY_AFTER_QUOTE = false const val DEFAULT_STRIP_SIGNATURE = true const val DEFAULT_REMOTE_SEARCH_NUM_RESULTS = 25 + const val DEFAULT_RINGTONE_URI = "content://settings/system/notification_sound" } } diff --git a/app/core/src/main/java/com/fsck/k9/BaseAccount.java b/app/core/src/main/java/com/fsck/k9/BaseAccount.java deleted file mode 100644 index 5e62cd1b1..000000000 --- a/app/core/src/main/java/com/fsck/k9/BaseAccount.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.fsck.k9; - -public interface BaseAccount { - String getEmail(); - String getName(); - String getUuid(); -} diff --git a/app/core/src/main/java/com/fsck/k9/BaseAccount.kt b/app/core/src/main/java/com/fsck/k9/BaseAccount.kt new file mode 100644 index 000000000..330418bad --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/BaseAccount.kt @@ -0,0 +1,7 @@ +package com.fsck.k9 + +interface BaseAccount { + val uuid: String + val name: String? + val email: String +} diff --git a/app/core/src/main/java/com/fsck/k9/NotificationSetting.java b/app/core/src/main/java/com/fsck/k9/NotificationSetting.java deleted file mode 100644 index c9d69f249..000000000 --- a/app/core/src/main/java/com/fsck/k9/NotificationSetting.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.fsck.k9; - -/** - * Describes how a notification should behave. - */ -public class NotificationSetting { - private boolean ringEnabled; - private String ringtoneUri; - - private boolean ledEnabled; - private int ledColor; - - private boolean vibrateEnabled; - - private int vibratePattern; - private int vibrateTimes; - - /** - * Set the ringtone kill switch. Allow to disable ringtone without losing - * ringtone selection. - * - * @param ringEnabled - * true to allow ringtones, false - * otherwise. - */ - public synchronized void setRingEnabled(boolean ringEnabled) { - this.ringEnabled = ringEnabled; - } - - /** - * @return true if ringtone is allowed to play, - * false otherwise. - */ - public synchronized boolean isRingEnabled() { - return ringEnabled; - } - - public synchronized String getRingtone() { - return ringtoneUri; - } - - public synchronized void setRingtone(String ringtoneUri) { - this.ringtoneUri = ringtoneUri; - } - - public synchronized boolean isLedEnabled() { - return ledEnabled; - } - - public synchronized void setLed(final boolean led) { - ledEnabled = led; - } - - public synchronized int getLedColor() { - return ledColor; - } - - public synchronized void setLedColor(int color) { - ledColor = color; - } - - public synchronized boolean isVibrateEnabled() { - return vibrateEnabled; - } - - public synchronized void setVibrateEnabled(boolean vibrate) { - vibrateEnabled = vibrate; - } - - public synchronized int getVibratePattern() { - return vibratePattern; - } - - public synchronized int getVibrateTimes() { - return vibrateTimes; - } - - public synchronized void setVibratePattern(int pattern) { - vibratePattern = pattern; - } - - public synchronized void setVibrateTimes(int times) { - vibrateTimes = times; - } - - - - /* - * Fetch a vibration pattern. - * - * @param vibratePattern Vibration pattern index to use. - * @param vibrateTimes Number of times to do the vibration pattern. - * @return Pattern multiplied by the number of times requested. - */ - - public long[] getVibration() { - return getVibration(vibratePattern, vibrateTimes); - } - - public static long[] getVibration(int pattern, int times) { - // These are "off, on" patterns, specified in milliseconds - long[] pattern0 = new long[] {300, 200}; // like the default pattern - long[] pattern1 = new long[] {100, 200}; - long[] pattern2 = new long[] {100, 500}; - long[] pattern3 = new long[] {200, 200}; - long[] pattern4 = new long[] {200, 500}; - long[] pattern5 = new long[] {500, 500}; - - long[] selectedPattern = pattern0; //default pattern - - switch (pattern) { - case 1: - selectedPattern = pattern1; - break; - case 2: - selectedPattern = pattern2; - break; - case 3: - selectedPattern = pattern3; - break; - case 4: - selectedPattern = pattern4; - break; - case 5: - selectedPattern = pattern5; - break; - } - - long[] repeatedPattern = new long[selectedPattern.length * times]; - for (int n = 0; n < times; n++) { - System.arraycopy(selectedPattern, 0, repeatedPattern, n * selectedPattern.length, selectedPattern.length); - } - // Do not wait before starting the vibration pattern. - repeatedPattern[0] = 0; - return repeatedPattern; - } - -} diff --git a/app/core/src/main/java/com/fsck/k9/NotificationSettings.kt b/app/core/src/main/java/com/fsck/k9/NotificationSettings.kt new file mode 100644 index 000000000..2f7d29a38 --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/NotificationSettings.kt @@ -0,0 +1,67 @@ +package com.fsck.k9 + +/** + * Describes how a notification should behave. + */ +data class NotificationSettings( + val isRingEnabled: Boolean = false, + val ringtone: String? = null, + val isLedEnabled: Boolean = false, + val ledColor: Int = 0, + val isVibrateEnabled: Boolean = false, + val vibratePattern: VibratePattern = VibratePattern.Default, + val vibrateTimes: Int = 0 +) { + val vibrationPattern: LongArray + get() = getVibrationPattern(vibratePattern, vibrateTimes) + + companion object { + fun getVibrationPattern(vibratePattern: VibratePattern, times: Int): LongArray { + val selectedPattern = vibratePattern.vibrationPattern + val repeatedPattern = LongArray(selectedPattern.size * times) + for (n in 0 until times) { + System.arraycopy(selectedPattern, 0, repeatedPattern, n * selectedPattern.size, selectedPattern.size) + } + + // Do not wait before starting the vibration pattern. + repeatedPattern[0] = 0 + + return repeatedPattern + } + } +} + +enum class VibratePattern( + /** + * These are "off, on" patterns, specified in milliseconds. + */ + val vibrationPattern: LongArray +) { + Default(vibrationPattern = longArrayOf(300, 200)), + Pattern1(vibrationPattern = longArrayOf(100, 200)), + Pattern2(vibrationPattern = longArrayOf(100, 500)), + Pattern3(vibrationPattern = longArrayOf(200, 200)), + Pattern4(vibrationPattern = longArrayOf(200, 500)), + Pattern5(vibrationPattern = longArrayOf(500, 500)); + + fun serialize(): Int = when (this) { + Default -> 0 + Pattern1 -> 1 + Pattern2 -> 2 + Pattern3 -> 3 + Pattern4 -> 4 + Pattern5 -> 5 + } + + companion object { + fun deserialize(value: Int): VibratePattern = when (value) { + 0 -> Default + 1 -> Pattern1 + 2 -> Pattern2 + 3 -> Pattern3 + 4 -> Pattern4 + 5 -> Pattern5 + else -> error("Unknown VibratePattern value: $value") + } + } +} diff --git a/app/core/src/main/java/com/fsck/k9/notification/BaseNotificationDataCreator.kt b/app/core/src/main/java/com/fsck/k9/notification/BaseNotificationDataCreator.kt index 86f44c861..6a45e8995 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/BaseNotificationDataCreator.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/BaseNotificationDataCreator.kt @@ -40,8 +40,8 @@ internal class BaseNotificationDataCreator { } private fun createNotificationAppearance(account: Account): NotificationAppearance { - return with(account.notificationSetting) { - val vibrationPattern = if (isVibrateEnabled) vibration else null + return with(account.notificationSettings) { + val vibrationPattern = if (isVibrateEnabled) vibrationPattern else null NotificationAppearance(ringtone, vibrationPattern, ledColor) } } diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationChannelManager.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationChannelManager.kt index 0410c7f47..948df9a20 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/NotificationChannelManager.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationChannelManager.kt @@ -6,7 +6,7 @@ import android.app.NotificationManager import android.os.Build import androidx.annotation.RequiresApi import com.fsck.k9.Account -import com.fsck.k9.NotificationSetting +import com.fsck.k9.NotificationSettings import com.fsck.k9.Preferences import java.util.concurrent.Executor import timber.log.Timber @@ -165,7 +165,7 @@ class NotificationChannelManager( val oldChannelId = getChannelIdFor(account, ChannelType.MESSAGES) val oldNotificationChannel = notificationManager.getNotificationChannel(oldChannelId) - if (oldNotificationChannel.matches(account.notificationSetting)) { + if (oldNotificationChannel.matches(account.notificationSettings)) { Timber.v("Not recreating NotificationChannel. The current one already matches the app's settings.") return } @@ -183,7 +183,7 @@ class NotificationChannelManager( group = account.uuid copyPropertiesFrom(oldNotificationChannel) - copyPropertiesFrom(account.notificationSetting) + copyPropertiesFrom(account.notificationSettings) } Timber.v("Recreating NotificationChannel(%s => %s)", oldChannelId, newChannelId) @@ -191,10 +191,10 @@ class NotificationChannelManager( } @RequiresApi(Build.VERSION_CODES.O) - private fun NotificationChannel.matches(notificationSetting: NotificationSetting): Boolean { - return lightColor == notificationSetting.ledColor && - shouldVibrate() == notificationSetting.isVibrateEnabled && - vibrationPattern.contentEquals(notificationSetting.vibration) + private fun NotificationChannel.matches(notificationSettings: NotificationSettings): Boolean { + return lightColor == notificationSettings.ledColor && + shouldVibrate() == notificationSettings.isVibrateEnabled && + vibrationPattern.contentEquals(notificationSettings.vibrationPattern) } @RequiresApi(Build.VERSION_CODES.O) @@ -211,10 +211,10 @@ class NotificationChannelManager( } @RequiresApi(Build.VERSION_CODES.O) - private fun NotificationChannel.copyPropertiesFrom(notificationSetting: NotificationSetting) { - lightColor = notificationSetting.ledColor - vibrationPattern = notificationSetting.vibration - enableVibration(notificationSetting.isVibrateEnabled) + private fun NotificationChannel.copyPropertiesFrom(notificationSettings: NotificationSettings) { + lightColor = notificationSettings.ledColor + vibrationPattern = notificationSettings.vibrationPattern + enableVibration(notificationSettings.isVibrateEnabled) } private val Account.messagesNotificationChannelSuffix: String diff --git a/app/core/src/main/java/com/fsck/k9/notification/SyncNotificationController.kt b/app/core/src/main/java/com/fsck/k9/notification/SyncNotificationController.kt index e127136c8..ab69e4494 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/SyncNotificationController.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/SyncNotificationController.kt @@ -41,7 +41,7 @@ internal class SyncNotificationController( builder = notificationBuilder, ringtone = null, vibrationPattern = null, - ledColor = account.notificationSetting.ledColor, + ledColor = account.notificationSettings.ledColor, ledSpeed = NotificationHelper.NOTIFICATION_LED_BLINK_FAST, ringAndVibrate = true ) @@ -88,7 +88,7 @@ internal class SyncNotificationController( builder = notificationBuilder, ringtone = null, vibrationPattern = null, - ledColor = account.notificationSetting.ledColor, + ledColor = account.notificationSettings.ledColor, ledSpeed = NotificationHelper.NOTIFICATION_LED_BLINK_FAST, ringAndVibrate = true ) @@ -118,7 +118,7 @@ internal class SyncNotificationController( builder = notificationBuilder, ringtone = null, vibrationPattern = null, - ledColor = account.notificationSetting.ledColor, + ledColor = account.notificationSettings.ledColor, ledSpeed = NotificationHelper.NOTIFICATION_LED_BLINK_FAST, ringAndVibrate = true ) diff --git a/app/core/src/main/java/com/fsck/k9/search/SearchAccount.java b/app/core/src/main/java/com/fsck/k9/search/SearchAccount.java deleted file mode 100644 index e6b0cb36e..000000000 --- a/app/core/src/main/java/com/fsck/k9/search/SearchAccount.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.fsck.k9.search; - - -import com.fsck.k9.BaseAccount; -import com.fsck.k9.CoreResourceProvider; -import com.fsck.k9.DI; -import com.fsck.k9.search.SearchSpecification.Attribute; -import com.fsck.k9.search.SearchSpecification.SearchField; - - -/** - * This class is basically a wrapper around a LocalSearch. It allows to expose it as - * an account. This is a meta-account containing all the email that matches the search. - */ -public class SearchAccount implements BaseAccount { - public static final String UNIFIED_INBOX = "unified_inbox"; - public static final String NEW_MESSAGES = "new_messages"; - - - // create the unified inbox meta account ( all accounts is default when none specified ) - public static SearchAccount createUnifiedInboxAccount() { - CoreResourceProvider resourceProvider = DI.get(CoreResourceProvider.class); - LocalSearch tmpSearch = new LocalSearch(); - tmpSearch.setId(UNIFIED_INBOX); - tmpSearch.and(SearchField.INTEGRATE, "1", Attribute.EQUALS); - return new SearchAccount(UNIFIED_INBOX, tmpSearch, resourceProvider.searchUnifiedInboxTitle(), - resourceProvider.searchUnifiedInboxDetail()); - } - - private String mId; - private String mEmail; - private String name; - private LocalSearch mSearch; - - public SearchAccount(String id, LocalSearch search, String name, String email) - throws IllegalArgumentException { - - if (search == null) { - throw new IllegalArgumentException("Provided LocalSearch was null"); - } - - mId = id; - mSearch = search; - this.name = name; - mEmail = email; - } - - public String getId() { - return mId; - } - - @Override - public synchronized String getEmail() { - return mEmail; - } - - @Override - public String getName() { - return name; - } - - public LocalSearch getRelatedSearch() { - return mSearch; - } - - /** - * Returns the ID of this {@code SearchAccount} instance. - * - *

- * This isn't really a UUID. But since we don't expose this value to other apps and we only - * use the account UUID as opaque string (e.g. as key in a {@code Map}) we're fine.
- * Using a constant string is necessary to identify the same search account even when the - * corresponding {@link SearchAccount} object has been recreated. - *

- */ - @Override - public String getUuid() { - return mId; - } -} diff --git a/app/core/src/main/java/com/fsck/k9/search/SearchAccount.kt b/app/core/src/main/java/com/fsck/k9/search/SearchAccount.kt new file mode 100644 index 000000000..68a7abea6 --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/search/SearchAccount.kt @@ -0,0 +1,53 @@ +package com.fsck.k9.search + +import com.fsck.k9.BaseAccount +import com.fsck.k9.CoreResourceProvider +import com.fsck.k9.search.SearchSpecification.SearchField +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +/** + * This class is basically a wrapper around a LocalSearch. It allows to expose it as an account. + * This is a meta-account containing all the messages that match the search. + */ +class SearchAccount( + val id: String, + search: LocalSearch, + override val name: String, + override val email: String +) : BaseAccount { + /** + * Returns the ID of this `SearchAccount` instance. + * + * This isn't really a UUID. But since we don't expose this value to other apps and we only use the account UUID + * as opaque string (e.g. as key in a `Map`) we're fine. + * + * Using a constant string is necessary to identify the same search account even when the corresponding + * [SearchAccount] object has been recreated. + */ + override val uuid: String = id + + val relatedSearch: LocalSearch = search + + companion object : KoinComponent { + private val resourceProvider: CoreResourceProvider by inject() + + const val UNIFIED_INBOX = "unified_inbox" + const val NEW_MESSAGES = "new_messages" + + @JvmStatic + fun createUnifiedInboxAccount(): SearchAccount { + val tmpSearch = LocalSearch().apply { + id = UNIFIED_INBOX + and(SearchField.INTEGRATE, "1", SearchSpecification.Attribute.EQUALS) + } + + return SearchAccount( + id = UNIFIED_INBOX, + search = tmpSearch, + name = resourceProvider.searchUnifiedInboxTitle(), + email = resourceProvider.searchUnifiedInboxDetail() + ) + } + } +} diff --git a/app/core/src/test/java/com/fsck/k9/helper/IdentityHelperTest.kt b/app/core/src/test/java/com/fsck/k9/helper/IdentityHelperTest.kt index 7bae4690d..cb2867043 100644 --- a/app/core/src/test/java/com/fsck/k9/helper/IdentityHelperTest.kt +++ b/app/core/src/test/java/com/fsck/k9/helper/IdentityHelperTest.kt @@ -115,13 +115,15 @@ class IdentityHelperTest : RobolectricTest() { } private fun createDummyAccount() = Account(UUID.randomUUID().toString()).apply { - identities = listOf( - newIdentity("Default", DEFAULT_ADDRESS), - newIdentity("Identity 1", IDENTITY_1_ADDRESS), - newIdentity("Identity 2", IDENTITY_2_ADDRESS), - newIdentity("Identity 3", IDENTITY_3_ADDRESS), - newIdentity("Identity 4", IDENTITY_4_ADDRESS), - newIdentity("Identity 5", IDENTITY_5_ADDRESS) + replaceIdentities( + listOf( + newIdentity("Default", DEFAULT_ADDRESS), + newIdentity("Identity 1", IDENTITY_1_ADDRESS), + newIdentity("Identity 2", IDENTITY_2_ADDRESS), + newIdentity("Identity 3", IDENTITY_3_ADDRESS), + newIdentity("Identity 4", IDENTITY_4_ADDRESS), + newIdentity("Identity 5", IDENTITY_5_ADDRESS) + ) ) } diff --git a/app/core/src/test/java/com/fsck/k9/notification/BaseNotificationDataCreatorTest.kt b/app/core/src/test/java/com/fsck/k9/notification/BaseNotificationDataCreatorTest.kt index 3ec3c6e8d..749d529d4 100644 --- a/app/core/src/test/java/com/fsck/k9/notification/BaseNotificationDataCreatorTest.kt +++ b/app/core/src/test/java/com/fsck/k9/notification/BaseNotificationDataCreatorTest.kt @@ -4,7 +4,8 @@ import com.fsck.k9.Account import com.fsck.k9.Identity import com.fsck.k9.K9 import com.fsck.k9.K9.LockScreenNotificationVisibility -import com.fsck.k9.NotificationSetting +import com.fsck.k9.NotificationSettings +import com.fsck.k9.VibratePattern import com.google.common.truth.Truth.assertThat import org.junit.Test import org.mockito.kotlin.mock @@ -138,7 +139,7 @@ class BaseNotificationDataCreatorTest { @Test fun ringtone() { - account.notificationSetting.ringtone = "content://ringtone/1" + account.updateNotificationSettings { it.copy(ringtone = "content://ringtone/1") } val notificationData = createNotificationData() val result = notificationDataCreator.createBaseNotificationData(notificationData) @@ -148,19 +149,21 @@ class BaseNotificationDataCreatorTest { @Test fun `vibration pattern`() { - account.notificationSetting.isVibrateEnabled = true - account.notificationSetting.vibratePattern = 3 - account.notificationSetting.vibrateTimes = 2 + account.updateNotificationSettings { + it.copy(isVibrateEnabled = true, vibratePattern = VibratePattern.Pattern3, vibrateTimes = 2) + } val notificationData = createNotificationData() val result = notificationDataCreator.createBaseNotificationData(notificationData) - assertThat(result.appearance.vibrationPattern).isEqualTo(NotificationSetting.getVibration(3, 2)) + assertThat(result.appearance.vibrationPattern).isEqualTo( + NotificationSettings.getVibrationPattern(VibratePattern.Pattern3, 2) + ) } @Test fun `led color`() { - account.notificationSetting.ledColor = 0x00FF00 + account.updateNotificationSettings { it.copy(ledColor = 0x00FF00) } val notificationData = createNotificationData() val result = notificationDataCreator.createBaseNotificationData(notificationData) @@ -192,7 +195,7 @@ class BaseNotificationDataCreatorTest { private fun createAccount(): Account { return Account("00000000-0000-4000-0000-000000000000").apply { name = "account name" - identities = listOf(Identity()) + replaceIdentities(listOf(Identity())) } } } diff --git a/app/k9mail/src/main/java/com/fsck/k9/widget/unread/UnreadWidgetDataProvider.kt b/app/k9mail/src/main/java/com/fsck/k9/widget/unread/UnreadWidgetDataProvider.kt index 4766a43be..5690639c1 100644 --- a/app/k9mail/src/main/java/com/fsck/k9/widget/unread/UnreadWidgetDataProvider.kt +++ b/app/k9mail/src/main/java/com/fsck/k9/widget/unread/UnreadWidgetDataProvider.kt @@ -35,7 +35,7 @@ class UnreadWidgetDataProvider( private fun loadSearchAccountData(configuration: UnreadWidgetConfiguration): UnreadWidgetData { val searchAccount = getSearchAccount(configuration.accountUuid) - val title = searchAccount.name + val title = searchAccount.name ?: searchAccount.email val unreadCount = messagingController.getUnreadMessageCount(searchAccount) val clickIntent = MessageList.intentDisplaySearch(context, searchAccount.relatedSearch, false, true, true) diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/BundleExtensions.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/BundleExtensions.kt index b062cf3cf..efb906f7e 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/BundleExtensions.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/BundleExtensions.kt @@ -10,3 +10,8 @@ inline fun > Bundle.getEnum(key: String, defaultValue: T): T val value = getString(key) ?: return defaultValue return enumValueOf(value) } + +inline fun > Bundle.getEnum(key: String): T { + val value = getString(key) ?: error("Missing enum value for key '$key'") + return enumValueOf(value) +} diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsDataStore.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsDataStore.kt index eebf6d7cd..28574489d 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsDataStore.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsDataStore.kt @@ -30,7 +30,7 @@ class AccountSettingsDataStore( "account_notify" -> account.isNotifyNewMail "account_notify_self" -> account.isNotifySelfNewMail "account_notify_contacts_mail_only" -> account.isNotifyContactsMailOnly - "account_led" -> account.notificationSetting.isLedEnabled + "account_led" -> account.notificationSettings.isLedEnabled "account_notify_sync" -> account.isNotifySync "openpgp_hide_sign_only" -> account.isOpenPgpHideSignOnly "openpgp_encrypt_subject" -> account.isOpenPgpEncryptSubject @@ -55,7 +55,7 @@ class AccountSettingsDataStore( "account_notify" -> account.isNotifyNewMail = value "account_notify_self" -> account.isNotifySelfNewMail = value "account_notify_contacts_mail_only" -> account.isNotifyContactsMailOnly = value - "account_led" -> account.notificationSetting.setLed(value) + "account_led" -> account.updateNotificationSettings { it.copy(isLedEnabled = value) } "account_notify_sync" -> account.isNotifySync = value "openpgp_hide_sign_only" -> account.isOpenPgpHideSignOnly = value "openpgp_encrypt_subject" -> account.isOpenPgpEncryptSubject = value @@ -72,7 +72,7 @@ class AccountSettingsDataStore( override fun getInt(key: String?, defValue: Int): Int { return when (key) { "chip_color" -> account.chipColor - "led_color" -> account.notificationSetting.ledColor + "led_color" -> account.notificationSettings.ledColor else -> defValue } } @@ -134,7 +134,7 @@ class AccountSettingsDataStore( "folder_notify_new_mail_mode" -> account.folderNotifyNewMailMode.name "account_combined_vibration" -> getCombinedVibrationValue() "account_remote_search_num_results" -> account.remoteSearchNumResults.toString() - "account_ringtone" -> account.notificationSetting.ringtone + "account_ringtone" -> account.notificationSettings.ringtone else -> defValue } } @@ -149,12 +149,12 @@ class AccountSettingsDataStore( "account_message_age" -> account.maximumPolledMessageAge = value.toInt() "account_autodownload_size" -> account.maximumAutoDownloadMessageSize = value.toInt() "account_check_frequency" -> { - if (account.setAutomaticCheckIntervalMinutes(value.toInt())) { + if (account.updateAutomaticCheckIntervalMinutes(value.toInt())) { reschedulePoll() } } "folder_sync_mode" -> { - if (account.setFolderSyncMode(Account.FolderMode.valueOf(value))) { + if (account.updateFolderSyncMode(Account.FolderMode.valueOf(value))) { reschedulePoll() } } @@ -178,10 +178,7 @@ class AccountSettingsDataStore( "folder_notify_new_mail_mode" -> account.folderNotifyNewMailMode = Account.FolderMode.valueOf(value) "account_combined_vibration" -> setCombinedVibrationValue(value) "account_remote_search_num_results" -> account.remoteSearchNumResults = value.toInt() - "account_ringtone" -> with(account.notificationSetting) { - isRingEnabled = true - ringtone = value - } + "account_ringtone" -> account.updateNotificationSettings { it.copy(isRingEnabled = true, ringtone = value) } else -> return } @@ -189,8 +186,8 @@ class AccountSettingsDataStore( } private fun setNotificationLightColor(value: Int) { - if (account.notificationSetting.ledColor != value) { - account.notificationSetting.ledColor = value + if (account.notificationSettings.ledColor != value) { + account.updateNotificationSettings { it.copy(ledColor = value) } notificationSettingsChanged = true } } @@ -245,17 +242,21 @@ class AccountSettingsDataStore( private fun getCombinedVibrationValue(): String { return VibrationPreference.encode( - isVibrationEnabled = account.notificationSetting.isVibrateEnabled, - vibrationPattern = account.notificationSetting.vibratePattern, - vibrationTimes = account.notificationSetting.vibrateTimes + isVibrationEnabled = account.notificationSettings.isVibrateEnabled, + vibratePattern = account.notificationSettings.vibratePattern, + vibrationTimes = account.notificationSettings.vibrateTimes ) } private fun setCombinedVibrationValue(value: String) { val (isVibrationEnabled, vibrationPattern, vibrationTimes) = VibrationPreference.decode(value) - account.notificationSetting.isVibrateEnabled = isVibrationEnabled - account.notificationSetting.vibratePattern = vibrationPattern - account.notificationSetting.vibrateTimes = vibrationTimes + account.updateNotificationSettings { notificationSettings -> + notificationSettings.copy( + isVibrateEnabled = isVibrationEnabled, + vibratePattern = vibrationPattern, + vibrateTimes = vibrationTimes, + ) + } notificationSettingsChanged = true } } diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/VibrationDialogFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/VibrationDialogFragment.kt index b9ba9e99c..d978d2c3b 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/VibrationDialogFragment.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/VibrationDialogFragment.kt @@ -16,8 +16,11 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SwitchCompat import androidx.core.content.getSystemService import androidx.preference.PreferenceDialogFragmentCompat -import com.fsck.k9.NotificationSetting +import com.fsck.k9.NotificationSettings +import com.fsck.k9.VibratePattern import com.fsck.k9.ui.R +import com.fsck.k9.ui.getEnum +import com.fsck.k9.ui.putEnum class VibrationDialogFragment : PreferenceDialogFragmentCompat() { private val vibrator by lazy { requireContext().getSystemService() ?: error("Vibrator service missing") } @@ -31,15 +34,15 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() { val context = requireContext() val isVibrationEnabled: Boolean - val vibrationPattern: Int + val vibratePattern: VibratePattern val vibrationTimes: Int if (savedInstanceState != null) { isVibrationEnabled = savedInstanceState.getBoolean(STATE_VIBRATE) - vibrationPattern = savedInstanceState.getInt(STATE_VIBRATION_PATTERN) + vibratePattern = savedInstanceState.getEnum(STATE_VIBRATE_PATTERN) vibrationTimes = savedInstanceState.getInt(STATE_VIBRATION_TIMES) } else { isVibrationEnabled = vibrationPreference.isVibrationEnabled - vibrationPattern = vibrationPreference.vibrationPattern + vibratePattern = vibrationPreference.vibratePattern vibrationTimes = vibrationPreference.vibrationTimes } @@ -47,7 +50,7 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() { isVibrationEnabled, entries = vibrationPreference.entries.map { it.toString() }, entryValues = vibrationPreference.entryValues.map { it.toString().toInt() }, - vibrationPattern, + vibratePattern, vibrationTimes ) @@ -62,7 +65,7 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() { if (positiveResult) { vibrationPreference.setVibration( isVibrationEnabled = adapter.isVibrationEnabled, - vibrationPattern = adapter.vibrationPattern, + vibratePattern = adapter.vibratePattern, vibrationTimes = adapter.vibrationTimes ) } @@ -71,21 +74,21 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() { override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putBoolean(STATE_VIBRATE, adapter.isVibrationEnabled) - outState.putInt(STATE_VIBRATION_PATTERN, adapter.vibrationPattern) + outState.putEnum(STATE_VIBRATE_PATTERN, adapter.vibratePattern) outState.putInt(STATE_VIBRATION_TIMES, adapter.vibrationTimes) } private fun playVibration() { - val vibrationPattern = adapter.vibrationPattern + val vibratePattern = adapter.vibratePattern val vibrationTimes = adapter.vibrationTimes - val combinedPattern = NotificationSetting.getVibration(vibrationPattern, vibrationTimes) + val vibrationPattern = NotificationSettings.getVibrationPattern(vibratePattern, vibrationTimes) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val vibrationEffect = VibrationEffect.createWaveform(combinedPattern, -1) + val vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, -1) vibrator.vibrate(vibrationEffect) } else { @Suppress("DEPRECATION") - vibrator.vibrate(combinedPattern, -1) + vibrator.vibrate(vibrationPattern, -1) } } @@ -93,13 +96,13 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() { var isVibrationEnabled: Boolean, private val entries: List, private val entryValues: List, - initialVibrationPattern: Int, + initialVibratePattern: VibratePattern, initialVibrationTimes: Int ) : BaseAdapter() { - private var checkedEntryIndex = entryValues.indexOf(initialVibrationPattern).takeIf { it != -1 } ?: 0 + private var checkedEntryIndex = entryValues.indexOf(initialVibratePattern.serialize()).takeIf { it != -1 } ?: 0 - val vibrationPattern: Int - get() = entryValues[checkedEntryIndex] + val vibratePattern: VibratePattern + get() = VibratePattern.deserialize(entryValues[checkedEntryIndex]) var vibrationTimes = initialVibrationTimes @@ -209,7 +212,7 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() { companion object { private const val STATE_VIBRATE = "vibrate" - private const val STATE_VIBRATION_PATTERN = "vibrationPattern" + private const val STATE_VIBRATE_PATTERN = "vibratePattern" private const val STATE_VIBRATION_TIMES = "vibrationTimes" } } diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/VibrationPreference.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/VibrationPreference.kt index 2cbd33405..825f75a85 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/VibrationPreference.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/VibrationPreference.kt @@ -5,7 +5,8 @@ import android.content.Context import android.util.AttributeSet import androidx.core.content.res.TypedArrayUtils import androidx.preference.ListPreference -import com.fsck.k9.NotificationSetting +import com.fsck.k9.NotificationSettings +import com.fsck.k9.VibratePattern import com.fsck.k9.ui.R import com.takisoft.preferencex.PreferenceFragmentCompat @@ -28,7 +29,7 @@ constructor( internal var isVibrationEnabled: Boolean = false private set - internal var vibrationPattern: Int = DEFAULT_VIBRATION_PATTERN + internal var vibratePattern = DEFAULT_VIBRATE_PATTERN private set internal var vibrationTimes: Int = DEFAULT_VIBRATION_TIMES @@ -39,7 +40,7 @@ constructor( val (isVibrationEnabled, vibrationPattern, vibrationTimes) = decode(encoded) this.isVibrationEnabled = isVibrationEnabled - this.vibrationPattern = vibrationPattern + this.vibratePattern = vibrationPattern this.vibrationTimes = vibrationTimes updateSummary() @@ -49,12 +50,12 @@ constructor( preferenceManager.showDialog(this) } - fun setVibration(isVibrationEnabled: Boolean, vibrationPattern: Int, vibrationTimes: Int) { + fun setVibration(isVibrationEnabled: Boolean, vibratePattern: VibratePattern, vibrationTimes: Int) { this.isVibrationEnabled = isVibrationEnabled - this.vibrationPattern = vibrationPattern + this.vibratePattern = vibratePattern this.vibrationTimes = vibrationTimes - val encoded = encode(isVibrationEnabled, vibrationPattern, vibrationTimes) + val encoded = encode(isVibrationEnabled, vibratePattern, vibrationTimes) persistString(encoded) updateSummary() @@ -62,26 +63,29 @@ constructor( fun setVibrationFromSystem(isVibrationEnabled: Boolean, combinedPattern: List?) { if (combinedPattern == null || combinedPattern.size < 2 || combinedPattern.size % 2 != 0) { - setVibration(isVibrationEnabled, DEFAULT_VIBRATION_PATTERN, DEFAULT_VIBRATION_TIMES) + setVibration(isVibrationEnabled, DEFAULT_VIBRATE_PATTERN, DEFAULT_VIBRATION_TIMES) return } val combinedPatternArray = combinedPattern.toLongArray() val vibrationTimes = combinedPattern.size / 2 val vibrationPattern = entryValues.asSequence() - .map { entryValue -> entryValue.toString().toInt() } - .firstOrNull { vibrationPattern -> - val testPattern = NotificationSetting.getVibration(vibrationPattern, vibrationTimes) + .map { entryValue -> + val serializedVibratePattern = entryValue.toString().toInt() + VibratePattern.deserialize(serializedVibratePattern) + } + .firstOrNull { vibratePattern -> + val testPattern = NotificationSettings.getVibrationPattern(vibratePattern, vibrationTimes) testPattern.contentEquals(combinedPatternArray) - } ?: DEFAULT_VIBRATION_PATTERN + } ?: DEFAULT_VIBRATE_PATTERN setVibration(isVibrationEnabled, vibrationPattern, vibrationTimes) } private fun updateSummary() { summary = if (isVibrationEnabled) { - val index = entryValues.indexOf(vibrationPattern.toString()) + val index = entryValues.indexOf(vibratePattern.serialize().toString()) entries[index] } else { context.getString(R.string.account_settings_vibrate_summary_disabled) @@ -89,7 +93,7 @@ constructor( } companion object { - private const val DEFAULT_VIBRATION_PATTERN = 0 + private val DEFAULT_VIBRATE_PATTERN = VibratePattern.Default private const val DEFAULT_VIBRATION_TIMES = 1 init { @@ -98,14 +102,14 @@ constructor( ) } - fun encode(isVibrationEnabled: Boolean, vibrationPattern: Int, vibrationTimes: Int): String { - return "$isVibrationEnabled|$vibrationPattern|$vibrationTimes" + fun encode(isVibrationEnabled: Boolean, vibratePattern: VibratePattern, vibrationTimes: Int): String { + return "$isVibrationEnabled|${vibratePattern.name}|$vibrationTimes" } - fun decode(encoded: String): Triple { + fun decode(encoded: String): Triple { val parts = encoded.split('|') val isVibrationEnabled = parts[0].toBoolean() - val vibrationPattern = parts[1].toInt() + val vibrationPattern = VibratePattern.valueOf(parts[1]) val vibrationTimes = parts[2].toInt() return Triple(isVibrationEnabled, vibrationPattern, vibrationTimes) }