From 3a98e7b0ec67f6e1778574141c4300cf0e0ca983 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 22 Feb 2022 21:45:38 +0100 Subject: [PATCH 01/50] Prepare for version 5.913 --- app/k9mail/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/k9mail/build.gradle b/app/k9mail/build.gradle index f28c76a53c..222f55712f 100644 --- a/app/k9mail/build.gradle +++ b/app/k9mail/build.gradle @@ -48,7 +48,7 @@ android { testApplicationId "com.fsck.k9.tests" versionCode 29012 - versionName '5.912' + versionName '5.913-SNAPSHOT' // Keep in sync with the resource string array 'supported_languages' resConfigs "in", "br", "ca", "cs", "cy", "da", "de", "et", "en", "en_GB", "es", "eo", "eu", "fr", "gd", "gl", -- GitLab From 58709b156a5255eb1ad89d0b298ed2f11b37f80c Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 21 Feb 2022 23:08:39 +0100 Subject: [PATCH 02/50] Remove commented out code --- .../fsck/k9/controller/MessagingController.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index 12cd1439a3..faf1c8f595 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -2401,25 +2401,11 @@ public class MessagingController { if (LocalFolder.isModeMismatch(aDisplayMode, fDisplayClass)) { // Never sync a folder that isn't displayed - /* - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "Not syncing folder " + folder.getName() + - " which is in display mode " + fDisplayClass + " while account is in display mode " + aDisplayMode); - } - */ - continue; } if (LocalFolder.isModeMismatch(aSyncMode, fSyncClass)) { // Do not sync folders in the wrong class - /* - if (K9.DEBUG) { - Log.v(K9.LOG_TAG, "Not syncing folder " + folder.getName() + - " which is in sync mode " + fSyncClass + " while account is in sync mode " + aSyncMode); - } - */ - continue; } synchronizeFolder(account, folder, ignoreLastCheckedTime, listener, notificationState); -- GitLab From dea6bf1f010a301f65d43478e182f441b9744571 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 22 Feb 2022 01:45:11 +0100 Subject: [PATCH 03/50] Don't create notifications when manually refreshing the message list --- .../k9/controller/MessagingController.java | 59 +++++++++---------- .../setup/AccountSetupCheckSettings.java | 2 +- .../fsck/k9/fragment/MessageListFragment.kt | 6 +- .../src/main/java/com/fsck/k9/ui/K9Drawer.kt | 2 +- 4 files changed, 32 insertions(+), 37 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index faf1c8f595..56b26d0365 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -575,7 +575,7 @@ public class MessagingController { if (localFolder.getVisibleLimit() > 0) { localFolder.setVisibleLimit(localFolder.getVisibleLimit() + account.getDisplayCount()); } - synchronizeMailbox(account, folderId, listener); + synchronizeMailbox(account, folderId, false, listener); } catch (MessagingException me) { throw new RuntimeException("Unable to set visible limit on folder", me); } @@ -584,9 +584,9 @@ public class MessagingController { /** * Start background synchronization of the specified folder. */ - public void synchronizeMailbox(Account account, long folderId, MessagingListener listener) { + public void synchronizeMailbox(Account account, long folderId, boolean notify, MessagingListener listener) { putBackground("synchronizeMailbox", listener, () -> - synchronizeMailboxSynchronous(account, folderId, listener, new NotificationState()) + synchronizeMailboxSynchronous(account, folderId, notify, listener, new NotificationState()) ); } @@ -596,7 +596,7 @@ public class MessagingController { final CountDownLatch latch = new CountDownLatch(1); putBackground("synchronizeMailbox", null, () -> { try { - synchronizeMailboxSynchronous(account, folderId, null, new NotificationState()); + synchronizeMailboxSynchronous(account, folderId, true, null, new NotificationState()); } finally { latch.countDown(); } @@ -609,19 +609,12 @@ public class MessagingController { } } - /** - * Start foreground synchronization of the specified folder. This is generally only called - * by synchronizeMailbox. - *

- * TODO Break this method up into smaller chunks. - */ - @VisibleForTesting - void synchronizeMailboxSynchronous(Account account, long folderId, MessagingListener listener, - NotificationState notificationState) { + private void synchronizeMailboxSynchronous(Account account, long folderId, boolean notify, + MessagingListener listener, NotificationState notificationState) { refreshFolderListIfStale(account); Backend backend = getBackend(account); - syncFolder(account, folderId, listener, backend, notificationState); + syncFolder(account, folderId, notify, listener, backend, notificationState); } private void refreshFolderListIfStale(Account account) { @@ -636,7 +629,7 @@ public class MessagingController { } } - private void syncFolder(Account account, long folderId, MessagingListener listener, Backend backend, + private void syncFolder(Account account, long folderId, boolean notify, MessagingListener listener, Backend backend, NotificationState notificationState) { ServerSettings serverSettings = account.getIncomingServerSettings(); if (serverSettings.isMissingCredentials()) { @@ -667,9 +660,14 @@ public class MessagingController { return; } - MessageStore messageStore = messageStoreManager.getMessageStore(account); - Long lastChecked = messageStore.getFolder(folderId, FolderDetailsAccessor::getLastChecked); - boolean suppressNotifications = lastChecked == null; + final boolean suppressNotifications; + if (notify) { + MessageStore messageStore = messageStoreManager.getMessageStore(account); + Long lastChecked = messageStore.getFolder(folderId, FolderDetailsAccessor::getLastChecked); + suppressNotifications = lastChecked == null; + } else { + suppressNotifications = true; + } String folderServerId = localFolder.getServerId(); SyncConfig syncConfig = createSyncConfig(account); @@ -2279,7 +2277,7 @@ public class MessagingController { public boolean performPeriodicMailSync(Account account) { final CountDownLatch latch = new CountDownLatch(1); MutableBoolean syncError = new MutableBoolean(false); - checkMail(account, false, false, new SimpleMessagingListener() { + checkMail(account, false, false, true, new SimpleMessagingListener() { @Override public void checkMailFinished(Context context, Account account) { latch.countDown(); @@ -2315,10 +2313,8 @@ public class MessagingController { * Checks mail for one or multiple accounts. If account is null all accounts * are checked. */ - public void checkMail(final Account account, - final boolean ignoreLastCheckedTime, - final boolean useManualWakeLock, - final MessagingListener listener) { + public void checkMail(Account account, boolean ignoreLastCheckedTime, boolean useManualWakeLock, boolean notify, + MessagingListener listener) { final WakeLock wakeLock; if (useManualWakeLock) { @@ -2350,7 +2346,7 @@ public class MessagingController { } for (final Account account : accounts) { - checkMailForAccount(context, account, ignoreLastCheckedTime, listener); + checkMailForAccount(account, ignoreLastCheckedTime, notify, listener); } } catch (Exception e) { @@ -2377,9 +2373,8 @@ public class MessagingController { } - private void checkMailForAccount(final Context context, final Account account, - final boolean ignoreLastCheckedTime, - final MessagingListener listener) { + private void checkMailForAccount(Account account, boolean ignoreLastCheckedTime, boolean notify, + MessagingListener listener) { Timber.i("Synchronizing account %s", account); NotificationState notificationState = new NotificationState(); @@ -2408,7 +2403,7 @@ public class MessagingController { // Do not sync folders in the wrong class continue; } - synchronizeFolder(account, folder, ignoreLastCheckedTime, listener, notificationState); + synchronizeFolder(account, folder, ignoreLastCheckedTime, notify, listener, notificationState); } } catch (MessagingException e) { Timber.e(e, "Unable to synchronize account %s", account); @@ -2432,14 +2427,14 @@ public class MessagingController { } private void synchronizeFolder(Account account, LocalFolder folder, boolean ignoreLastCheckedTime, - MessagingListener listener, NotificationState notificationState) { + boolean notify, MessagingListener listener, NotificationState notificationState) { putBackground("sync" + folder.getServerId(), null, () -> { - synchronizeFolderInBackground(account, folder, ignoreLastCheckedTime, listener, notificationState); + synchronizeFolderInBackground(account, folder, ignoreLastCheckedTime, notify, listener, notificationState); }); } private void synchronizeFolderInBackground(Account account, LocalFolder folder, boolean ignoreLastCheckedTime, - MessagingListener listener, NotificationState notificationState) { + boolean notify, MessagingListener listener, NotificationState notificationState) { Timber.v("Folder %s was last synced @ %tc", folder.getServerId(), folder.getLastChecked()); if (!ignoreLastCheckedTime) { @@ -2462,7 +2457,7 @@ public class MessagingController { try { showFetchingMailNotificationIfNecessary(account, folder); try { - synchronizeMailboxSynchronous(account, folder.getDatabaseId(), listener, notificationState); + synchronizeMailboxSynchronous(account, folder.getDatabaseId(), notify, listener, notificationState); } finally { showEmptyFetchingMailNotificationIfNecessary(account); } diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java index 3a58d31a12..450eb29318 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java +++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/setup/AccountSetupCheckSettings.java @@ -510,7 +510,7 @@ public class AccountSetupCheckSettings extends K9Activity implements OnClickList messagingController.refreshFolderListSynchronous(account); Long inboxFolderId = account.getInboxFolderId(); if (inboxFolderId != null) { - messagingController.synchronizeMailbox(account, inboxFolderId, null); + messagingController.synchronizeMailbox(account, inboxFolderId, false, null); } } diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt index 7edccacf8f..c1cc821f17 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.kt @@ -1184,14 +1184,14 @@ class MessageListFragment : private fun checkMail() { if (isSingleAccountMode && isSingleFolderMode) { val folderId = currentFolder!!.databaseId - messagingController.synchronizeMailbox(account, folderId, activityListener) + messagingController.synchronizeMailbox(account, folderId, false, activityListener) messagingController.sendPendingMessages(account, activityListener) } else if (allAccounts) { - messagingController.checkMail(null, true, true, activityListener) + messagingController.checkMail(null, true, true, false, activityListener) } else { for (accountUuid in accountUuids) { val account = preferences.getAccount(accountUuid) - messagingController.checkMail(account, true, true, activityListener) + messagingController.checkMail(account, true, true, false, activityListener) } } } diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/K9Drawer.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/K9Drawer.kt index e9eeee1bbc..7a8e4feaa0 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/K9Drawer.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/K9Drawer.kt @@ -311,7 +311,7 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) : K swipeRefreshLayout.setOnRefreshListener { val accountToRefresh = if (headerView.selectionListShown) null else account messagingController.checkMail( - accountToRefresh, true, true, + accountToRefresh, true, true, true, object : SimpleMessagingListener() { override fun checkMailFinished(context: Context?, account: Account?) { swipeRefreshLayout.post { -- GitLab From 065f98cfddf5e2af8f3a0c2b31ffbd3bbdda58a5 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 24 Feb 2022 19:24:17 +0100 Subject: [PATCH 04/50] Remove unnecessary method parameters --- .../com/fsck/k9/controller/MessagingController.java | 4 ++-- .../main/java/com/fsck/k9/mailstore/LocalFolder.java | 11 +++++------ .../fsck/k9/controller/MessagingControllerTest.java | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index 56b26d0365..e3f7af1bc5 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -1070,7 +1070,7 @@ public class MessagingController { Timber.i("Marking all messages in %s:%s as read", account, folderServerId); // TODO: Make this one database UPDATE operation - List messages = localFolder.getMessages(null, false); + List messages = localFolder.getMessages(false); for (Message message : messages) { if (!message.isSet(Flag.SEEN)) { message.setFlag(Flag.SEEN, true); @@ -1539,7 +1539,7 @@ public class MessagingController { long outboxFolderId = localFolder.getDatabaseId(); - List localMessages = localFolder.getMessages(null); + List localMessages = localFolder.getMessages(); int progress = 0; int todo = localMessages.size(); for (MessagingListener l : getListeners()) { diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/LocalFolder.java b/app/core/src/main/java/com/fsck/k9/mailstore/LocalFolder.java index 02b508d7c4..bed0363b49 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/LocalFolder.java +++ b/app/core/src/main/java/com/fsck/k9/mailstore/LocalFolder.java @@ -576,17 +576,16 @@ public class LocalFolder { }); } - public List getMessages(MessageRetrievalListener listener) throws MessagingException { - return getMessages(listener, true); + public List getMessages() throws MessagingException { + return getMessages(true); } - public List getMessages(final MessageRetrievalListener listener, - final boolean includeDeleted) throws MessagingException { + public List getMessages(final boolean includeDeleted) throws MessagingException { return localStore.getDatabase().execute(false, new DbCallback>() { @Override public List doDbWork(final SQLiteDatabase db) throws MessagingException { open(); - return LocalFolder.this.localStore.getMessages(listener, LocalFolder.this, + return LocalFolder.this.localStore.getMessages(null, LocalFolder.this, "SELECT " + LocalStore.GET_MESSAGES_COLS + "FROM messages " + "LEFT JOIN message_parts ON (message_parts.id = messages.message_part_id) " + @@ -898,7 +897,7 @@ public class LocalFolder { public void setFlags(final Set flags, boolean value) throws MessagingException { open(); - for (LocalMessage message : getMessages(null)) { + for (LocalMessage message : getMessages()) { message.setFlags(flags, value); } } diff --git a/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java b/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java index 6fad0313b1..7896c2c374 100644 --- a/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java +++ b/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java @@ -419,7 +419,7 @@ public class MessagingControllerTest extends K9RobolectricTest { when(localStore.getFolder(SENT_FOLDER_ID)).thenReturn(sentFolder); when(sentFolder.getDatabaseId()).thenReturn(SENT_FOLDER_ID); when(localFolder.exists()).thenReturn(true); - when(localFolder.getMessages(null)).thenReturn(Collections.singletonList(localMessageToSend1)); + when(localFolder.getMessages()).thenReturn(Collections.singletonList(localMessageToSend1)); when(localMessageToSend1.getUid()).thenReturn("localMessageToSend1"); when(localMessageToSend1.getDatabaseId()).thenReturn(42L); when(localMessageToSend1.getHeader(K9.IDENTITY_HEADER)).thenReturn(new String[]{}); -- GitLab From e59f57f6026e8b68004a3181b5157d692392892d Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 24 Feb 2022 19:32:29 +0100 Subject: [PATCH 05/50] Remove unused callbacks from `MessageRetrievalListener` --- .../k9/controller/MessagingController.java | 8 -------- .../java/com/fsck/k9/mailstore/LocalStore.java | 10 ++++------ .../java/com/fsck/k9/backend/imap/ImapSync.kt | 6 ------ .../com/fsck/k9/backend/pop3/Pop3Sync.java | 18 ------------------ .../com/fsck/k9/backend/webdav/WebDavSync.java | 18 ------------------ .../fsck/k9/mail/MessageRetrievalListener.java | 7 ------- .../fsck/k9/mail/store/imap/RealImapFolder.kt | 5 ----- .../k9/mail/store/imap/RealImapFolderTest.kt | 2 -- .../fsck/k9/mail/store/pop3/Pop3Folder.java | 12 ------------ .../k9/mail/store/webdav/WebDavFolder.java | 10 ---------- .../k9/mail/store/webdav/WebDavFolderTest.java | 5 ----- 11 files changed, 4 insertions(+), 97 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index e3f7af1bc5..1743a81288 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -418,14 +418,6 @@ public class MessagingController { // Collecting statistics of the search result MessageRetrievalListener retrievalListener = new MessageRetrievalListener() { - @Override - public void messageStarted(String message, int number, int ofTotal) { - } - - @Override - public void messagesFinished(int number) { - } - @Override public void messageFinished(LocalMessage message, int number, int ofTotal) { if (!isMessageSuppressed(message)) { diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java b/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java index 773e2926eb..8e187b76e4 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java +++ b/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java @@ -395,9 +395,9 @@ public class LocalStore { final String queryString, final String[] placeHolders ) throws MessagingException { final List messages = new ArrayList<>(); - final int j = database.execute(false, new DbCallback() { + database.execute(false, new DbCallback() { @Override - public Integer doDbWork(final SQLiteDatabase db) { + public Void doDbWork(final SQLiteDatabase db) { Cursor cursor = null; int i = 0; try { @@ -431,12 +431,10 @@ public class LocalStore { } finally { Utility.closeQuietly(cursor); } - return i; + + return null; } }); - if (listener != null) { - listener.messagesFinished(j); - } return Collections.unmodifiableList(messages); diff --git a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt index 05c40802dd..7441860029 100644 --- a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt +++ b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt @@ -509,9 +509,6 @@ internal class ImapSync( Timber.e(e, "Error while storing downloaded message.") } } - - override fun messageStarted(uid: String, number: Int, ofTotal: Int) = Unit - override fun messagesFinished(total: Int) = Unit }, syncConfig.maximumAutoDownloadMessageSize ) @@ -562,9 +559,6 @@ internal class ImapSync( Timber.e(e, "SYNC: fetch small messages") } } - - override fun messageStarted(uid: String, number: Int, ofTotal: Int) = Unit - override fun messagesFinished(total: Int) = Unit }, -1 ) diff --git a/backend/pop3/src/main/java/com/fsck/k9/backend/pop3/Pop3Sync.java b/backend/pop3/src/main/java/com/fsck/k9/backend/pop3/Pop3Sync.java index 84f5010850..3c2b8f2e75 100644 --- a/backend/pop3/src/main/java/com/fsck/k9/backend/pop3/Pop3Sync.java +++ b/backend/pop3/src/main/java/com/fsck/k9/backend/pop3/Pop3Sync.java @@ -447,16 +447,6 @@ class Pop3Sync { Timber.e(e, "Error while storing downloaded message."); } } - - @Override - public void messageStarted(String uid, int number, int ofTotal) { - } - - @Override - public void messagesFinished(int total) { - // FIXME this method is almost never invoked by various Stores! Don't rely on it unless fixed!! - } - }, syncConfig.getMaximumAutoDownloadMessageSize()); } @@ -503,14 +493,6 @@ class Pop3Sync { Timber.e(e, "SYNC: fetch small messages"); } } - - @Override - public void messageStarted(String uid, int number, int ofTotal) { - } - - @Override - public void messagesFinished(int total) { - } }, -1); diff --git a/backend/webdav/src/main/java/com/fsck/k9/backend/webdav/WebDavSync.java b/backend/webdav/src/main/java/com/fsck/k9/backend/webdav/WebDavSync.java index 1b0c627e8d..aa8863e4bf 100644 --- a/backend/webdav/src/main/java/com/fsck/k9/backend/webdav/WebDavSync.java +++ b/backend/webdav/src/main/java/com/fsck/k9/backend/webdav/WebDavSync.java @@ -433,16 +433,6 @@ class WebDavSync { Timber.e(e, "Error while storing downloaded message."); } } - - @Override - public void messageStarted(String uid, int number, int ofTotal) { - } - - @Override - public void messagesFinished(int total) { - // FIXME this method is almost never invoked by various Stores! Don't rely on it unless fixed!! - } - }, syncConfig.getMaximumAutoDownloadMessageSize()); } @@ -488,14 +478,6 @@ class WebDavSync { Timber.e(e, "SYNC: fetch small messages"); } } - - @Override - public void messageStarted(String uid, int number, int ofTotal) { - } - - @Override - public void messagesFinished(int total) { - } }, -1); diff --git a/mail/common/src/main/java/com/fsck/k9/mail/MessageRetrievalListener.java b/mail/common/src/main/java/com/fsck/k9/mail/MessageRetrievalListener.java index 273898acc4..df87da0ece 100644 --- a/mail/common/src/main/java/com/fsck/k9/mail/MessageRetrievalListener.java +++ b/mail/common/src/main/java/com/fsck/k9/mail/MessageRetrievalListener.java @@ -3,12 +3,5 @@ package com.fsck.k9.mail; public interface MessageRetrievalListener { - void messageStarted(String uid, int number, int ofTotal); - void messageFinished(T message, int number, int ofTotal); - - /** - * FIXME this method is almost never invoked by various Stores! Don't rely on it unless fixed!! - */ - void messagesFinished(int total); } diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt index 959240f39c..a7d186ba53 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt @@ -505,7 +505,6 @@ internal class RealImapFolder( val count = uids.size return uids.mapIndexed { index, uidLong -> val uid = uidLong.toString() - listener?.messageStarted(uid, index, count) val message = ImapMessage(uid) listener?.messageFinished(message, index, count) @@ -596,8 +595,6 @@ internal class RealImapFolder( continue } - listener?.messageStarted(uid, messageNumber++, messageMap.size) - val literal = handleFetchResponse(message, fetchList) if (literal != null) { when (literal) { @@ -668,8 +665,6 @@ internal class RealImapFolder( continue } - listener?.messageStarted(uid, messageNumber++, 1) - val literal = handleFetchResponse(message, fetchList) if (literal != null) { when (literal) { diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt index 96c7555e3f..a762f8fc49 100644 --- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt +++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt @@ -475,7 +475,6 @@ class RealImapFolderTest { val messages = folder.getMessages(1, 10, null, listener) - verify(listener).messageStarted("99", 0, 1) verify(listener).messageFinished(messages[0], 0, 1) verifyNoMoreInteractions(listener) } @@ -563,7 +562,6 @@ class RealImapFolderTest { val messages = folder.getMessages(setOf(1L), true, listener) - verify(listener).messageStarted("99", 0, 1) verify(listener).messageFinished(messages[0], 0, 1) verifyNoMoreInteractions(listener) } diff --git a/mail/protocols/pop3/src/main/java/com/fsck/k9/mail/store/pop3/Pop3Folder.java b/mail/protocols/pop3/src/main/java/com/fsck/k9/mail/store/pop3/Pop3Folder.java index 260ce705c1..3b0dfb2a7c 100644 --- a/mail/protocols/pop3/src/main/java/com/fsck/k9/mail/store/pop3/Pop3Folder.java +++ b/mail/protocols/pop3/src/main/java/com/fsck/k9/mail/store/pop3/Pop3Folder.java @@ -133,9 +133,6 @@ public class Pop3Folder { continue; } - if (listener != null) { - listener.messageStarted(message.getUid(), i++, (end - start) + 1); - } messages.add(message); if (listener != null) { listener.messageFinished(message, i++, (end - start) + 1); @@ -320,9 +317,6 @@ public class Pop3Folder { for (int i = 0, count = messages.size(); i < count; i++) { Pop3Message pop3Message = messages.get(i); try { - if (listener != null && !fp.contains(FetchProfile.Item.ENVELOPE)) { - listener.messageStarted(pop3Message.getUid(), i, count); - } if (fp.contains(FetchProfile.Item.BODY)) { fetchBody(pop3Message, -1); } else if (fp.contains(FetchProfile.Item.BODY_SANE)) { @@ -369,9 +363,6 @@ public class Pop3Folder { */ for (int i = 0, count = messages.size(); i < count; i++) { Pop3Message message = messages.get(i); - if (listener != null) { - listener.messageStarted(message.getUid(), i, count); - } String response = connection.executeSimpleCommand( String.format(Locale.US, LIST_COMMAND + " %d", uidToMsgNumMap.get(message.getUid()))); @@ -400,9 +391,6 @@ public class Pop3Folder { int msgSize = Integer.parseInt(listParts[1]); Pop3Message pop3Message = msgNumToMsgMap.get(msgNum); if (pop3Message != null && msgUidIndex.contains(pop3Message.getUid())) { - if (listener != null) { - listener.messageStarted(pop3Message.getUid(), i, count); - } pop3Message.setSize(msgSize); if (listener != null) { listener.messageFinished(pop3Message, i, count); diff --git a/mail/protocols/webdav/src/main/java/com/fsck/k9/mail/store/webdav/WebDavFolder.java b/mail/protocols/webdav/src/main/java/com/fsck/k9/mail/store/webdav/WebDavFolder.java index ead75aaeb7..98a79b8b74 100644 --- a/mail/protocols/webdav/src/main/java/com/fsck/k9/mail/store/webdav/WebDavFolder.java +++ b/mail/protocols/webdav/src/main/java/com/fsck/k9/mail/store/webdav/WebDavFolder.java @@ -235,9 +235,6 @@ public class WebDavFolder { uidsLength = uids.length; for (int i = 0; i < uidsLength; i++) { - if (listener != null) { - listener.messageStarted(uids[i], i, uidsLength); - } WebDavMessage message = new WebDavMessage(uids[i], this); message.setUrl(uidToUrl.get(uids[i])); messages.add(message); @@ -313,10 +310,6 @@ public class WebDavFolder { WebDavMessage wdMessage = messages.get(i); int statusCode = 0; - if (listener != null) { - listener.messageStarted(wdMessage.getUid(), i, count); - } - /** * If fetch is called outside of the initial list (ie, a locally stored message), it may not have a URL * associated. Verify and fix that @@ -514,9 +507,6 @@ public class WebDavFolder { int count = messages.size(); for (int i = messages.size() - 1; i >= 0; i--) { WebDavMessage message = messages.get(i); - if (listener != null) { - listener.messageStarted(messages.get(i).getUid(), i, count); - } ParsedMessageEnvelope envelope = envelopes.get(message.getUid()); if (envelope != null) { diff --git a/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java index 8fcf00a20d..08082308b5 100644 --- a/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java +++ b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java @@ -221,7 +221,6 @@ public class WebDavFolderTest { FetchProfile profile = new FetchProfile(); profile.add(FetchProfile.Item.BODY_SANE); folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE); - verify(listener, times(25)).messageStarted(any(String.class), anyInt(), eq(25)); verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25)); } @@ -252,7 +251,6 @@ public class WebDavFolderTest { profile.add(FetchProfile.Item.FLAGS); profile.add(FetchProfile.Item.BODY); folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE); - verify(listener, times(25)).messageStarted(any(String.class), anyInt(), anyInt()); verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), anyInt()); } @@ -293,7 +291,6 @@ public class WebDavFolderTest { FetchProfile profile = new FetchProfile(); profile.add(FetchProfile.Item.BODY_SANE); folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE); - verify(listener, times(25)).messageStarted(any(String.class), anyInt(), eq(25)); verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25)); } @@ -324,7 +321,6 @@ public class WebDavFolderTest { FetchProfile profile = new FetchProfile(); profile.add(FetchProfile.Item.BODY_SANE); folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE); - verify(listener, times(25)).messageStarted(any(String.class), anyInt(), eq(25)); verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25)); } @@ -391,7 +387,6 @@ public class WebDavFolderTest { folder.getMessages(messageStart, messageEnd, listener); - verify(listener, times(5)).messageStarted(anyString(), anyInt(), eq(5)); verify(listener, times(5)).messageFinished(any(WebDavMessage.class), anyInt(), eq(5)); } -- GitLab From 4bed7a595bd22bffd2faa2221682a579dc39bbac Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 24 Feb 2022 19:56:50 +0100 Subject: [PATCH 06/50] Remove unused method parameters --- .../k9/controller/MessagingController.java | 2 +- .../com/fsck/k9/mailstore/LocalStore.java | 7 ++---- .../controller/MessagingControllerTest.java | 2 +- .../java/com/fsck/k9/backend/imap/ImapSync.kt | 4 ++-- .../fsck/k9/backend/imap/TestImapFolder.kt | 4 ++-- .../com/fsck/k9/backend/pop3/Pop3Sync.java | 4 ++-- .../fsck/k9/backend/webdav/WebDavSync.java | 4 ++-- .../k9/mail/MessageRetrievalListener.java | 2 +- .../fsck/k9/mail/store/imap/RealImapFolder.kt | 11 ++++------ .../k9/mail/store/imap/RealImapFolderTest.kt | 4 ++-- .../fsck/k9/mail/store/pop3/Pop3Folder.java | 15 +++++-------- .../k9/mail/store/webdav/WebDavFolder.java | 22 +++++++------------ .../mail/store/webdav/WebDavFolderTest.java | 10 ++++----- 13 files changed, 38 insertions(+), 53 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index 1743a81288..f31c9ed68a 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -419,7 +419,7 @@ public class MessagingController { // Collecting statistics of the search result MessageRetrievalListener retrievalListener = new MessageRetrievalListener() { @Override - public void messageFinished(LocalMessage message, int number, int ofTotal) { + public void messageFinished(LocalMessage message) { if (!isMessageSuppressed(message)) { List messages = new ArrayList<>(); diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java b/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java index 8e187b76e4..73beaea139 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java +++ b/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java @@ -399,7 +399,6 @@ public class LocalStore { @Override public Void doDbWork(final SQLiteDatabase db) { Cursor cursor = null; - int i = 0; try { cursor = db.rawQuery(queryString + " LIMIT 10", placeHolders); @@ -409,9 +408,8 @@ public class LocalStore { messages.add(message); if (listener != null) { - listener.messageFinished(message, i, -1); + listener.messageFinished(message); } - i++; } cursor.close(); cursor = db.rawQuery(queryString + " LIMIT -1 OFFSET 10", placeHolders); @@ -422,9 +420,8 @@ public class LocalStore { messages.add(message); if (listener != null) { - listener.messageFinished(message, i, -1); + listener.messageFinished(message); } - i++; } } catch (Exception e) { Timber.d(e, "Got an exception"); diff --git a/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java b/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java index 7896c2c374..ccb348672f 100644 --- a/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java +++ b/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java @@ -201,7 +201,7 @@ public class MessagingControllerTest extends K9RobolectricTest { controller.searchLocalMessagesSynchronous(search, listener); verify(localStore).searchForMessages(messageRetrievalListenerCaptor.capture(), eq(search)); - messageRetrievalListenerCaptor.getValue().messageFinished(localMessage, 1, 1); + messageRetrievalListenerCaptor.getValue().messageFinished(localMessage); verify(listener).listLocalMessagesAddMessages(eq(account), eq((String) null), eq(Collections.singletonList(localMessage))); } diff --git a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt index 7441860029..843204b8f1 100644 --- a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt +++ b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt @@ -483,7 +483,7 @@ internal class ImapSync( unsyncedMessages, fetchProfile, object : MessageRetrievalListener { - override fun messageFinished(message: ImapMessage, number: Int, ofTotal: Int) { + override fun messageFinished(message: ImapMessage) { try { if (message.isSet(Flag.DELETED)) { Timber.v( @@ -533,7 +533,7 @@ internal class ImapSync( smallMessages, fetchProfile, object : MessageRetrievalListener { - override fun messageFinished(message: ImapMessage, number: Int, ofTotal: Int) { + override fun messageFinished(message: ImapMessage) { try { // Store the updated message locally backendFolder.saveMessage(message, MessageDownloadState.FULL) diff --git a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt index 2ac34ede94..fe78507fc3 100644 --- a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt +++ b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt @@ -97,7 +97,7 @@ class TestImapFolder(override val serverId: String) : ImapFolder { ) { if (messages.isEmpty()) return - messages.forEachIndexed { index, imapMessage -> + for (imapMessage in messages) { val uid = imapMessage.uid.toLong() val flags = messageFlags[uid].orEmpty().toSet() @@ -109,7 +109,7 @@ class TestImapFolder(override val serverId: String) : ImapFolder { } imapMessage.body = storedMessage.body - listener?.messageFinished(imapMessage, index, messages.size) + listener?.messageFinished(imapMessage) } } diff --git a/backend/pop3/src/main/java/com/fsck/k9/backend/pop3/Pop3Sync.java b/backend/pop3/src/main/java/com/fsck/k9/backend/pop3/Pop3Sync.java index 3c2b8f2e75..136364eba2 100644 --- a/backend/pop3/src/main/java/com/fsck/k9/backend/pop3/Pop3Sync.java +++ b/backend/pop3/src/main/java/com/fsck/k9/backend/pop3/Pop3Sync.java @@ -419,7 +419,7 @@ class Pop3Sync { remoteFolder.fetch(unsyncedMessages, fp, new MessageRetrievalListener() { @Override - public void messageFinished(Pop3Message message, int number, int ofTotal) { + public void messageFinished(Pop3Message message) { try { if (message.isSet(Flag.DELETED) || message.olderThan(earliestDate)) { if (message.isSet(Flag.DELETED)) { @@ -467,7 +467,7 @@ class Pop3Sync { remoteFolder.fetch(smallMessages, fp, new MessageRetrievalListener() { @Override - public void messageFinished(final Pop3Message message, int number, int ofTotal) { + public void messageFinished(final Pop3Message message) { try { // Store the updated message locally diff --git a/backend/webdav/src/main/java/com/fsck/k9/backend/webdav/WebDavSync.java b/backend/webdav/src/main/java/com/fsck/k9/backend/webdav/WebDavSync.java index aa8863e4bf..ca400431f3 100644 --- a/backend/webdav/src/main/java/com/fsck/k9/backend/webdav/WebDavSync.java +++ b/backend/webdav/src/main/java/com/fsck/k9/backend/webdav/WebDavSync.java @@ -405,7 +405,7 @@ class WebDavSync { remoteFolder.fetch(unsyncedMessages, fp, new MessageRetrievalListener() { @Override - public void messageFinished(WebDavMessage message, int number, int ofTotal) { + public void messageFinished(WebDavMessage message) { try { if (message.isSet(Flag.DELETED) || message.olderThan(earliestDate)) { if (message.isSet(Flag.DELETED)) { @@ -453,7 +453,7 @@ class WebDavSync { remoteFolder.fetch(smallMessages, fp, new MessageRetrievalListener() { @Override - public void messageFinished(final WebDavMessage message, int number, int ofTotal) { + public void messageFinished(final WebDavMessage message) { try { // Store the updated message locally diff --git a/mail/common/src/main/java/com/fsck/k9/mail/MessageRetrievalListener.java b/mail/common/src/main/java/com/fsck/k9/mail/MessageRetrievalListener.java index df87da0ece..0503aa4be1 100644 --- a/mail/common/src/main/java/com/fsck/k9/mail/MessageRetrievalListener.java +++ b/mail/common/src/main/java/com/fsck/k9/mail/MessageRetrievalListener.java @@ -3,5 +3,5 @@ package com.fsck.k9.mail; public interface MessageRetrievalListener { - void messageFinished(T message, int number, int ofTotal); + void messageFinished(T message); } diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt index a7d186ba53..64c1959e3a 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt @@ -502,11 +502,10 @@ internal class RealImapFolder( // crazy adding stuff at the top. val uids = searchResponse.numbers.sortedDescending() - val count = uids.size - return uids.mapIndexed { index, uidLong -> + return uids.map { uidLong -> val uid = uidLong.toString() val message = ImapMessage(uid) - listener?.messageFinished(message, index, count) + listener?.messageFinished(message) message } @@ -571,7 +570,6 @@ internal class RealImapFolder( val command = String.format("UID FETCH %s (%s)", commaSeparatedUids, spaceSeparatedFetchFields) connection!!.sendCommand(command, false) - var messageNumber = 0 var callback: ImapResponseCallback? = null if (fetchProfile.contains(FetchProfile.Item.BODY) || fetchProfile.contains(FetchProfile.Item.BODY_SANE) @@ -612,7 +610,7 @@ internal class RealImapFolder( } } - listener?.messageFinished(message, messageNumber, messageMap.size) + listener?.messageFinished(message) } else { handleUntaggedResponse(response) } @@ -647,7 +645,6 @@ internal class RealImapFolder( val command = String.format("UID FETCH %s (UID %s)", message.uid, fetch) connection!!.sendCommand(command, false) - var messageNumber = 0 val callback: ImapResponseCallback = FetchPartCallback(part, bodyFactory) var response: ImapResponse @@ -687,7 +684,7 @@ internal class RealImapFolder( } } - listener?.messageFinished(message, messageNumber, 1) + listener?.messageFinished(message) } else { handleUntaggedResponse(response) } diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt index a762f8fc49..927e66baf1 100644 --- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt +++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt @@ -475,7 +475,7 @@ class RealImapFolderTest { val messages = folder.getMessages(1, 10, null, listener) - verify(listener).messageFinished(messages[0], 0, 1) + verify(listener).messageFinished(messages[0]) verifyNoMoreInteractions(listener) } @@ -562,7 +562,7 @@ class RealImapFolderTest { val messages = folder.getMessages(setOf(1L), true, listener) - verify(listener).messageFinished(messages[0], 0, 1) + verify(listener).messageFinished(messages[0]) verifyNoMoreInteractions(listener) } diff --git a/mail/protocols/pop3/src/main/java/com/fsck/k9/mail/store/pop3/Pop3Folder.java b/mail/protocols/pop3/src/main/java/com/fsck/k9/mail/store/pop3/Pop3Folder.java index 3b0dfb2a7c..a5c526e93a 100644 --- a/mail/protocols/pop3/src/main/java/com/fsck/k9/mail/store/pop3/Pop3Folder.java +++ b/mail/protocols/pop3/src/main/java/com/fsck/k9/mail/store/pop3/Pop3Folder.java @@ -120,7 +120,6 @@ public class Pop3Folder { throw new MessagingException("getMessages", ioe); } List messages = new ArrayList<>(); - int i = 0; for (int msgNum = start; msgNum <= end; msgNum++) { Pop3Message message = msgNumToMsgMap.get(msgNum); if (message == null) { @@ -135,7 +134,7 @@ public class Pop3Folder { messages.add(message); if (listener != null) { - listener.messageFinished(message, i++, (end - start) + 1); + listener.messageFinished(message); } } return messages; @@ -314,8 +313,7 @@ public class Pop3Folder { } catch (IOException ioe) { throw new MessagingException("fetch", ioe); } - for (int i = 0, count = messages.size(); i < count; i++) { - Pop3Message pop3Message = messages.get(i); + for (Pop3Message pop3Message : messages) { try { if (fp.contains(FetchProfile.Item.BODY)) { fetchBody(pop3Message, -1); @@ -337,7 +335,7 @@ public class Pop3Folder { pop3Message.setBody(null); } if (listener != null && !(fp.contains(FetchProfile.Item.ENVELOPE) && fp.size() == 1)) { - listener.messageFinished(pop3Message, i, count); + listener.messageFinished(pop3Message); } } catch (IOException ioe) { throw new MessagingException("Unable to fetch message", ioe); @@ -361,8 +359,7 @@ public class Pop3Folder { * In extreme cases we'll do a command per message instead of a bulk request * to hopefully save some time and bandwidth. */ - for (int i = 0, count = messages.size(); i < count; i++) { - Pop3Message message = messages.get(i); + for (Pop3Message message : messages) { String response = connection.executeSimpleCommand( String.format(Locale.US, LIST_COMMAND + " %d", uidToMsgNumMap.get(message.getUid()))); @@ -371,7 +368,7 @@ public class Pop3Folder { int msgSize = Integer.parseInt(listParts[2]); message.setSize(msgSize); if (listener != null) { - listener.messageFinished(message, i, count); + listener.messageFinished(message); } } } else { @@ -393,7 +390,7 @@ public class Pop3Folder { if (pop3Message != null && msgUidIndex.contains(pop3Message.getUid())) { pop3Message.setSize(msgSize); if (listener != null) { - listener.messageFinished(pop3Message, i, count); + listener.messageFinished(pop3Message); } i++; } diff --git a/mail/protocols/webdav/src/main/java/com/fsck/k9/mail/store/webdav/WebDavFolder.java b/mail/protocols/webdav/src/main/java/com/fsck/k9/mail/store/webdav/WebDavFolder.java index 98a79b8b74..ddf5adf28d 100644 --- a/mail/protocols/webdav/src/main/java/com/fsck/k9/mail/store/webdav/WebDavFolder.java +++ b/mail/protocols/webdav/src/main/java/com/fsck/k9/mail/store/webdav/WebDavFolder.java @@ -207,7 +207,6 @@ public class WebDavFolder { List messages = new ArrayList<>(); String[] uids; Map headers = new HashMap<>(); - int uidsLength; String messageBody; int prevStart = start; @@ -232,15 +231,14 @@ public class WebDavFolder { DataSet dataset = store.processRequest(this.mFolderUrl, "SEARCH", messageBody, headers); uids = dataset.getUids(); Map uidToUrl = dataset.getUidToUrl(); - uidsLength = uids.length; - for (int i = 0; i < uidsLength; i++) { - WebDavMessage message = new WebDavMessage(uids[i], this); - message.setUrl(uidToUrl.get(uids[i])); + for (String uid : uids) { + WebDavMessage message = new WebDavMessage(uid, this); + message.setUrl(uidToUrl.get(uid)); messages.add(message); if (listener != null) { - listener.messageFinished(message, i, uidsLength); + listener.messageFinished(message); } } @@ -306,8 +304,7 @@ public class WebDavFolder { /** * We can't hand off to processRequest() since we need the stream to parse. */ - for (int i = 0, count = messages.size(); i < count; i++) { - WebDavMessage wdMessage = messages.get(i); + for (WebDavMessage wdMessage : messages) { int statusCode = 0; /** @@ -398,7 +395,7 @@ public class WebDavFolder { } if (listener != null) { - listener.messageFinished(wdMessage, i, count); + listener.messageFinished(wdMessage); } } } @@ -504,10 +501,7 @@ public class WebDavFolder { Map envelopes = dataset.getMessageEnvelopes(); - int count = messages.size(); - for (int i = messages.size() - 1; i >= 0; i--) { - WebDavMessage message = messages.get(i); - + for (WebDavMessage message : messages) { ParsedMessageEnvelope envelope = envelopes.get(message.getUid()); if (envelope != null) { message.setNewHeaders(envelope); @@ -517,7 +511,7 @@ public class WebDavFolder { } if (listener != null) { - listener.messageFinished(messages.get(i), i, count); + listener.messageFinished(message); } } } diff --git a/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java index 08082308b5..79f3809f29 100644 --- a/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java +++ b/mail/protocols/webdav/src/test/java/com/fsck/k9/mail/store/webdav/WebDavFolderTest.java @@ -221,7 +221,7 @@ public class WebDavFolderTest { FetchProfile profile = new FetchProfile(); profile.add(FetchProfile.Item.BODY_SANE); folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE); - verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25)); + verify(listener, times(25)).messageFinished(any(WebDavMessage.class)); } @Test @@ -251,7 +251,7 @@ public class WebDavFolderTest { profile.add(FetchProfile.Item.FLAGS); profile.add(FetchProfile.Item.BODY); folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE); - verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), anyInt()); + verify(listener, times(25)).messageFinished(any(WebDavMessage.class)); } private void setupStoreForMessageFetching() { @@ -291,7 +291,7 @@ public class WebDavFolderTest { FetchProfile profile = new FetchProfile(); profile.add(FetchProfile.Item.BODY_SANE); folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE); - verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25)); + verify(listener, times(25)).messageFinished(any(WebDavMessage.class)); } @Test @@ -321,7 +321,7 @@ public class WebDavFolderTest { FetchProfile profile = new FetchProfile(); profile.add(FetchProfile.Item.BODY_SANE); folder.fetch(messages, profile, listener, MAX_DOWNLOAD_SIZE); - verify(listener, times(25)).messageFinished(any(WebDavMessage.class), anyInt(), eq(25)); + verify(listener, times(25)).messageFinished(any(WebDavMessage.class)); } @Test @@ -387,7 +387,7 @@ public class WebDavFolderTest { folder.getMessages(messageStart, messageEnd, listener); - verify(listener, times(5)).messageFinished(any(WebDavMessage.class), anyInt(), eq(5)); + verify(listener, times(5)).messageFinished(any(WebDavMessage.class)); } @Test -- GitLab From 51457a6859519e36c0a412b0a16eb616d39670c0 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 24 Feb 2022 20:28:03 +0100 Subject: [PATCH 07/50] Simplify `LocalStore.searchForMessages()` --- .../k9/controller/MessagingController.java | 39 ++----------- .../com/fsck/k9/mailstore/LocalStore.java | 8 +-- .../controller/MessagingControllerTest.java | 24 ++------ .../com/fsck/k9/external/MessageProvider.java | 56 ++++++------------- 4 files changed, 30 insertions(+), 97 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index f31c9ed68a..3a61eedd00 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -60,7 +60,6 @@ import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.FolderClass; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessageDownloadState; -import com.fsck.k9.mail.MessageRetrievalListener; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Part; import com.fsck.k9.mail.ServerSettings; @@ -401,48 +400,22 @@ public class MessagingController { /** * Find all messages in any local account which match the query 'query' */ - public void searchLocalMessages(final LocalSearch search, final MessagingListener listener) { - threadPool.execute(new Runnable() { - @Override - public void run() { - searchLocalMessagesSynchronous(search, listener); - } - }); - } - - @VisibleForTesting - void searchLocalMessagesSynchronous(final LocalSearch search, final MessagingListener listener) { + public List searchLocalMessages(final LocalSearch search) { List searchAccounts = getAccountsFromLocalSearch(search, preferences); + List messages = new ArrayList<>(); for (final Account account : searchAccounts) { - - // Collecting statistics of the search result - MessageRetrievalListener retrievalListener = new MessageRetrievalListener() { - @Override - public void messageFinished(LocalMessage message) { - if (!isMessageSuppressed(message)) { - List messages = new ArrayList<>(); - - messages.add(message); - if (listener != null) { - listener.listLocalMessagesAddMessages(account, null, messages); - } - } - } - }; - - // build and do the query in the localstore try { LocalStore localStore = localStoreProvider.getInstance(account); - localStore.searchForMessages(retrievalListener, search); + List localMessages = localStore.searchForMessages(search); + + messages.addAll(localMessages); } catch (Exception e) { Timber.e(e); } } - if (listener != null) { - listener.listLocalMessagesFinished(); - } + return messages; } public Future searchRemoteMessages(String acctUuid, long folderId, String query, Set requiredFlags, diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java b/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java index 73beaea139..56615f95d2 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java +++ b/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java @@ -359,9 +359,7 @@ public class LocalStore { }); } - public List searchForMessages(MessageRetrievalListener retrievalListener, - LocalSearch search) throws MessagingException { - + public List searchForMessages(LocalSearch search) throws MessagingException { StringBuilder query = new StringBuilder(); List queryArgs = new ArrayList<>(); SqlQueryBuilder.buildWhereClause(account, search.getConditions(), query, queryArgs); @@ -382,7 +380,7 @@ public class LocalStore { Timber.d("Query = %s", sqlQuery); - return getMessages(retrievalListener, null, sqlQuery, selectionArgs); + return getMessages(null, null, sqlQuery, selectionArgs); } /* @@ -443,7 +441,7 @@ public class LocalStore { LocalSearch search = new LocalSearch(); search.and(SearchField.THREAD_ID, rootIdString, Attribute.EQUALS); - return searchForMessages(null, search); + return searchForMessages(search); } public AttachmentInfo getAttachmentInfo(final String attachmentId) throws MessagingException { diff --git a/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java b/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java index ccb348672f..978788b78a 100644 --- a/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java +++ b/app/core/src/test/java/com/fsck/k9/controller/MessagingControllerTest.java @@ -52,6 +52,7 @@ import org.mockito.stubbing.Answer; import org.robolectric.RuntimeEnvironment; import org.robolectric.shadows.ShadowLog; +import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.ArgumentMatchers.eq; @@ -178,32 +179,17 @@ public class MessagingControllerTest extends K9RobolectricTest { } @Test - public void searchLocalMessagesSynchronous_shouldCallSearchForMessagesOnLocalStore() - throws Exception { - when(search.searchAllAccounts()).thenReturn(true); - when(search.getAccountUuids()).thenReturn(new String[0]); - - controller.searchLocalMessagesSynchronous(search, listener); - - verify(localStore).searchForMessages(nullable(MessageRetrievalListener.class), eq(search)); - } - - @Test - public void searchLocalMessagesSynchronous_shouldNotifyWhenStoreFinishesRetrievingAMessage() + public void searchLocalMessages_shouldIgnoreExceptions() throws Exception { LocalMessage localMessage = mock(LocalMessage.class); when(localMessage.getFolder()).thenReturn(localFolder); when(search.searchAllAccounts()).thenReturn(true); when(search.getAccountUuids()).thenReturn(new String[0]); - when(localStore.searchForMessages(nullable(MessageRetrievalListener.class), eq(search))) - .thenThrow(new MessagingException("Test")); + when(localStore.searchForMessages(search)).thenThrow(new MessagingException("Test")); - controller.searchLocalMessagesSynchronous(search, listener); + List messages = controller.searchLocalMessages(search); - verify(localStore).searchForMessages(messageRetrievalListenerCaptor.capture(), eq(search)); - messageRetrievalListenerCaptor.getValue().messageFinished(localMessage); - verify(listener).listLocalMessagesAddMessages(eq(account), - eq((String) null), eq(Collections.singletonList(localMessage))); + assertThat(messages).isEmpty(); } private void setupRemoteSearch() throws Exception { diff --git a/app/k9mail/src/main/java/com/fsck/k9/external/MessageProvider.java b/app/k9mail/src/main/java/com/fsck/k9/external/MessageProvider.java index b05091dc68..8ef22f8e80 100644 --- a/app/k9mail/src/main/java/com/fsck/k9/external/MessageProvider.java +++ b/app/k9mail/src/main/java/com/fsck/k9/external/MessageProvider.java @@ -478,16 +478,12 @@ public class MessageProvider extends ContentProvider { } protected MatrixCursor getMessages(String[] projection) throws InterruptedException { - BlockingQueue> queue = new SynchronousQueue<>(); - // new code for integrated inbox, only execute this once as it will be processed afterwards via the listener SearchAccount integratedInboxAccount = SearchAccount.createUnifiedInboxAccount(); MessagingController msgController = MessagingController.getInstance(getContext()); - msgController.searchLocalMessages(integratedInboxAccount.getRelatedSearch(), - new MessageInfoHolderRetrieverListener(queue)); - - List holders = queue.take(); + List messages = msgController.searchLocalMessages(integratedInboxAccount.getRelatedSearch()); + List holders = convertToMessageInfoHolder(messages); // TODO add sort order parameter Collections.sort(holders, new ReverseDateComparator()); @@ -521,6 +517,20 @@ public class MessageProvider extends ContentProvider { return cursor; } + private List convertToMessageInfoHolder(List messages) { + List holders = new ArrayList<>(); + + Context context = getContext(); + for (LocalMessage message : messages) { + Account messageAccount = message.getAccount(); + MessageInfoHolder messageInfoHolder = MessageInfoHolder.create(context, message, messageAccount); + + holders.add(messageInfoHolder); + } + + return holders; + } + protected LinkedHashMap> resolveMessageExtractors( String[] projection, int count) { LinkedHashMap> extractors = new LinkedHashMap<>(); @@ -1033,38 +1043,4 @@ public class MessageProvider extends ContentProvider { return wrapped; } } - - /** - * Synchronized listener used to retrieve {@link MessageInfoHolder}s using a given {@link BlockingQueue}. - */ - protected class MessageInfoHolderRetrieverListener extends SimpleMessagingListener { - private final BlockingQueue> queue; - private List holders = new ArrayList<>(); - - - public MessageInfoHolderRetrieverListener(BlockingQueue> queue) { - this.queue = queue; - } - - @Override - public void listLocalMessagesAddMessages(Account account, String folderServerId, List messages) { - Context context = getContext(); - - for (LocalMessage message : messages) { - Account messageAccount = message.getAccount(); - MessageInfoHolder messageInfoHolder = MessageInfoHolder.create(context, message, messageAccount); - - holders.add(messageInfoHolder); - } - } - - @Override - public void listLocalMessagesFinished() { - try { - queue.put(holders); - } catch (InterruptedException e) { - Timber.e(e, "Unable to return message list back to caller"); - } - } - } } -- GitLab From c206af5ab054782027de10e88af4b9b5b92c5378 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 24 Feb 2022 20:34:45 +0100 Subject: [PATCH 08/50] Remove unnecessary method parameter --- .../java/com/fsck/k9/mailstore/LocalFolder.java | 2 +- .../java/com/fsck/k9/mailstore/LocalStore.java | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/LocalFolder.java b/app/core/src/main/java/com/fsck/k9/mailstore/LocalFolder.java index bed0363b49..72441da924 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/LocalFolder.java +++ b/app/core/src/main/java/com/fsck/k9/mailstore/LocalFolder.java @@ -585,7 +585,7 @@ public class LocalFolder { @Override public List doDbWork(final SQLiteDatabase db) throws MessagingException { open(); - return LocalFolder.this.localStore.getMessages(null, LocalFolder.this, + return LocalFolder.this.localStore.getMessages(LocalFolder.this, "SELECT " + LocalStore.GET_MESSAGES_COLS + "FROM messages " + "LEFT JOIN message_parts ON (message_parts.id = messages.message_part_id) " + diff --git a/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java b/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java index 56615f95d2..1fb427d6cd 100644 --- a/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java +++ b/app/core/src/main/java/com/fsck/k9/mailstore/LocalStore.java @@ -43,7 +43,6 @@ import com.fsck.k9.mail.FetchProfile.Item; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.FolderClass; import com.fsck.k9.mail.FolderType; -import com.fsck.k9.mail.MessageRetrievalListener; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Multipart; import com.fsck.k9.mail.Part; @@ -380,18 +379,15 @@ public class LocalStore { Timber.d("Query = %s", sqlQuery); - return getMessages(null, null, sqlQuery, selectionArgs); + return getMessages(null, sqlQuery, selectionArgs); } /* * Given a query string, actually do the query for the messages and * call the MessageRetrievalListener for each one */ - List getMessages( - final MessageRetrievalListener listener, - final LocalFolder folder, - final String queryString, final String[] placeHolders - ) throws MessagingException { + List getMessages(LocalFolder folder, String queryString, String[] placeHolders) + throws MessagingException { final List messages = new ArrayList<>(); database.execute(false, new DbCallback() { @Override @@ -405,9 +401,6 @@ public class LocalStore { message.populateFromGetMessageCursor(cursor); messages.add(message); - if (listener != null) { - listener.messageFinished(message); - } } cursor.close(); cursor = db.rawQuery(queryString + " LIMIT -1 OFFSET 10", placeHolders); @@ -417,9 +410,6 @@ public class LocalStore { message.populateFromGetMessageCursor(cursor); messages.add(message); - if (listener != null) { - listener.messageFinished(message); - } } } catch (Exception e) { Timber.d(e, "Got an exception"); -- GitLab From 237a6024e47ae99dad5c96a6640c0c57879c9439 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 24 Feb 2022 20:37:23 +0100 Subject: [PATCH 09/50] Remove unused callbacks --- .../k9/controller/MessagingController.java | 24 +++++++------------ .../fsck/k9/controller/MessagingListener.java | 5 ---- .../controller/SimpleMessagingListener.java | 13 ---------- 3 files changed, 8 insertions(+), 34 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java index 3a61eedd00..2f0e3af215 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -2163,7 +2163,7 @@ public class MessagingController { // Remove all messages marked as deleted folder.destroyDeletedMessages(); - compact(account, null); + compact(account); } public void emptyTrash(final Account account, MessagingListener listener) { @@ -2447,21 +2447,13 @@ public class MessagingController { notificationController.clearFetchingMailNotification(account); } - public void compact(final Account account, final MessagingListener ml) { - putBackground("compact:" + account, ml, new Runnable() { - @Override - public void run() { - try { - MessageStore messageStore = messageStoreManager.getMessageStore(account); - long oldSize = messageStore.getSize(); - messageStore.compact(); - long newSize = messageStore.getSize(); - for (MessagingListener l : getListeners(ml)) { - l.accountSizeChanged(account, oldSize, newSize); - } - } catch (Exception e) { - Timber.e(e, "Failed to compact account %s", account); - } + public void compact(Account account) { + putBackground("compact:" + account, null, () -> { + try { + MessageStore messageStore = messageStoreManager.getMessageStore(account); + messageStore.compact(); + } catch (Exception e) { + Timber.e(e, "Failed to compact account %s", account); } }); } diff --git a/app/core/src/main/java/com/fsck/k9/controller/MessagingListener.java b/app/core/src/main/java/com/fsck/k9/controller/MessagingListener.java index 299f9e8dbd..e0ea6f01f1 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/MessagingListener.java +++ b/app/core/src/main/java/com/fsck/k9/controller/MessagingListener.java @@ -13,11 +13,6 @@ import com.fsck.k9.mailstore.LocalMessage; public interface MessagingListener { - void accountSizeChanged(Account account, long oldSize, long newSize); - - void listLocalMessagesAddMessages(Account account, String folderServerId, List messages); - void listLocalMessagesFinished(); - void synchronizeMailboxStarted(Account account, long folderId); void synchronizeMailboxHeadersStarted(Account account, String folderServerId); void synchronizeMailboxHeadersProgress(Account account, String folderServerId, int completed, int total); diff --git a/app/core/src/main/java/com/fsck/k9/controller/SimpleMessagingListener.java b/app/core/src/main/java/com/fsck/k9/controller/SimpleMessagingListener.java index 6f2747cf23..0f7c6b4098 100644 --- a/app/core/src/main/java/com/fsck/k9/controller/SimpleMessagingListener.java +++ b/app/core/src/main/java/com/fsck/k9/controller/SimpleMessagingListener.java @@ -9,22 +9,9 @@ import android.content.Context; import com.fsck.k9.Account; import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Part; -import com.fsck.k9.mailstore.LocalMessage; public abstract class SimpleMessagingListener implements MessagingListener { - @Override - public void accountSizeChanged(Account account, long oldSize, long newSize) { - } - - @Override - public void listLocalMessagesAddMessages(Account account, String folderServerId, List messages) { - } - - @Override - public void listLocalMessagesFinished() { - } - @Override public void synchronizeMailboxStarted(Account account, long folderId) { } -- GitLab From f66f7e50737f36daa94d11a06f11c5a6097aa946 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 25 Feb 2022 19:05:20 +0100 Subject: [PATCH 10/50] Use account settings when creating the NotificationChannel for messages --- .../k9/notification/NotificationChannelManager.kt | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 55b1233801..e1da468325 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 @@ -116,16 +116,15 @@ class NotificationChannelManager( @RequiresApi(api = Build.VERSION_CODES.O) private fun getChannelMessages(account: Account): NotificationChannel { val channelName = resourceProvider.messagesChannelName - val channelDescription = resourceProvider.messagesChannelDescription val channelId = getChannelIdFor(account, ChannelType.MESSAGES) val importance = NotificationManager.IMPORTANCE_DEFAULT - val channelGroupId = account.uuid - val messagesChannel = NotificationChannel(channelId, channelName, importance) - messagesChannel.description = channelDescription - messagesChannel.group = channelGroupId + return NotificationChannel(channelId, channelName, importance).apply { + description = resourceProvider.messagesChannelDescription + group = account.uuid - return messagesChannel + setPropertiesFrom(account) + } } @RequiresApi(api = Build.VERSION_CODES.O) @@ -189,7 +188,7 @@ class NotificationChannelManager( group = account.uuid copyPropertiesFrom(oldNotificationChannel) - copyPropertiesFrom(account) + setPropertiesFrom(account) } Timber.v("Recreating NotificationChannel(%s => %s)", oldChannelId, newChannelId) @@ -226,7 +225,7 @@ class NotificationChannelManager( } @RequiresApi(Build.VERSION_CODES.O) - private fun NotificationChannel.copyPropertiesFrom(account: Account) { + private fun NotificationChannel.setPropertiesFrom(account: Account) { val notificationSettings = account.notificationSettings if (notificationSettings.isRingEnabled) { -- GitLab From b5db89a7a4de8ff3c57d1f5073a2d2adb664c706 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 1 Mar 2022 18:17:51 +0100 Subject: [PATCH 11/50] Update Android Gradle Plugin to version 7.1.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index f08bff3702..0faf91b6eb 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.0.0" } -- GitLab From 82cd9a91cce8991baf34cf03dc923c964d5c80d4 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 1 Mar 2022 18:23:25 +0100 Subject: [PATCH 12/50] Update Gradle Wrapper to version 7.4 --- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a0f7639f7d..b1159fc54f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -- GitLab From 13b4ba10702738afac318dd15d944463fa61026c Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 1 Mar 2022 19:15:17 +0100 Subject: [PATCH 13/50] Update Android SDK build tools to version 32.0.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0faf91b6eb..dd62f0f7ae 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { 'compileSdk': 31, 'targetSdk': 31, 'minSdk': 21, - 'buildTools': '30.0.3', + 'buildTools': '32.0.0', 'robolectricSdk': 31 ] -- GitLab From 2c86c196ac5478ad7ae13aaea438a877ebcaa12a Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 1 Mar 2022 18:45:00 +0100 Subject: [PATCH 14/50] Update Kotlin to version 1.6.10 And update Moshi to version 1.13.0 to avoid an incompatibility with Kotlin 1.6 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index dd62f0f7ae..b0ab63ac3c 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,7 @@ buildscript { ] versions = [ - 'kotlin': '1.5.31', + 'kotlin': '1.6.10', 'kotlinCoroutines': '1.5.2', 'androidxAppCompat': '1.3.1', 'androidxActivity': '1.4.0', @@ -32,7 +32,7 @@ buildscript { 'fastAdapter': '5.5.1', 'preferencesFix': '1.1.0', 'okio': '2.10.0', - 'moshi': '1.12.0', + 'moshi': '1.13.0', 'timber': '5.0.1', 'koin': '3.1.3', 'commonsIo': '2.6', -- GitLab From 4c67bbd1564a1f37f7731f7b692ef2d70c78a97e Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 1 Mar 2022 19:22:19 +0100 Subject: [PATCH 15/50] Update kotlinx-coroutines to version 1.6.0 --- .../legacy/src/main/java/com/fsck/k9/ui/FlowExtensions.kt | 6 +++--- build.gradle | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/FlowExtensions.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/FlowExtensions.kt index 61689a8e5d..d1c6e6267b 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/FlowExtensions.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/FlowExtensions.kt @@ -5,13 +5,13 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.launch -fun Flow.observe(lifecycleOwner: LifecycleOwner, action: suspend (T) -> Unit) { +fun Flow.observe(lifecycleOwner: LifecycleOwner, collector: FlowCollector) { lifecycleOwner.lifecycleScope.launch { lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - collect(action) + collect(collector) } } } diff --git a/build.gradle b/build.gradle index b0ab63ac3c..c5310d62de 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { versions = [ 'kotlin': '1.6.10', - 'kotlinCoroutines': '1.5.2', + 'kotlinCoroutines': '1.6.0', 'androidxAppCompat': '1.3.1', 'androidxActivity': '1.4.0', 'androidxRecyclerView': '1.2.1', -- GitLab From e24d51a43710f16e7d956e2ec639d7fc1f171f1d Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 16:55:36 +0100 Subject: [PATCH 16/50] Remove @OptIn annotations that are now unnecessary --- .../src/main/java/com/fsck/k9/notification/NotificationData.kt | 1 - .../k9/notification/SingleMessageNotificationDataCreator.kt | 2 -- .../com/fsck/k9/notification/SummaryNotificationDataCreator.kt | 2 -- .../fsck/k9/notification/SummaryNotificationDataCreatorTest.kt | 1 - 4 files changed, 6 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationData.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationData.kt index c50985022b..ab6f363995 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/NotificationData.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationData.kt @@ -17,7 +17,6 @@ internal data class NotificationData( val isSingleMessageNotification: Boolean get() = activeNotifications.size == 1 - @OptIn(ExperimentalStdlibApi::class) val messageReferences: List get() { return buildList(capacity = newMessagesCount) { diff --git a/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationDataCreator.kt b/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationDataCreator.kt index c9012752fb..d095ef00b6 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationDataCreator.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/SingleMessageNotificationDataCreator.kt @@ -41,7 +41,6 @@ internal class SingleMessageNotificationDataCreator { ) } - @OptIn(ExperimentalStdlibApi::class) private fun createSingleNotificationActions(): List { return buildList { add(NotificationAction.Reply) @@ -53,7 +52,6 @@ internal class SingleMessageNotificationDataCreator { } } - @OptIn(ExperimentalStdlibApi::class) private fun createSingleNotificationWearActions(account: Account): List { return buildList { add(WearNotificationAction.Reply) diff --git a/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationDataCreator.kt b/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationDataCreator.kt index 77e1272826..6decadf15f 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationDataCreator.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/SummaryNotificationDataCreator.kt @@ -43,7 +43,6 @@ internal class SummaryNotificationDataCreator( ) } - @OptIn(ExperimentalStdlibApi::class) private fun createSummaryNotificationActions(): List { return buildList { add(SummaryNotificationAction.MarkAsRead) @@ -54,7 +53,6 @@ internal class SummaryNotificationDataCreator( } } - @OptIn(ExperimentalStdlibApi::class) private fun createSummaryWearNotificationActions(account: Account): List { return buildList { add(SummaryWearNotificationAction.MarkAsRead) diff --git a/app/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt b/app/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt index 216d20c1ae..906e6a0098 100644 --- a/app/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt +++ b/app/core/src/test/java/com/fsck/k9/notification/SummaryNotificationDataCreatorTest.kt @@ -266,7 +266,6 @@ class SummaryNotificationDataCreatorTest { return NotificationData(account, activeNotifications, inactiveNotifications = emptyList()) } - @OptIn(ExperimentalStdlibApi::class) private fun createNotificationDataWithMultipleMessages(times: Int = 2): NotificationData { val contentList = buildList { repeat(times) { -- GitLab From 52da227e7b78a5835c315c077d946c00ec262477 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 20:35:01 +0100 Subject: [PATCH 17/50] Update AndroidX AppCompat to version 1.4.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c5310d62de..a29c515140 100644 --- a/build.gradle +++ b/build.gradle @@ -13,7 +13,7 @@ buildscript { versions = [ 'kotlin': '1.6.10', 'kotlinCoroutines': '1.6.0', - 'androidxAppCompat': '1.3.1', + 'androidxAppCompat': '1.4.1', 'androidxActivity': '1.4.0', 'androidxRecyclerView': '1.2.1', 'androidxLifecycle': '2.4.0', -- GitLab From 213b7ff6b398f992b119c9c7c366edef0fb32749 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 20:35:59 +0100 Subject: [PATCH 18/50] Update AndroidX Lifecycle to version 2.4.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a29c515140..dd5f9f6f4d 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { 'androidxAppCompat': '1.4.1', 'androidxActivity': '1.4.0', 'androidxRecyclerView': '1.2.1', - 'androidxLifecycle': '2.4.0', + 'androidxLifecycle': '2.4.1', 'androidxAnnotation': '1.2.0', 'androidxBiometric': '1.1.0', 'androidxNavigation': '2.3.5', -- GitLab From cce049f8d55931eab654c5c96aec64f3726b05ad Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 20:37:17 +0100 Subject: [PATCH 19/50] Update AndroidX Annotation to version 1.3.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dd5f9f6f4d..55f3ff545a 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { 'androidxActivity': '1.4.0', 'androidxRecyclerView': '1.2.1', 'androidxLifecycle': '2.4.1', - 'androidxAnnotation': '1.2.0', + 'androidxAnnotation': '1.3.0', 'androidxBiometric': '1.1.0', 'androidxNavigation': '2.3.5', 'androidxConstraintLayout': '2.1.1', -- GitLab From c34340e14f02fe9103d0cf6386d9cc234ddcf279 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 20:55:07 +0100 Subject: [PATCH 20/50] Update AndroidX Navigation to version 2.4.1 --- app/ui/base/build.gradle | 4 ++-- build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/ui/base/build.gradle b/app/ui/base/build.gradle index 6b94ff2b65..97dc1f2bd1 100644 --- a/app/ui/base/build.gradle +++ b/app/ui/base/build.gradle @@ -7,8 +7,8 @@ dependencies { api "androidx.appcompat:appcompat:${versions.androidxAppCompat}" api "androidx.activity:activity:${versions.androidxActivity}" api "com.google.android.material:material:${versions.materialComponents}" - api "androidx.navigation:navigation-fragment-ktx:${versions.androidxNavigation}" - api "androidx.navigation:navigation-ui-ktx:${versions.androidxNavigation}" + api "androidx.navigation:navigation-fragment:${versions.androidxNavigation}" + api "androidx.navigation:navigation-ui:${versions.androidxNavigation}" api "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}" implementation "androidx.core:core-ktx:${versions.androidxCore}" diff --git a/build.gradle b/build.gradle index 55f3ff545a..feeafcbbfa 100644 --- a/build.gradle +++ b/build.gradle @@ -19,7 +19,7 @@ buildscript { 'androidxLifecycle': '2.4.1', 'androidxAnnotation': '1.3.0', 'androidxBiometric': '1.1.0', - 'androidxNavigation': '2.3.5', + 'androidxNavigation': '2.4.1', 'androidxConstraintLayout': '2.1.1', 'androidxWorkManager': '2.7.0', 'androidxFragment': '1.3.6', -- GitLab From cd5d90e8db0b1fcaadf9679216771a42ba4a2593 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 20:55:22 +0100 Subject: [PATCH 21/50] Update AndroidX WorkManager to version 2.7.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index feeafcbbfa..ce3658fe43 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ buildscript { 'androidxBiometric': '1.1.0', 'androidxNavigation': '2.4.1', 'androidxConstraintLayout': '2.1.1', - 'androidxWorkManager': '2.7.0', + 'androidxWorkManager': '2.7.1', 'androidxFragment': '1.3.6', 'androidxLocalBroadcastManager': '1.0.0', 'androidxCore': '1.7.0', -- GitLab From d0c066d86e5ceb1f6c3fb3b392a418c89a0d880c Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 21:25:12 +0100 Subject: [PATCH 22/50] Update AndroidX Fragment to version 1.4.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ce3658fe43..76d3250397 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ buildscript { 'androidxNavigation': '2.4.1', 'androidxConstraintLayout': '2.1.1', 'androidxWorkManager': '2.7.1', - 'androidxFragment': '1.3.6', + 'androidxFragment': '1.4.1', 'androidxLocalBroadcastManager': '1.0.0', 'androidxCore': '1.7.0', 'androidxCardView': '1.0.0', -- GitLab From 7814b138d3e60ebdc3e1d715e9ebb3fe9d910c0a Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 21:29:23 +0100 Subject: [PATCH 23/50] Update AndroidX Preference to version 1.2.0 --- .../com/fsck/k9/ui/settings/account/FolderListPreference.kt | 2 +- build.gradle | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/FolderListPreference.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/FolderListPreference.kt index c6ec7a0bff..8f6677ac5c 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/FolderListPreference.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/FolderListPreference.kt @@ -65,7 +65,7 @@ constructor( isEnabled = true } - override fun getSummary(): CharSequence { + override fun getSummary(): CharSequence? { // While folders are being loaded the summary returned by ListPreference will be empty. This leads to the // summary view being hidden. Once folders are loaded the summary updates and the list height changes. This // adds quite a bit of visual clutter. We avoid that by returning a placeholder summary value. diff --git a/build.gradle b/build.gradle index 76d3250397..602c1c6bbf 100644 --- a/build.gradle +++ b/build.gradle @@ -26,7 +26,7 @@ buildscript { 'androidxLocalBroadcastManager': '1.0.0', 'androidxCore': '1.7.0', 'androidxCardView': '1.0.0', - 'androidxPreference': '1.1.1', + 'androidxPreference': '1.2.0', 'androidxTestCore': '1.4.0', 'materialComponents': '1.4.0', 'fastAdapter': '5.5.1', -- GitLab From d914c09364de5e0c0eba62d08458b99a8c762c14 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 21:39:19 +0100 Subject: [PATCH 24/50] Update Material Components to version 1.5.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 602c1c6bbf..dccaf9b51d 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ buildscript { 'androidxCardView': '1.0.0', 'androidxPreference': '1.2.0', 'androidxTestCore': '1.4.0', - 'materialComponents': '1.4.0', + 'materialComponents': '1.5.0', 'fastAdapter': '5.5.1', 'preferencesFix': '1.1.0', 'okio': '2.10.0', -- GitLab From d02f2450b811eb697fee71677c35d178710ecc3f Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 21:40:44 +0100 Subject: [PATCH 25/50] Update FastAdapter to version 5.6.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dccaf9b51d..ee090f464f 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ buildscript { 'androidxPreference': '1.2.0', 'androidxTestCore': '1.4.0', 'materialComponents': '1.5.0', - 'fastAdapter': '5.5.1', + 'fastAdapter': '5.6.0', 'preferencesFix': '1.1.0', 'okio': '2.10.0', 'moshi': '1.13.0', -- GitLab From eb1ea4f2467eb147756582e9e9815c97738d24ef Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 21:44:16 +0100 Subject: [PATCH 26/50] Update MaterialDrawer to version 8.4.5 --- app/ui/legacy/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/ui/legacy/build.gradle b/app/ui/legacy/build.gradle index ce530a5eb5..a8e62de33b 100644 --- a/app/ui/legacy/build.gradle +++ b/app/ui/legacy/build.gradle @@ -32,8 +32,8 @@ dependencies { implementation "de.cketti.library.changelog:ckchangelog-core:2.0.0-beta02" implementation "com.splitwise:tokenautocomplete:4.0.0-beta01" implementation "de.cketti.safecontentresolver:safe-content-resolver-v21:1.0.0" - implementation 'com.mikepenz:materialdrawer:8.4.1' - implementation 'com.mikepenz:materialdrawer-iconics:8.3.3' + implementation 'com.mikepenz:materialdrawer:8.4.5' + implementation 'com.mikepenz:materialdrawer-iconics:8.4.5' implementation 'com.mikepenz:fontawesome-typeface:5.9.0.0-kotlin@aar' implementation 'com.github.ByteHamster:SearchPreference:v2.0.0' implementation "com.mikepenz:fastadapter:${versions.fastAdapter}" -- GitLab From aa2b0c0339e84216e96f35b0cf7bb4b6960c2876 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 21:58:38 +0100 Subject: [PATCH 27/50] Update Okio to version 3.0.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ee090f464f..fcca02cee8 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ buildscript { 'materialComponents': '1.5.0', 'fastAdapter': '5.6.0', 'preferencesFix': '1.1.0', - 'okio': '2.10.0', + 'okio': '3.0.0', 'moshi': '1.13.0', 'timber': '5.0.1', 'koin': '3.1.3', -- GitLab From 83daa2229aeebc22a6bb6f347da8beb24a377788 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 21:58:54 +0100 Subject: [PATCH 28/50] Update Koin to version 3.1.5 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fcca02cee8..624653b238 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ buildscript { 'okio': '3.0.0', 'moshi': '1.13.0', 'timber': '5.0.1', - 'koin': '3.1.3', + 'koin': '3.1.5', 'commonsIo': '2.6', 'mime4j': '0.8.6', 'okhttp': '4.9.2', -- GitLab From 5ffd16c126df74bc3b453d03b60b18cb2f7f8281 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 22:08:50 +0100 Subject: [PATCH 29/50] Update OkHttp to version 4.9.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 624653b238..1b00eccc86 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ buildscript { 'koin': '3.1.5', 'commonsIo': '2.6', 'mime4j': '0.8.6', - 'okhttp': '4.9.2', + 'okhttp': '4.9.3', 'minidns': '1.0.0', 'glide': '4.12.0', 'jsoup': '1.13.1', -- GitLab From 4bcdce5cebe1db38f660a7a21b8a01af6a270774 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 22:17:56 +0100 Subject: [PATCH 30/50] Update MiniDNS to version 1.0.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1b00eccc86..eaa4991cbe 100644 --- a/build.gradle +++ b/build.gradle @@ -38,7 +38,7 @@ buildscript { 'commonsIo': '2.6', 'mime4j': '0.8.6', 'okhttp': '4.9.3', - 'minidns': '1.0.0', + 'minidns': '1.0.3', 'glide': '4.12.0', 'jsoup': '1.13.1', -- GitLab From e1635a2ab2728d7b1dee116602fbf0c635e25cfa Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 22:25:08 +0100 Subject: [PATCH 31/50] Update Glide to version 4.13.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index eaa4991cbe..437ceffaee 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ buildscript { 'mime4j': '0.8.6', 'okhttp': '4.9.3', 'minidns': '1.0.3', - 'glide': '4.12.0', + 'glide': '4.13.1', 'jsoup': '1.13.1', 'androidxTestRunner': '1.4.0', -- GitLab From d5baa9982d2368eba3b084bc991d6aa8d76db2a7 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 3 Mar 2022 22:52:49 +0100 Subject: [PATCH 32/50] Update Jsoup to version 1.14.3 --- .../src/test/java/com/fsck/k9/message/html/HtmlHelper.kt | 4 ++-- .../src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt | 4 ++-- .../src/main/java/app/k9mail/html/cleaner/HeadCleaner.kt | 6 +++--- build.gradle | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/core/src/test/java/com/fsck/k9/message/html/HtmlHelper.kt b/app/core/src/test/java/com/fsck/k9/message/html/HtmlHelper.kt index d0725a4d62..2a31f49d3e 100644 --- a/app/core/src/test/java/com/fsck/k9/message/html/HtmlHelper.kt +++ b/app/core/src/test/java/com/fsck/k9/message/html/HtmlHelper.kt @@ -1,11 +1,11 @@ package com.fsck.k9.message.html import org.jsoup.Jsoup -import org.jsoup.safety.Whitelist as AllowList +import org.jsoup.safety.Safelist object HtmlHelper { @JvmStatic fun extractText(html: String): String { - return Jsoup.clean(html, AllowList.none()) + return Jsoup.clean(html, Safelist.none()) } } diff --git a/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt b/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt index 0720118d38..327d7f8061 100644 --- a/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt +++ b/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/BodyCleaner.kt @@ -2,7 +2,7 @@ package app.k9mail.html.cleaner import org.jsoup.nodes.Document import org.jsoup.safety.Cleaner -import org.jsoup.safety.Whitelist as AllowList +import org.jsoup.safety.Safelist internal class BodyCleaner { private val cleaner: Cleaner @@ -12,7 +12,7 @@ internal class BodyCleaner { ) init { - val allowList = AllowList.relaxed() + val allowList = Safelist.relaxed() .addTags("font", "hr", "ins", "del", "center", "map", "area", "title") .addAttributes("font", "color", "face", "size") .addAttributes( diff --git a/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/HeadCleaner.kt b/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/HeadCleaner.kt index 54b9920d16..d9ac70f810 100644 --- a/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/HeadCleaner.kt +++ b/app/html-cleaner/src/main/java/app/k9mail/html/cleaner/HeadCleaner.kt @@ -54,12 +54,12 @@ internal class CleaningVisitor( if (source === elementToSkip) { elementToSkip = null } else if (source is Element && isSafeTag(source)) { - destination = destination.parent() + destination = destination.parent() ?: error("Missing parent") } } - private fun isSafeTag(node: Node): Boolean { - if (isMetaRefresh(node)) return false + private fun isSafeTag(node: Node?): Boolean { + if (node == null || isMetaRefresh(node)) return false val tag = node.nodeName().lowercase() return tag in ALLOWED_TAGS diff --git a/build.gradle b/build.gradle index 437ceffaee..65465c22f3 100644 --- a/build.gradle +++ b/build.gradle @@ -40,7 +40,7 @@ buildscript { 'okhttp': '4.9.3', 'minidns': '1.0.3', 'glide': '4.13.1', - 'jsoup': '1.13.1', + 'jsoup': '1.14.3', 'androidxTestRunner': '1.4.0', 'junit': '4.13.2', -- GitLab From 729fcca5f26567ba821a2ed80b32f51930fc1278 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 4 Mar 2022 00:16:12 +0100 Subject: [PATCH 33/50] Update AndroidX LocalBroadcastManager to version 1.1.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 65465c22f3..a10af5bd48 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ buildscript { 'androidxConstraintLayout': '2.1.1', 'androidxWorkManager': '2.7.1', 'androidxFragment': '1.4.1', - 'androidxLocalBroadcastManager': '1.0.0', + 'androidxLocalBroadcastManager': '1.1.0', 'androidxCore': '1.7.0', 'androidxCardView': '1.0.0', 'androidxPreference': '1.2.0', -- GitLab From 464be2e45e231d14a9338686274b78a75d80b28b Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 4 Mar 2022 00:20:04 +0100 Subject: [PATCH 34/50] Update AndroidX ConstraintLayout to version 2.1.3 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a10af5bd48..5828661034 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ buildscript { 'androidxAnnotation': '1.3.0', 'androidxBiometric': '1.1.0', 'androidxNavigation': '2.4.1', - 'androidxConstraintLayout': '2.1.1', + 'androidxConstraintLayout': '2.1.3', 'androidxWorkManager': '2.7.1', 'androidxFragment': '1.4.1', 'androidxLocalBroadcastManager': '1.1.0', -- GitLab From 4581bf3f8bd1d4dada682f5d4160ef2755a94c50 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 4 Mar 2022 00:23:32 +0100 Subject: [PATCH 35/50] Update SearchPreference to version 2.1.0 --- app/ui/legacy/build.gradle | 2 +- .../fsck/k9/ui/settings/general/GeneralSettingsActivity.kt | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/ui/legacy/build.gradle b/app/ui/legacy/build.gradle index a8e62de33b..d7d82b89dc 100644 --- a/app/ui/legacy/build.gradle +++ b/app/ui/legacy/build.gradle @@ -35,7 +35,7 @@ dependencies { implementation 'com.mikepenz:materialdrawer:8.4.5' implementation 'com.mikepenz:materialdrawer-iconics:8.4.5' implementation 'com.mikepenz:fontawesome-typeface:5.9.0.0-kotlin@aar' - implementation 'com.github.ByteHamster:SearchPreference:v2.0.0' + implementation 'com.github.ByteHamster:SearchPreference:v2.1.0' implementation "com.mikepenz:fastadapter:${versions.fastAdapter}" implementation "com.mikepenz:fastadapter-extensions-drag:${versions.fastAdapter}" implementation "com.mikepenz:fastadapter-extensions-utils:${versions.fastAdapter}" diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsActivity.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsActivity.kt index 3aec73cd44..35c4bfd22e 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsActivity.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/general/GeneralSettingsActivity.kt @@ -16,7 +16,6 @@ import com.fsck.k9.ui.R import com.fsck.k9.ui.base.K9Activity import com.fsck.k9.ui.fragmentTransaction import com.fsck.k9.ui.fragmentTransactionWithBackStack -import com.fsck.k9.ui.resolveColorAttribute class GeneralSettingsActivity : K9Activity(), OnPreferenceStartScreenCallback, SearchPreferenceResultListener { private lateinit var searchPreferenceActionView: SearchPreferenceActionView @@ -63,8 +62,7 @@ class GeneralSettingsActivity : K9Activity(), OnPreferenceStartScreenCallback, S replace(R.id.generalSettingsContainer, fragment) } - val accentColor = theme.resolveColorAttribute(R.attr.colorAccent) - result.highlight(fragment as PreferenceFragmentCompat, accentColor) + result.highlight(fragment as PreferenceFragmentCompat) } override fun onCreateOptionsMenu(menu: Menu): Boolean { -- GitLab From b9d608419ed273293679bcb360fcfec4c117b147 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 4 Mar 2022 00:28:22 +0100 Subject: [PATCH 36/50] Update Mockito to version 4.3.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5828661034..6a02b28559 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ buildscript { 'androidxTestRunner': '1.4.0', 'junit': '4.13.2', 'robolectric': '4.7.3', - 'mockito': '4.0.0', + 'mockito': '4.3.1', 'mockitoKotlin': '4.0.0', 'truth': '1.1.3', 'turbine': '0.7.0', -- GitLab From f6454121f031e43a20ea7ba64538e97e613eabc4 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 4 Mar 2022 23:51:23 +0100 Subject: [PATCH 37/50] IMAP: Log number of downloaded messages (instead of unread) --- .../java/com/fsck/k9/backend/imap/ImapSync.kt | 42 +++++-------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt index 843204b8f1..eb545e18a3 100644 --- a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt +++ b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt @@ -195,7 +195,7 @@ internal class ImapSync( /* * Now we download the actual content of messages. */ - val newMessages = downloadMessages( + downloadMessages( syncConfig, remoteFolder, backendFolder, @@ -212,13 +212,7 @@ internal class ImapSync( backendFolder.setLastChecked(System.currentTimeMillis()) backendFolder.setStatus(null) - Timber.d( - "Done synchronizing folder %s:%s @ %tc with %d new messages", - accountName, - folder, - System.currentTimeMillis(), - newMessages - ) + Timber.d("Done synchronizing folder %s:%s @ %tc", accountName, folder, System.currentTimeMillis()) listener.syncFinished(folder) @@ -286,7 +280,6 @@ internal class ImapSync( * A list of messages objects that store the UIDs of which messages to download. * @param flagSyncOnly * Only flags will be fetched from the remote store if this is `true`. - * @return The number of downloaded messages that are not flagged as [Flag.SEEN]. */ private fun downloadMessages( syncConfig: SyncConfig, @@ -296,12 +289,12 @@ internal class ImapSync( flagSyncOnly: Boolean, highestKnownUid: Long?, listener: SyncListener - ): Int { + ) { val folder = remoteFolder.serverId val syncFlagMessages = mutableListOf() var unsyncedMessages = mutableListOf() - val newMessages = AtomicInteger(0) + val downloadedMessageCount = AtomicInteger(0) val messages = inputMessages.toMutableList() for (message in messages) { @@ -377,7 +370,7 @@ internal class ImapSync( backendFolder, smallMessages, progress, - newMessages, + downloadedMessageCount, todo, fp, highestKnownUid, @@ -395,7 +388,7 @@ internal class ImapSync( backendFolder, largeMessages, progress, - newMessages, + downloadedMessageCount, todo, fp, highestKnownUid, @@ -410,9 +403,7 @@ internal class ImapSync( */ refreshLocalMessageFlags(syncConfig, remoteFolder, backendFolder, syncFlagMessages, progress, todo, listener) - Timber.d("SYNC: Synced remote messages for folder %s, %d new messages", folder, newMessages.get()) - - return newMessages.get() + Timber.d("SYNC: Synced remote messages for folder %s, %d new messages", folder, downloadedMessageCount.get()) } private fun evaluateMessageForDownload( @@ -519,7 +510,7 @@ internal class ImapSync( backendFolder: BackendFolder, smallMessages: List, progress: AtomicInteger, - newMessages: AtomicInteger, + downloadedMessageCount: AtomicInteger, todo: Int, fetchProfile: FetchProfile, highestKnownUid: Long?, @@ -538,11 +529,7 @@ internal class ImapSync( // Store the updated message locally backendFolder.saveMessage(message, MessageDownloadState.FULL) progress.incrementAndGet() - - // Increment the number of "new messages" if the newly downloaded message is not marked as read. - if (!message.isSet(Flag.SEEN)) { - newMessages.incrementAndGet() - } + downloadedMessageCount.incrementAndGet() val messageServerId = message.uid Timber.v( @@ -571,7 +558,7 @@ internal class ImapSync( backendFolder: BackendFolder, largeMessages: List, progress: AtomicInteger, - newMessages: AtomicInteger, + downloadedMessageCount: AtomicInteger, todo: Int, fetchProfile: FetchProfile, highestKnownUid: Long?, @@ -597,14 +584,7 @@ internal class ImapSync( // Update the listener with what we've found progress.incrementAndGet() - - // TODO do we need to re-fetch this here? - val flags = backendFolder.getMessageFlags(messageServerId) - // Increment the number of "new messages" if the newly downloaded message is - // not marked as read. - if (!flags.contains(Flag.SEEN)) { - newMessages.incrementAndGet() - } + downloadedMessageCount.incrementAndGet() listener.syncProgress(folder, progress.get(), todo) -- GitLab From 1d5a291ac7e250e906e8b48c1006de4c49cb7ffb Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 5 Mar 2022 00:11:16 +0100 Subject: [PATCH 38/50] Don't reuse `FetchProfile` instance --- .../java/com/fsck/k9/backend/imap/ImapSync.kt | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt index eb545e18a3..783765cfd8 100644 --- a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt +++ b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt @@ -326,10 +326,6 @@ internal class ImapSync( unsyncedMessages = unsyncedMessages.subList(0, visibleLimit) } - val fp = FetchProfile() - fp.add(FetchProfile.Item.FLAGS) - fp.add(FetchProfile.Item.ENVELOPE) - Timber.d("SYNC: About to fetch %d unsynced messages for folder %s", unsyncedMessages.size, folder) fetchUnsyncedMessages( @@ -340,7 +336,6 @@ internal class ImapSync( largeMessages, progress, todo, - fp, listener ) @@ -360,11 +355,7 @@ internal class ImapSync( * download of 625k. */ val maxDownloadSize = syncConfig.maximumAutoDownloadMessageSize - var fp = FetchProfile() // TODO: Only fetch small and large messages if we have some - fp.add(FetchProfile.Item.BODY) - // fp.add(FetchProfile.Item.FLAGS); - // fp.add(FetchProfile.Item.ENVELOPE); downloadSmallMessages( remoteFolder, backendFolder, @@ -372,7 +363,6 @@ internal class ImapSync( progress, downloadedMessageCount, todo, - fp, highestKnownUid, listener ) @@ -381,8 +371,6 @@ internal class ImapSync( /* * Now do the large messages that require more round trips. */ - fp = FetchProfile() - fp.add(FetchProfile.Item.STRUCTURE) downloadLargeMessages( remoteFolder, backendFolder, @@ -390,7 +378,6 @@ internal class ImapSync( progress, downloadedMessageCount, todo, - fp, highestKnownUid, listener, maxDownloadSize @@ -465,10 +452,13 @@ internal class ImapSync( largeMessages: MutableList, progress: AtomicInteger, todo: Int, - fetchProfile: FetchProfile, listener: SyncListener ) { val folder = remoteFolder.serverId + val fetchProfile = FetchProfile().apply { + add(FetchProfile.Item.FLAGS) + add(FetchProfile.Item.ENVELOPE) + } remoteFolder.fetch( unsyncedMessages, @@ -512,11 +502,13 @@ internal class ImapSync( progress: AtomicInteger, downloadedMessageCount: AtomicInteger, todo: Int, - fetchProfile: FetchProfile, highestKnownUid: Long?, listener: SyncListener ) { val folder = remoteFolder.serverId + val fetchProfile = FetchProfile().apply { + add(FetchProfile.Item.BODY) + } Timber.d("SYNC: Fetching %d small messages for folder %s", smallMessages.size, folder) @@ -560,12 +552,15 @@ internal class ImapSync( progress: AtomicInteger, downloadedMessageCount: AtomicInteger, todo: Int, - fetchProfile: FetchProfile, highestKnownUid: Long?, listener: SyncListener, maxDownloadSize: Int ) { val folder = remoteFolder.serverId + val fetchProfile = FetchProfile().apply { + add(FetchProfile.Item.STRUCTURE) + } + Timber.d("SYNC: Fetching large messages for folder %s", folder) remoteFolder.fetch(largeMessages, fetchProfile, null, maxDownloadSize) -- GitLab From 9dd076686b20f644dabb6c4e9b1626fd210bf156 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 5 Mar 2022 00:14:00 +0100 Subject: [PATCH 39/50] Remove unnecessary/unused method parameters --- .../k9/backend/imap/CommandFetchMessage.kt | 2 +- .../java/com/fsck/k9/backend/imap/ImapSync.kt | 21 +++++-------------- .../fsck/k9/backend/imap/TestImapFolder.kt | 1 - .../com/fsck/k9/mail/store/imap/ImapFolder.kt | 1 - .../fsck/k9/mail/store/imap/RealImapFolder.kt | 3 --- .../k9/mail/store/imap/RealImapFolderTest.kt | 6 +++--- .../fsck/k9/mail/store/imap/TestImapFolder.kt | 1 - 7 files changed, 9 insertions(+), 26 deletions(-) diff --git a/backend/imap/src/main/java/com/fsck/k9/backend/imap/CommandFetchMessage.kt b/backend/imap/src/main/java/com/fsck/k9/backend/imap/CommandFetchMessage.kt index 059fcd4a5c..9feb47492d 100644 --- a/backend/imap/src/main/java/com/fsck/k9/backend/imap/CommandFetchMessage.kt +++ b/backend/imap/src/main/java/com/fsck/k9/backend/imap/CommandFetchMessage.kt @@ -13,7 +13,7 @@ internal class CommandFetchMessage(private val imapStore: ImapStore) { folder.open(OpenMode.READ_WRITE) val message = folder.getMessage(messageServerId) - folder.fetchPart(message, part, null, bodyFactory, -1) + folder.fetchPart(message, part, bodyFactory, -1) } finally { folder.close() } diff --git a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt index 783765cfd8..2687964fba 100644 --- a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt +++ b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt @@ -200,7 +200,6 @@ internal class ImapSync( remoteFolder, backendFolder, remoteMessages, - false, highestKnownUid, listener ) @@ -260,7 +259,6 @@ internal class ImapSync( remoteFolder, backendFolder, listOf(remoteMessage), - false, null, SimpleSyncListener() ) @@ -278,15 +276,12 @@ internal class ImapSync( * The [BackendFolder] instance corresponding to the remote folder. * @param inputMessages * A list of messages objects that store the UIDs of which messages to download. - * @param flagSyncOnly - * Only flags will be fetched from the remote store if this is `true`. */ private fun downloadMessages( syncConfig: SyncConfig, remoteFolder: ImapFolder, backendFolder: BackendFolder, inputMessages: List, - flagSyncOnly: Boolean, highestKnownUid: Long?, listener: SyncListener ) { @@ -301,10 +296,8 @@ internal class ImapSync( evaluateMessageForDownload( message, backendFolder, - remoteFolder, unsyncedMessages, - syncFlagMessages, - flagSyncOnly + syncFlagMessages ) } @@ -396,10 +389,8 @@ internal class ImapSync( private fun evaluateMessageForDownload( message: ImapMessage, backendFolder: BackendFolder, - remoteFolder: ImapFolder, unsyncedMessages: MutableList, - syncFlagMessages: MutableList, - flagSyncOnly: Boolean + syncFlagMessages: MutableList ) { val messageServerId = message.uid if (message.isSet(Flag.DELETED)) { @@ -410,10 +401,8 @@ internal class ImapSync( val messagePresentLocally = backendFolder.isMessagePresent(messageServerId) if (!messagePresentLocally) { - if (!flagSyncOnly) { - Timber.v("Message with uid %s has not yet been downloaded", messageServerId) - unsyncedMessages.add(message) - } + Timber.v("Message with uid %s has not yet been downloaded", messageServerId) + unsyncedMessages.add(message) return } @@ -668,7 +657,7 @@ internal class ImapSync( */ val bodyFactory: BodyFactory = DefaultBodyFactory() for (part in viewables) { - remoteFolder.fetchPart(message, part, null, bodyFactory, maxDownloadSize) + remoteFolder.fetchPart(message, part, bodyFactory, maxDownloadSize) } // Store the updated message locally diff --git a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt index fe78507fc3..8b97bfa61e 100644 --- a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt +++ b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt @@ -116,7 +116,6 @@ class TestImapFolder(override val serverId: String) : ImapFolder { override fun fetchPart( message: ImapMessage, part: Part, - listener: MessageRetrievalListener?, bodyFactory: BodyFactory, maxDownloadSize: Int ) { diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt index ffd4733185..5589d6de70 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt @@ -53,7 +53,6 @@ interface ImapFolder { fun fetchPart( message: ImapMessage, part: Part, - listener: MessageRetrievalListener?, bodyFactory: BodyFactory, maxDownloadSize: Int ) diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt index 64c1959e3a..81d85f9bfe 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt @@ -627,7 +627,6 @@ internal class RealImapFolder( override fun fetchPart( message: ImapMessage, part: Part, - listener: MessageRetrievalListener?, bodyFactory: BodyFactory, maxDownloadSize: Int ) { @@ -683,8 +682,6 @@ internal class RealImapFolder( } } } - - listener?.messageFinished(message) } else { handleUntaggedResponse(response) } diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt index 927e66baf1..9e5866a50b 100644 --- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt +++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/RealImapFolderTest.kt @@ -794,7 +794,7 @@ class RealImapFolderTest { val part = createPart("TEXT") whenever(imapConnection.readResponse(anyOrNull())).thenReturn(createImapResponse("x OK")) - folder.fetchPart(message, part, null, mock(), 4096) + folder.fetchPart(message, part, mock(), 4096) verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[TEXT]<0.4096>)", false) } @@ -808,7 +808,7 @@ class RealImapFolderTest { val part = createPart("1.1") whenever(imapConnection.readResponse(anyOrNull())).thenReturn(createImapResponse("x OK")) - folder.fetchPart(message, part, null, mock(), MAX_DOWNLOAD_SIZE) + folder.fetchPart(message, part, mock(), MAX_DOWNLOAD_SIZE) verify(imapConnection).sendCommand("UID FETCH 1 (UID BODY.PEEK[1.1])", false) } @@ -822,7 +822,7 @@ class RealImapFolderTest { val part = createPlainTextPart("1.1") setupSingleFetchResponseToCallback() - folder.fetchPart(message, part, null, DefaultBodyFactory(), MAX_DOWNLOAD_SIZE) + folder.fetchPart(message, part, DefaultBodyFactory(), MAX_DOWNLOAD_SIZE) val bodyArgumentCaptor = argumentCaptor() verify(part).body = bodyArgumentCaptor.capture() diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt index 75da635116..67be422ed0 100644 --- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt +++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt @@ -77,7 +77,6 @@ internal open class TestImapFolder( override fun fetchPart( message: ImapMessage, part: Part, - listener: MessageRetrievalListener?, bodyFactory: BodyFactory, maxDownloadSize: Int ) { -- GitLab From 556188efcc7d361daaf6941b500f399b85f77550 Mon Sep 17 00:00:00 2001 From: cketti Date: Sat, 5 Mar 2022 01:13:46 +0100 Subject: [PATCH 40/50] Properly report progress when receiving multiple FETCH responses per message --- .../java/com/fsck/k9/backend/imap/ImapSync.kt | 22 +++--- .../com/fsck/k9/backend/imap/ImapSyncTest.kt | 67 ++++++++++++++++--- .../fsck/k9/backend/imap/TestImapFolder.kt | 9 +-- .../com/fsck/k9/backend/imap/TestImapStore.kt | 17 +++-- .../com/fsck/k9/mail/store/imap/ImapFolder.kt | 6 +- .../fsck/k9/mail/store/imap/RealImapFolder.kt | 8 ++- .../fsck/k9/mail/store/imap/TestImapFolder.kt | 2 +- 7 files changed, 99 insertions(+), 32 deletions(-) diff --git a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt index 2687964fba..8b67a6ea0d 100644 --- a/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt +++ b/backend/imap/src/main/java/com/fsck/k9/backend/imap/ImapSync.kt @@ -13,8 +13,8 @@ import com.fsck.k9.mail.DefaultBodyFactory import com.fsck.k9.mail.FetchProfile import com.fsck.k9.mail.Flag import com.fsck.k9.mail.MessageDownloadState -import com.fsck.k9.mail.MessageRetrievalListener import com.fsck.k9.mail.internet.MessageExtractor +import com.fsck.k9.mail.store.imap.FetchListener import com.fsck.k9.mail.store.imap.ImapFolder import com.fsck.k9.mail.store.imap.ImapMessage import com.fsck.k9.mail.store.imap.ImapStore @@ -452,15 +452,18 @@ internal class ImapSync( remoteFolder.fetch( unsyncedMessages, fetchProfile, - object : MessageRetrievalListener { - override fun messageFinished(message: ImapMessage) { + object : FetchListener { + override fun onFetchResponse(message: ImapMessage, isFirstResponse: Boolean) { try { if (message.isSet(Flag.DELETED)) { Timber.v( "Newly downloaded message %s:%s:%s was marked deleted on server, skipping", accountName, folder, message.uid ) - progress.incrementAndGet() + + if (isFirstResponse) { + progress.incrementAndGet() + } // TODO: This might be the source of poll count errors in the UI. Is todo always the same as ofTotal listener.syncProgress(folder, progress.get(), todo) @@ -504,13 +507,16 @@ internal class ImapSync( remoteFolder.fetch( smallMessages, fetchProfile, - object : MessageRetrievalListener { - override fun messageFinished(message: ImapMessage) { + object : FetchListener { + override fun onFetchResponse(message: ImapMessage, isFirstResponse: Boolean) { try { // Store the updated message locally backendFolder.saveMessage(message, MessageDownloadState.FULL) - progress.incrementAndGet() - downloadedMessageCount.incrementAndGet() + + if (isFirstResponse) { + progress.incrementAndGet() + downloadedMessageCount.incrementAndGet() + } val messageServerId = message.uid Timber.v( diff --git a/backend/imap/src/test/java/com/fsck/k9/backend/imap/ImapSyncTest.kt b/backend/imap/src/test/java/com/fsck/k9/backend/imap/ImapSyncTest.kt index cb990fbb90..273de02a11 100644 --- a/backend/imap/src/test/java/com/fsck/k9/backend/imap/ImapSyncTest.kt +++ b/backend/imap/src/test/java/com/fsck/k9/backend/imap/ImapSyncTest.kt @@ -5,11 +5,14 @@ import com.fsck.k9.backend.api.FolderInfo import com.fsck.k9.backend.api.SyncConfig import com.fsck.k9.backend.api.SyncConfig.ExpungePolicy import com.fsck.k9.backend.api.SyncListener +import com.fsck.k9.mail.FetchProfile import com.fsck.k9.mail.Flag import com.fsck.k9.mail.FolderType import com.fsck.k9.mail.Message import com.fsck.k9.mail.MessageDownloadState import com.fsck.k9.mail.buildMessage +import com.fsck.k9.mail.store.imap.FetchListener +import com.fsck.k9.mail.store.imap.ImapMessage import com.google.common.truth.Truth.assertThat import java.util.Date import org.apache.james.mime4j.dom.field.DateTimeField @@ -17,6 +20,7 @@ import org.apache.james.mime4j.field.DefaultFieldParser import org.junit.Test import org.mockito.Mockito.verify import org.mockito.kotlin.any +import org.mockito.kotlin.atLeast import org.mockito.kotlin.eq import org.mockito.kotlin.mock import org.mockito.kotlin.never @@ -208,6 +212,35 @@ class ImapSyncTest { verify(syncListener).syncNewMessage(FOLDER_SERVER_ID, messageServerId = "1", isOldMessage = false) } + @Test + fun `sync with multiple FETCH responses when downloading small message should report correct progress`() { + val folderServerId = "FOLDER_TWO" + backendStorage.createBackendFolder(folderServerId) + val specialImapFolder = object : TestImapFolder(folderServerId) { + override fun fetch( + messages: List, + fetchProfile: FetchProfile, + listener: FetchListener?, + maxDownloadSize: Int + ) { + super.fetch(messages, fetchProfile, listener, maxDownloadSize) + + // When fetching the body simulate an additional FETCH response + if (FetchProfile.Item.BODY in fetchProfile) { + val message = messages.first() + listener?.onFetchResponse(message, isFirstResponse = false) + } + } + } + specialImapFolder.addMessage(42) + imapStore.addFolder(specialImapFolder) + + imapSync.sync(folderServerId, defaultSyncConfig, syncListener) + + verify(syncListener, atLeast(1)).syncProgress(folderServerId, completed = 1, total = 1) + verify(syncListener, never()).syncProgress(folderServerId, completed = 2, total = 1) + } + private fun addMessageToBackendFolder(uid: Long, date: String = DEFAULT_MESSAGE_DATE) { val messageServerId = uid.toString() val message = createSimpleMessage(messageServerId, date).apply { @@ -222,13 +255,21 @@ class ImapSyncTest { } private fun addMessageToImapFolder(uid: Long, flags: Set = emptySet(), date: String = DEFAULT_MESSAGE_DATE) { + imapFolder.addMessage(uid, flags, date) + } + + private fun TestImapFolder.addMessage( + uid: Long, + flags: Set = emptySet(), + date: String = DEFAULT_MESSAGE_DATE + ) { val messageServerId = uid.toString() val message = createSimpleMessage(messageServerId, date) - imapFolder.addMessage(uid, message) + addMessage(uid, message) if (flags.isNotEmpty()) { - val imapMessage = imapFolder.getMessage(messageServerId) - imapFolder.setFlags(listOf(imapMessage), flags, true) + val imapMessage = getMessage(messageServerId) + setFlags(listOf(imapMessage), flags, true) } } @@ -239,14 +280,18 @@ class ImapSyncTest { private fun createBackendStorage(): InMemoryBackendStorage { return InMemoryBackendStorage().apply { - createFolderUpdater().use { updater -> - val folderInfo = FolderInfo( - serverId = FOLDER_SERVER_ID, - name = "irrelevant", - type = FolderType.REGULAR - ) - updater.createFolders(listOf(folderInfo)) - } + createBackendFolder(FOLDER_SERVER_ID) + } + } + + private fun InMemoryBackendStorage.createBackendFolder(serverId: String) { + createFolderUpdater().use { updater -> + val folderInfo = FolderInfo( + serverId = serverId, + name = "irrelevant", + type = FolderType.REGULAR + ) + updater.createFolders(listOf(folderInfo)) } } diff --git a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt index 8b97bfa61e..729a909f91 100644 --- a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt +++ b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapFolder.kt @@ -6,15 +6,16 @@ import com.fsck.k9.mail.Flag import com.fsck.k9.mail.Message import com.fsck.k9.mail.MessageRetrievalListener import com.fsck.k9.mail.Part +import com.fsck.k9.mail.store.imap.FetchListener import com.fsck.k9.mail.store.imap.ImapFolder import com.fsck.k9.mail.store.imap.ImapMessage import com.fsck.k9.mail.store.imap.OpenMode import com.fsck.k9.mail.store.imap.createImapMessage import java.util.Date -class TestImapFolder(override val serverId: String) : ImapFolder { +open class TestImapFolder(override val serverId: String) : ImapFolder { override var mode: OpenMode? = null - private set + protected set override var messageCount: Int = 0 @@ -92,7 +93,7 @@ class TestImapFolder(override val serverId: String) : ImapFolder { override fun fetch( messages: List, fetchProfile: FetchProfile, - listener: MessageRetrievalListener?, + listener: FetchListener?, maxDownloadSize: Int ) { if (messages.isEmpty()) return @@ -109,7 +110,7 @@ class TestImapFolder(override val serverId: String) : ImapFolder { } imapMessage.body = storedMessage.body - listener?.messageFinished(imapMessage) + listener?.onFetchResponse(imapMessage, isFirstResponse = true) } } diff --git a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapStore.kt b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapStore.kt index 0c7168779e..db698d52ef 100644 --- a/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapStore.kt +++ b/backend/imap/src/test/java/com/fsck/k9/backend/imap/TestImapStore.kt @@ -6,16 +6,23 @@ import com.fsck.k9.mail.store.imap.ImapFolder import com.fsck.k9.mail.store.imap.ImapStore class TestImapStore : ImapStore { - private val folders = mutableMapOf() + private val folders = mutableMapOf() - fun addFolder(name: String): TestImapFolder { - require(!folders.containsKey(name)) { "Folder '$name' already exists" } + fun addFolder(serverId: String): TestImapFolder { + require(!folders.containsKey(serverId)) { "Folder '$serverId' already exists" } - return TestImapFolder(name).also { folder -> - folders[name] = folder + return TestImapFolder(serverId).also { folder -> + folders[serverId] = folder } } + fun addFolder(folder: ImapFolder) { + val serverId = folder.serverId + require(!folders.containsKey(serverId)) { "Folder '$serverId' already exists" } + + folders[serverId] = folder + } + override fun getFolder(name: String): ImapFolder { return folders[name] ?: error("Folder '$name' not found") } diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt index 5589d6de70..821522bba7 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/ImapFolder.kt @@ -45,7 +45,7 @@ interface ImapFolder { fun fetch( messages: List, fetchProfile: FetchProfile, - listener: MessageRetrievalListener?, + listener: FetchListener?, maxDownloadSize: Int ) @@ -86,3 +86,7 @@ interface ImapFolder { @Throws(MessagingException::class) fun expungeUids(uids: List) } + +interface FetchListener { + fun onFetchResponse(message: ImapMessage, isFirstResponse: Boolean) +} diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt index 81d85f9bfe..f037aadd84 100644 --- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt +++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/RealImapFolder.kt @@ -515,7 +515,7 @@ internal class RealImapFolder( override fun fetch( messages: List, fetchProfile: FetchProfile, - listener: MessageRetrievalListener?, + listener: FetchListener?, maxDownloadSize: Int ) { if (messages.isEmpty()) { @@ -561,6 +561,7 @@ internal class RealImapFolder( val spaceSeparatedFetchFields = ImapUtility.join(" ", fetchFields) var windowStart = 0 + val processedUids = mutableSetOf() while (windowStart < messages.size) { val windowEnd = min(windowStart + FETCH_WINDOW_SIZE, messages.size) val uidWindow = uids.subList(windowStart, windowEnd) @@ -610,7 +611,10 @@ internal class RealImapFolder( } } - listener?.messageFinished(message) + val isFirstResponse = uid !in processedUids + processedUids.add(uid) + + listener?.onFetchResponse(message, isFirstResponse) } else { handleUntaggedResponse(response) } diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt index 67be422ed0..495280a786 100644 --- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt +++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/TestImapFolder.kt @@ -68,7 +68,7 @@ internal open class TestImapFolder( override fun fetch( messages: List, fetchProfile: FetchProfile, - listener: MessageRetrievalListener?, + listener: FetchListener?, maxDownloadSize: Int ) { throw UnsupportedOperationException("not implemented") -- GitLab From d784151ef08cf4cc44903e75c1ea6e90ea873468 Mon Sep 17 00:00:00 2001 From: cketti Date: Mon, 7 Mar 2022 19:03:36 +0100 Subject: [PATCH 41/50] Add support for updating existing notifications --- .../NewMailNotificationController.kt | 4 +- .../NewMailNotificationManager.kt | 4 +- .../k9/notification/NotificationDataStore.kt | 50 +++++++++++--- .../k9/notification/NotificationRepository.kt | 4 +- .../NewMailNotificationManagerTest.kt | 4 ++ .../notification/NotificationDataStoreTest.kt | 65 +++++++++++++------ 6 files changed, 95 insertions(+), 36 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationController.kt b/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationController.kt index 1901448621..c29c7791f5 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationController.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationController.kt @@ -27,7 +27,9 @@ internal class NewMailNotificationController( fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean) { val notificationData = newMailNotificationManager.addNewMailNotification(account, message, silent) - processNewMailNotificationData(notificationData) + if (notificationData != null) { + processNewMailNotificationData(notificationData) + } } fun removeNewMailNotifications( diff --git a/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationManager.kt b/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationManager.kt index 8e1a8c3fe2..3edfdb6abb 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationManager.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/NewMailNotificationManager.kt @@ -38,10 +38,10 @@ internal class NewMailNotificationManager( ) } - fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean): NewMailNotificationData { + fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean): NewMailNotificationData? { val content = contentCreator.createFromMessage(account, message) - val result = notificationRepository.addNotification(account, content, timestamp = now()) + val result = notificationRepository.addNotification(account, content, timestamp = now()) ?: return null val singleNotificationData = createSingleNotificationData( account = account, diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationDataStore.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationDataStore.kt index 50cd1be99e..db99f40340 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/NotificationDataStore.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationDataStore.kt @@ -2,7 +2,6 @@ package com.fsck.k9.notification import com.fsck.k9.Account import com.fsck.k9.controller.MessageReference -import com.fsck.k9.core.BuildConfig internal const val MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS = 8 @@ -30,15 +29,45 @@ internal class NotificationDataStore { } @Synchronized - fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult { + fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult? { val notificationData = getNotificationData(account) val messageReference = content.messageReference - if (BuildConfig.DEBUG && notificationData.contains(messageReference)) { - throw AssertionError("Notification for message $messageReference already exists") + val activeNotification = notificationData.activeNotifications.firstOrNull { notificationHolder -> + notificationHolder.content.messageReference == messageReference } + val inactiveNotification = notificationData.inactiveNotifications.firstOrNull { inactiveNotificationHolder -> + inactiveNotificationHolder.content.messageReference == messageReference + } + + return if (activeNotification != null) { + val newActiveNotification = activeNotification.copy(content = content) + val notificationHolder = activeNotification.copy( + content = content + ) + + val operations = emptyList() + + val newActiveNotifications = notificationData.activeNotifications + .replace(activeNotification, newActiveNotification) + val newNotificationData = notificationData.copy( + activeNotifications = newActiveNotifications + ) + notificationDataMap[account.uuid] = newNotificationData + + AddNotificationResult.newNotification(newNotificationData, operations, notificationHolder) + } else if (inactiveNotification != null) { + val newInactiveNotification = inactiveNotification.copy(content = content) + val newInactiveNotifications = notificationData.inactiveNotifications + .replace(inactiveNotification, newInactiveNotification) - return if (notificationData.isMaxNumberOfActiveNotificationsReached) { + val newNotificationData = notificationData.copy( + inactiveNotifications = newInactiveNotifications + ) + notificationDataMap[account.uuid] = newNotificationData + + null + } else if (notificationData.isMaxNumberOfActiveNotificationsReached) { val lastNotificationHolder = notificationData.activeNotifications.last() val inactiveNotificationHolder = lastNotificationHolder.toInactiveNotificationHolder() @@ -175,14 +204,15 @@ internal class NotificationDataStore { throw AssertionError("getNewNotificationId() called with no free notification ID") } - private fun NotificationData.contains(messageReference: MessageReference): Boolean { - return activeNotifications.any { it.content.messageReference == messageReference } || - inactiveNotifications.any { it.content.messageReference == messageReference } - } - private fun NotificationHolder.toInactiveNotificationHolder() = InactiveNotificationHolder(timestamp, content) private fun InactiveNotificationHolder.toNotificationHolder(notificationId: Int): NotificationHolder { return NotificationHolder(notificationId, timestamp, content) } + + private fun List.replace(old: T, new: T): List { + return map { element -> + if (element === old) new else element + } + } } diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationRepository.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationRepository.kt index f624810c99..ddad7bb965 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/NotificationRepository.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationRepository.kt @@ -37,8 +37,8 @@ internal class NotificationRepository( } @Synchronized - fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult { - return notificationDataStore.addNotification(account, content, timestamp).also { result -> + fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult? { + return notificationDataStore.addNotification(account, content, timestamp)?.also { result -> persistNotificationDataStoreChanges( account = account, operations = result.notificationStoreOperations, diff --git a/app/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt b/app/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt index 72e8a193d5..105f49649b 100644 --- a/app/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt +++ b/app/core/src/test/java/com/fsck/k9/notification/NewMailNotificationManagerTest.kt @@ -49,6 +49,7 @@ class NewMailNotificationManagerTest { val result = manager.addNewMailNotification(account, message, silent = false) + assertNotNull(result) assertThat(result.singleNotificationData.first().content).isEqualTo( NotificationContent( messageReference = createMessageReference("msg-1"), @@ -85,6 +86,7 @@ class NewMailNotificationManagerTest { val result = manager.addNewMailNotification(account, messageTwo, silent = false) + assertNotNull(result) assertThat(result.singleNotificationData.first().content).isEqualTo( NotificationContent( messageReference = createMessageReference("msg-2"), @@ -121,6 +123,7 @@ class NewMailNotificationManagerTest { val result = manager.addNewMailNotification(account, message, silent = false) + assertNotNull(result) val notificationId = NotificationIds.getSingleMessageNotificationId(account, index = 0) assertThat(result.cancelNotificationIds).isEqualTo(listOf(notificationId)) assertThat(result.singleNotificationData.first().notificationId).isEqualTo(notificationId) @@ -196,6 +199,7 @@ class NewMailNotificationManagerTest { messageUid = "msg-2" ) val dataTwo = manager.addNewMailNotification(account, messageTwo, silent = true) + assertNotNull(dataTwo) val notificationIdTwo = dataTwo.singleNotificationData.first().notificationId val messageThree = addMessageToNotificationContentCreator( sender = "Alice", diff --git a/app/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt b/app/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt index a074639096..57ffc8aba4 100644 --- a/app/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt +++ b/app/core/src/test/java/com/fsck/k9/notification/NotificationDataStoreTest.kt @@ -3,12 +3,8 @@ package com.fsck.k9.notification import com.fsck.k9.Account import com.fsck.k9.RobolectricTest import com.fsck.k9.controller.MessageReference -import com.fsck.k9.core.BuildConfig import com.google.common.truth.Truth.assertThat import kotlin.test.assertNotNull -import org.junit.Assert.fail -import org.junit.Assume.assumeFalse -import org.junit.Assume.assumeTrue import org.junit.Test private const val ACCOUNT_UUID = "1-2-3" @@ -26,6 +22,7 @@ class NotificationDataStoreTest : RobolectricTest() { val result = notificationDataStore.addNotification(account, content, TIMESTAMP) + assertNotNull(result) assertThat(result.shouldCancelNotification).isFalse() val holder = result.notificationHolder @@ -48,6 +45,7 @@ class NotificationDataStoreTest : RobolectricTest() { val result = notificationDataStore.addNotification(account, createNotificationContent("9"), TIMESTAMP) + assertNotNull(result) assertThat(result.shouldCancelNotification).isTrue() assertThat(result.cancelNotificationId).isEqualTo(NotificationIds.getSingleMessageNotificationId(account, 0)) } @@ -107,19 +105,23 @@ class NotificationDataStoreTest : RobolectricTest() { fun testNewMessagesCount() { val contentOne = createNotificationContent("1") val resultOne = notificationDataStore.addNotification(account, contentOne, TIMESTAMP) + assertNotNull(resultOne) assertThat(resultOne.notificationData.newMessagesCount).isEqualTo(1) val contentTwo = createNotificationContent("2") val resultTwo = notificationDataStore.addNotification(account, contentTwo, TIMESTAMP) + assertNotNull(resultTwo) assertThat(resultTwo.notificationData.newMessagesCount).isEqualTo(2) } @Test fun testIsSingleMessageNotification() { val resultOne = notificationDataStore.addNotification(account, createNotificationContent("1"), TIMESTAMP) + assertNotNull(resultOne) assertThat(resultOne.notificationData.isSingleMessageNotification).isTrue() val resultTwo = notificationDataStore.addNotification(account, createNotificationContent("2"), TIMESTAMP) + assertNotNull(resultTwo) assertThat(resultTwo.notificationData.isSingleMessageNotification).isFalse() } @@ -128,37 +130,58 @@ class NotificationDataStoreTest : RobolectricTest() { val content = createNotificationContent("1") val addResult = notificationDataStore.addNotification(account, content, TIMESTAMP) + assertNotNull(addResult) assertThat(addResult.notificationData.activeNotifications.first()).isEqualTo(addResult.notificationHolder) } @Test - fun `adding notification for same message twice should throw in debug build`() { - assumeTrue(BuildConfig.DEBUG) - + fun `adding notification for message with active notification should update notification`() { val content1 = createNotificationContent("1") val content2 = createNotificationContent("1") - notificationDataStore.addNotification(account, content1, TIMESTAMP) - try { - notificationDataStore.addNotification(account, content2, TIMESTAMP) - fail("Exception expected") - } catch (e: AssertionError) { - assertThat(e).hasMessageThat().matches("Notification for message .+ already exists") + val resultOne = notificationDataStore.addNotification(account, content1, TIMESTAMP) + val resultTwo = notificationDataStore.addNotification(account, content2, TIMESTAMP) + + assertNotNull(resultOne) + assertNotNull(resultTwo) + assertThat(resultTwo.notificationData.activeNotifications).hasSize(1) + assertThat(resultTwo.notificationData.activeNotifications.first().content).isSameInstanceAs(content2) + assertThat(resultTwo.notificationStoreOperations).isEmpty() + with(resultTwo.notificationHolder) { + assertThat(notificationId).isEqualTo(resultOne.notificationHolder.notificationId) + assertThat(timestamp).isEqualTo(resultOne.notificationHolder.timestamp) + assertThat(content).isSameInstanceAs(content2) } + assertThat(resultTwo.shouldCancelNotification).isFalse() } @Test - fun `adding notification for same message twice should add another notification in release build`() { - assumeFalse(BuildConfig.DEBUG) + fun `adding notification for message with inactive notification should update notificationData`() { + notificationDataStore.addNotification(account, createNotificationContent("1"), TIMESTAMP) + notificationDataStore.addNotification(account, createNotificationContent("2"), TIMESTAMP) + notificationDataStore.addNotification(account, createNotificationContent("3"), TIMESTAMP) + notificationDataStore.addNotification(account, createNotificationContent("4"), TIMESTAMP) + notificationDataStore.addNotification(account, createNotificationContent("5"), TIMESTAMP) + notificationDataStore.addNotification(account, createNotificationContent("6"), TIMESTAMP) + notificationDataStore.addNotification(account, createNotificationContent("7"), TIMESTAMP) + notificationDataStore.addNotification(account, createNotificationContent("8"), TIMESTAMP) + val latestNotificationContent = createNotificationContent("9") + notificationDataStore.addNotification(account, latestNotificationContent, TIMESTAMP) + val content = createNotificationContent("1") - val content1 = createNotificationContent("1") - val content2 = createNotificationContent("1") + val resultOne = notificationDataStore.addNotification(account, content, TIMESTAMP) - val addResult1 = notificationDataStore.addNotification(account, content1, TIMESTAMP) - val addResult2 = notificationDataStore.addNotification(account, content2, TIMESTAMP) + assertThat(resultOne).isNull() - assertThat(addResult1.notificationHolder.notificationId) - .isNotEqualTo(addResult2.notificationHolder.notificationId) + val resultTwo = notificationDataStore.removeNotifications(account) { + listOf(latestNotificationContent.messageReference) + } + + assertNotNull(resultTwo) + val notificationHolder = resultTwo.notificationData.activeNotifications.first { notificationHolder -> + notificationHolder.content.messageReference == content.messageReference + } + assertThat(notificationHolder.content).isSameInstanceAs(content) } private fun createAccount(): Account { -- GitLab From aa90f4369d0ccd6883b46b9a36c81fb53416ce3a Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 8 Mar 2022 22:53:22 +0100 Subject: [PATCH 42/50] Avoid race condition when recreating the messages NotificationChannel --- app/core/src/main/java/com/fsck/k9/Account.kt | 5 ----- .../notification/NotificationChannelManager.kt | 18 ++++++++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/Account.kt b/app/core/src/main/java/com/fsck/k9/Account.kt index b32e628e70..c1e66a2819 100644 --- a/app/core/src/main/java/com/fsck/k9/Account.kt +++ b/app/core/src/main/java/com/fsck/k9/Account.kt @@ -490,11 +490,6 @@ class Account(override val uuid: String) : BaseAccount { (oldSyncMode != FolderMode.NONE && syncMode == FolderMode.NONE) } - @Synchronized - fun incrementMessagesNotificationChannelVersion() { - messagesNotificationChannelVersion++ - } - @Synchronized fun isSortAscending(sortType: SortType): Boolean { return sortAscending.getOrPut(sortType) { sortType.isDefaultAscending } 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 e1da468325..4970c4a41d 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 @@ -144,12 +144,16 @@ class NotificationChannelManager( fun getChannelIdFor(account: Account, channelType: ChannelType): String { return if (channelType == ChannelType.MESSAGES) { - "messages_channel_${account.uuid}${account.messagesNotificationChannelSuffix}" + getMessagesChannelId(account, account.messagesNotificationChannelSuffix) } else { "miscellaneous_channel_${account.uuid}" } } + private fun getMessagesChannelId(account: Account, suffix: String): String { + return "messages_channel_${account.uuid}$suffix" + } + @RequiresApi(Build.VERSION_CODES.O) fun getNotificationConfiguration(account: Account): NotificationConfiguration { val channelId = getChannelIdFor(account, ChannelType.MESSAGES) @@ -175,11 +179,8 @@ class NotificationChannelManager( return } - notificationManager.deleteNotificationChannel(oldChannelId) - - account.incrementMessagesNotificationChannelVersion() - - val newChannelId = getChannelIdFor(account, ChannelType.MESSAGES) + val newChannelVersion = account.messagesNotificationChannelVersion + 1 + val newChannelId = getMessagesChannelId(account, "_$newChannelVersion") val channelName = resourceProvider.messagesChannelName val importance = oldNotificationChannel.importance @@ -195,6 +196,11 @@ class NotificationChannelManager( Timber.v("Old NotificationChannel: %s", oldNotificationChannel) Timber.v("New NotificationChannel: %s", newNotificationChannel) notificationManager.createNotificationChannel(newNotificationChannel) + + // To avoid a race condition we first create the new NotificationChannel, point the Account to it, + // then delete the old one. + account.messagesNotificationChannelVersion = newChannelVersion + notificationManager.deleteNotificationChannel(oldChannelId) } @RequiresApi(Build.VERSION_CODES.O) -- GitLab From f3241622d54a853f6e451badb62d1ca169c29036 Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 8 Mar 2022 23:51:45 +0100 Subject: [PATCH 43/50] Avoid race condition when selecting a notification sound --- .../k9/ui/settings/account/AccountSettingsFragment.kt | 11 ++++++++++- .../settings/account/NotificationSoundPreference.kt | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsFragment.kt index 8aceb476e1..fc57d2873f 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsFragment.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsFragment.kt @@ -101,7 +101,16 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr val account = getAccount() initializeCryptoSettings(account) - maybeUpdateNotificationPreferences(account) + // Don't update the notification preferences when resuming after the user has selected a new notification sound + // via NotificationSoundPreference. Otherwise we race the background thread and might read data from the old + // NotificationChannel, overwriting the notification sound with the previous value. + notificationSoundPreference?.let { notificationSoundPreference -> + if (notificationSoundPreference.receivedActivityResultJustNow) { + notificationSoundPreference.receivedActivityResultJustNow = false + } else { + maybeUpdateNotificationPreferences(account) + } + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/NotificationSoundPreference.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/NotificationSoundPreference.kt index 7546f862af..44050ee669 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/NotificationSoundPreference.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/NotificationSoundPreference.kt @@ -27,6 +27,7 @@ constructor( ), defStyleRes: Int = 0 ) : Preference(context, attrs, defStyleAttr, defStyleRes), PreferenceActivityResultListener { + var receivedActivityResultJustNow = false fun setNotificationSound(sound: Uri?) { persistRingtone(sound) @@ -42,6 +43,7 @@ constructor( val uri = data?.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI) if (callChangeListener(uri?.toString().orEmpty())) { + receivedActivityResultJustNow = true persistRingtone(uri) } } -- GitLab From 25c0e4af431d7b6170858db4442cee8d10b324c3 Mon Sep 17 00:00:00 2001 From: cketti Date: Wed, 9 Mar 2022 21:54:30 +0100 Subject: [PATCH 44/50] Extract vibration settings to separate class `NotificationVibration` --- .../fsck/k9/AccountPreferenceSerializer.kt | 18 +++--- .../java/com/fsck/k9/NotificationSettings.kt | 59 +----------------- .../java/com/fsck/k9/NotificationVibration.kt | 62 +++++++++++++++++++ .../BaseNotificationDataCreator.kt | 2 +- .../NotificationChannelManager.kt | 8 +-- .../BaseNotificationDataCreatorTest.kt | 12 +++- .../account/AccountSettingsDataStore.kt | 21 ++++--- .../account/VibrationDialogFragment.kt | 4 +- .../settings/account/VibrationPreference.kt | 4 +- 9 files changed, 104 insertions(+), 86 deletions(-) create mode 100644 app/core/src/main/java/com/fsck/k9/NotificationVibration.kt 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 12f725b17d..c881cb312d 100644 --- a/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt +++ b/app/core/src/main/java/com/fsck/k9/AccountPreferenceSerializer.kt @@ -142,9 +142,11 @@ class AccountPreferenceSerializer( isRingEnabled = storage.getBoolean("$accountUuid.ring", true), ringtone = storage.getString("$accountUuid.ringtone", DEFAULT_RINGTONE_URI), light = getEnumStringPref(storage, "$accountUuid.notificationLight", NotificationLight.Disabled), - isVibrateEnabled = storage.getBoolean("$accountUuid.vibrate", false), - vibratePattern = VibratePattern.deserialize(storage.getInt("$accountUuid.vibratePattern", 0)), - vibrateTimes = storage.getInt("$accountUuid.vibrateTimes", 5) + vibration = NotificationVibration( + isEnabled = storage.getBoolean("$accountUuid.vibrate", false), + pattern = VibratePattern.deserialize(storage.getInt("$accountUuid.vibratePattern", 0)), + repeatCount = storage.getInt("$accountUuid.vibrateTimes", 5) + ) ) } @@ -322,9 +324,9 @@ class AccountPreferenceSerializer( editor.putBoolean("$accountUuid.markMessageAsReadOnDelete", isMarkMessageAsReadOnDelete) editor.putBoolean("$accountUuid.alwaysShowCcBcc", isAlwaysShowCcBcc) - editor.putBoolean("$accountUuid.vibrate", notificationSettings.isVibrateEnabled) - editor.putInt("$accountUuid.vibratePattern", notificationSettings.vibratePattern.serialize()) - editor.putInt("$accountUuid.vibrateTimes", notificationSettings.vibrateTimes) + editor.putBoolean("$accountUuid.vibrate", notificationSettings.vibration.isEnabled) + editor.putInt("$accountUuid.vibratePattern", notificationSettings.vibration.pattern.serialize()) + editor.putInt("$accountUuid.vibrateTimes", notificationSettings.vibration.repeatCount) editor.putBoolean("$accountUuid.ring", notificationSettings.isRingEnabled) editor.putString("$accountUuid.ringtone", notificationSettings.ringtone) editor.putString("$accountUuid.notificationLight", notificationSettings.light.name) @@ -607,9 +609,7 @@ class AccountPreferenceSerializer( isRingEnabled = true, ringtone = DEFAULT_RINGTONE_URI, light = NotificationLight.Disabled, - isVibrateEnabled = false, - vibratePattern = VibratePattern.Default, - vibrateTimes = 5 + vibration = NotificationVibration.DEFAULT ) } diff --git a/app/core/src/main/java/com/fsck/k9/NotificationSettings.kt b/app/core/src/main/java/com/fsck/k9/NotificationSettings.kt index b94eec51b8..5852e4b14d 100644 --- a/app/core/src/main/java/com/fsck/k9/NotificationSettings.kt +++ b/app/core/src/main/java/com/fsck/k9/NotificationSettings.kt @@ -7,60 +7,5 @@ data class NotificationSettings( val isRingEnabled: Boolean = false, val ringtone: String? = null, val light: NotificationLight = NotificationLight.Disabled, - 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") - } - } -} + val vibration: NotificationVibration = NotificationVibration.DEFAULT +) diff --git a/app/core/src/main/java/com/fsck/k9/NotificationVibration.kt b/app/core/src/main/java/com/fsck/k9/NotificationVibration.kt new file mode 100644 index 0000000000..0986baeff3 --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/NotificationVibration.kt @@ -0,0 +1,62 @@ +package com.fsck.k9 + +data class NotificationVibration( + val isEnabled: Boolean, + val pattern: VibratePattern, + val repeatCount: Int +) { + val systemPattern: LongArray + get() = getSystemPattern(pattern, repeatCount) + + companion object { + val DEFAULT = NotificationVibration(isEnabled = false, pattern = VibratePattern.Default, repeatCount = 5) + + fun getSystemPattern(vibratePattern: VibratePattern, repeatCount: Int): LongArray { + val selectedPattern = vibratePattern.vibrationPattern + val repeatedPattern = LongArray(selectedPattern.size * repeatCount) + for (n in 0 until repeatCount) { + 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 b07b78276b..9fe65d367c 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 @@ -41,7 +41,7 @@ internal class BaseNotificationDataCreator { private fun createNotificationAppearance(account: Account): NotificationAppearance { return with(account.notificationSettings) { - val vibrationPattern = if (isVibrateEnabled) vibrationPattern else null + val vibrationPattern = vibration.systemPattern.takeIf { vibration.isEnabled } NotificationAppearance(ringtone, vibrationPattern, account.notificationSettings.light.toColor(account)) } } 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 4970c4a41d..a2349ed74b 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 @@ -213,8 +213,8 @@ class NotificationChannelManager( val notificationSettings = account.notificationSettings return sound == notificationSettings.ringtoneUri && systemLight == notificationSettings.light && - shouldVibrate() == notificationSettings.isVibrateEnabled && - vibrationPattern.contentEquals(notificationSettings.vibrationPattern) + shouldVibrate() == notificationSettings.vibration.isEnabled && + vibrationPattern.contentEquals(notificationSettings.vibration.systemPattern) } @RequiresApi(Build.VERSION_CODES.O) @@ -244,8 +244,8 @@ class NotificationChannelManager( val isLightEnabled = notificationSettings.light != NotificationLight.Disabled enableLights(isLightEnabled) - vibrationPattern = notificationSettings.vibrationPattern - enableVibration(notificationSettings.isVibrateEnabled) + vibrationPattern = notificationSettings.vibration.systemPattern + enableVibration(notificationSettings.vibration.isEnabled) } private val Account.messagesNotificationChannelSuffix: String 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 f1520fc497..65dea9c1e2 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 @@ -5,7 +5,7 @@ import com.fsck.k9.Identity import com.fsck.k9.K9 import com.fsck.k9.K9.LockScreenNotificationVisibility import com.fsck.k9.NotificationLight -import com.fsck.k9.NotificationSettings +import com.fsck.k9.NotificationVibration import com.fsck.k9.VibratePattern import com.google.common.truth.Truth.assertThat import org.junit.Test @@ -151,14 +151,20 @@ class BaseNotificationDataCreatorTest { @Test fun `vibration pattern`() { account.updateNotificationSettings { - it.copy(isVibrateEnabled = true, vibratePattern = VibratePattern.Pattern3, vibrateTimes = 2) + it.copy( + vibration = NotificationVibration( + isEnabled = true, + pattern = VibratePattern.Pattern3, + repeatCount = 2 + ) + ) } val notificationData = createNotificationData() val result = notificationDataCreator.createBaseNotificationData(notificationData) assertThat(result.appearance.vibrationPattern).isEqualTo( - NotificationSettings.getVibrationPattern(VibratePattern.Pattern3, 2) + NotificationVibration.getSystemPattern(VibratePattern.Pattern3, 2) ) } 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 250964d0a8..9672c89366 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 @@ -4,6 +4,7 @@ import androidx.preference.PreferenceDataStore import com.fsck.k9.Account import com.fsck.k9.Account.SpecialFolderSelection import com.fsck.k9.NotificationLight +import com.fsck.k9.NotificationVibration import com.fsck.k9.Preferences import com.fsck.k9.job.K9JobManager import com.fsck.k9.notification.NotificationChannelManager @@ -263,20 +264,24 @@ class AccountSettingsDataStore( } private fun getCombinedVibrationValue(): String { - return VibrationPreference.encode( - isVibrationEnabled = account.notificationSettings.isVibrateEnabled, - vibratePattern = account.notificationSettings.vibratePattern, - vibrationTimes = account.notificationSettings.vibrateTimes - ) + return with(account.notificationSettings.vibration) { + VibrationPreference.encode( + isVibrationEnabled = isEnabled, + vibratePattern = pattern, + vibrationTimes = repeatCount + ) + } } private fun setCombinedVibrationValue(value: String) { val (isVibrationEnabled, vibrationPattern, vibrationTimes) = VibrationPreference.decode(value) account.updateNotificationSettings { notificationSettings -> notificationSettings.copy( - isVibrateEnabled = isVibrationEnabled, - vibratePattern = vibrationPattern, - vibrateTimes = vibrationTimes, + vibration = NotificationVibration( + isEnabled = isVibrationEnabled, + pattern = vibrationPattern, + repeatCount = 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 d978d2c3b0..4ecedb8baa 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,7 +16,7 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SwitchCompat import androidx.core.content.getSystemService import androidx.preference.PreferenceDialogFragmentCompat -import com.fsck.k9.NotificationSettings +import com.fsck.k9.NotificationVibration import com.fsck.k9.VibratePattern import com.fsck.k9.ui.R import com.fsck.k9.ui.getEnum @@ -81,7 +81,7 @@ class VibrationDialogFragment : PreferenceDialogFragmentCompat() { private fun playVibration() { val vibratePattern = adapter.vibratePattern val vibrationTimes = adapter.vibrationTimes - val vibrationPattern = NotificationSettings.getVibrationPattern(vibratePattern, vibrationTimes) + val vibrationPattern = NotificationVibration.getSystemPattern(vibratePattern, vibrationTimes) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val vibrationEffect = VibrationEffect.createWaveform(vibrationPattern, -1) 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 825f75a853..cc656a0101 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,7 @@ import android.content.Context import android.util.AttributeSet import androidx.core.content.res.TypedArrayUtils import androidx.preference.ListPreference -import com.fsck.k9.NotificationSettings +import com.fsck.k9.NotificationVibration import com.fsck.k9.VibratePattern import com.fsck.k9.ui.R import com.takisoft.preferencex.PreferenceFragmentCompat @@ -75,7 +75,7 @@ constructor( VibratePattern.deserialize(serializedVibratePattern) } .firstOrNull { vibratePattern -> - val testPattern = NotificationSettings.getVibrationPattern(vibratePattern, vibrationTimes) + val testPattern = NotificationVibration.getSystemPattern(vibratePattern, vibrationTimes) testPattern.contentEquals(combinedPatternArray) } ?: DEFAULT_VIBRATE_PATTERN -- GitLab From 803e388b906aa5869b4ffe9aa8166edd94801e6a Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 10 Mar 2022 04:11:03 +0100 Subject: [PATCH 45/50] Extract logic to convert system vibration pattern into `NotificationVibrationDecoder` --- .../fsck/k9/notification/CoreKoinModule.kt | 1 + .../NotificationVibrationDecoder.kt | 26 +++++++++++++++++++ .../account/AccountSettingsFragment.kt | 11 ++++++-- .../settings/account/VibrationPreference.kt | 23 ---------------- 4 files changed, 36 insertions(+), 25 deletions(-) create mode 100644 app/core/src/main/java/com/fsck/k9/notification/NotificationVibrationDecoder.kt diff --git a/app/core/src/main/java/com/fsck/k9/notification/CoreKoinModule.kt b/app/core/src/main/java/com/fsck/k9/notification/CoreKoinModule.kt index 970d3515b3..dc7e0677a3 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/CoreKoinModule.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/CoreKoinModule.kt @@ -114,4 +114,5 @@ val coreNotificationModule = module { ) } factory { NotificationLightDecoder() } + factory { NotificationVibrationDecoder() } } diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationVibrationDecoder.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationVibrationDecoder.kt new file mode 100644 index 0000000000..554479b1f6 --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationVibrationDecoder.kt @@ -0,0 +1,26 @@ +package com.fsck.k9.notification + +import com.fsck.k9.NotificationVibration +import com.fsck.k9.VibratePattern + +/** + * Converts the vibration values read from a `NotificationChannel` into [NotificationVibration]. + */ +class NotificationVibrationDecoder { + fun decode(isVibrationEnabled: Boolean, systemPattern: List?): NotificationVibration { + if (systemPattern == null || systemPattern.size < 2 || systemPattern.size % 2 != 0) { + return NotificationVibration.DEFAULT + } + + val systemPatternArray = systemPattern.toLongArray() + val repeatCount = systemPattern.size / 2 + val pattern = VibratePattern.values() + .firstOrNull { vibratePattern -> + val testPattern = NotificationVibration.getSystemPattern(vibratePattern, repeatCount) + + testPattern.contentEquals(systemPatternArray) + } ?: VibratePattern.Default + + return NotificationVibration(isVibrationEnabled, pattern, repeatCount) + } +} diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsFragment.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsFragment.kt index fc57d2873f..5c9ad9f150 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsFragment.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/settings/account/AccountSettingsFragment.kt @@ -29,6 +29,7 @@ import com.fsck.k9.mailstore.RemoteFolder import com.fsck.k9.notification.NotificationChannelManager import com.fsck.k9.notification.NotificationChannelManager.ChannelType import com.fsck.k9.notification.NotificationLightDecoder +import com.fsck.k9.notification.NotificationVibrationDecoder import com.fsck.k9.ui.R import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity import com.fsck.k9.ui.settings.onClick @@ -52,6 +53,7 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr private val accountRemover: BackgroundAccountRemover by inject() private val notificationChannelManager: NotificationChannelManager by inject() private val notificationLightDecoder: NotificationLightDecoder by inject() + private val notificationVibrationDecoder: NotificationVibrationDecoder by inject() private val vibrator by lazy { requireContext().getSystemService() } private lateinit var dataStore: AccountSettingsDataStore @@ -267,9 +269,14 @@ class AccountSettingsFragment : PreferenceFragmentCompat(), ConfirmationDialogFr } notificationVibrationPreference?.let { preference -> - preference.setVibrationFromSystem( + val notificationVibration = notificationVibrationDecoder.decode( isVibrationEnabled = notificationConfiguration.isVibrationEnabled, - combinedPattern = notificationConfiguration.vibrationPattern + systemPattern = notificationConfiguration.vibrationPattern + ) + preference.setVibration( + isVibrationEnabled = notificationVibration.isEnabled, + vibratePattern = notificationVibration.pattern, + vibrationTimes = notificationVibration.repeatCount ) preference.isEnabled = true } 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 cc656a0101..d8ea240f18 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,6 @@ import android.content.Context import android.util.AttributeSet import androidx.core.content.res.TypedArrayUtils import androidx.preference.ListPreference -import com.fsck.k9.NotificationVibration import com.fsck.k9.VibratePattern import com.fsck.k9.ui.R import com.takisoft.preferencex.PreferenceFragmentCompat @@ -61,28 +60,6 @@ constructor( updateSummary() } - fun setVibrationFromSystem(isVibrationEnabled: Boolean, combinedPattern: List?) { - if (combinedPattern == null || combinedPattern.size < 2 || combinedPattern.size % 2 != 0) { - setVibration(isVibrationEnabled, DEFAULT_VIBRATE_PATTERN, DEFAULT_VIBRATION_TIMES) - return - } - - val combinedPatternArray = combinedPattern.toLongArray() - val vibrationTimes = combinedPattern.size / 2 - val vibrationPattern = entryValues.asSequence() - .map { entryValue -> - val serializedVibratePattern = entryValue.toString().toInt() - VibratePattern.deserialize(serializedVibratePattern) - } - .firstOrNull { vibratePattern -> - val testPattern = NotificationVibration.getSystemPattern(vibratePattern, vibrationTimes) - - testPattern.contentEquals(combinedPatternArray) - } ?: DEFAULT_VIBRATE_PATTERN - - setVibration(isVibrationEnabled, vibrationPattern, vibrationTimes) - } - private fun updateSummary() { summary = if (isVibrationEnabled) { val index = entryValues.indexOf(vibratePattern.serialize().toString()) -- GitLab From 8206cbce4c1f87997b200fca230a81b281d3e523 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 10 Mar 2022 05:08:58 +0100 Subject: [PATCH 46/50] Update accounts with `NotificationChannel` configurations before exporting settings --- .../fsck/k9/notification/CoreKoinModule.kt | 8 +++++ .../NotificationConfigurationConverter.kt | 32 +++++++++++++++++ .../NotificationSettingsUpdater.kt | 34 +++++++++++++++++++ .../com/fsck/k9/preferences/KoinModule.kt | 3 +- .../fsck/k9/preferences/SettingsExporter.kt | 16 ++++++++- .../k9/preferences/SettingsExporterTest.kt | 4 ++- 6 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 app/core/src/main/java/com/fsck/k9/notification/NotificationConfigurationConverter.kt create mode 100644 app/core/src/main/java/com/fsck/k9/notification/NotificationSettingsUpdater.kt diff --git a/app/core/src/main/java/com/fsck/k9/notification/CoreKoinModule.kt b/app/core/src/main/java/com/fsck/k9/notification/CoreKoinModule.kt index dc7e0677a3..de6797b498 100644 --- a/app/core/src/main/java/com/fsck/k9/notification/CoreKoinModule.kt +++ b/app/core/src/main/java/com/fsck/k9/notification/CoreKoinModule.kt @@ -115,4 +115,12 @@ val coreNotificationModule = module { } factory { NotificationLightDecoder() } factory { NotificationVibrationDecoder() } + factory { + NotificationConfigurationConverter(notificationLightDecoder = get(), notificationVibrationDecoder = get()) + } + factory { + NotificationSettingsUpdater( + preferences = get(), notificationChannelManager = get(), notificationConfigurationConverter = get() + ) + } } diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationConfigurationConverter.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationConfigurationConverter.kt new file mode 100644 index 0000000000..3f40015f9d --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationConfigurationConverter.kt @@ -0,0 +1,32 @@ +package com.fsck.k9.notification + +import com.fsck.k9.Account +import com.fsck.k9.NotificationSettings + +/** + * Converts the [NotificationConfiguration] read from a `NotificationChannel` into a [NotificationSettings] instance. + */ +class NotificationConfigurationConverter( + private val notificationLightDecoder: NotificationLightDecoder, + private val notificationVibrationDecoder: NotificationVibrationDecoder +) { + fun convert(account: Account, notificationConfiguration: NotificationConfiguration): NotificationSettings { + val light = notificationLightDecoder.decode( + isBlinkLightsEnabled = notificationConfiguration.isBlinkLightsEnabled, + lightColor = notificationConfiguration.lightColor, + accountColor = account.chipColor + ) + + val vibration = notificationVibrationDecoder.decode( + isVibrationEnabled = notificationConfiguration.isVibrationEnabled, + systemPattern = notificationConfiguration.vibrationPattern + ) + + return NotificationSettings( + isRingEnabled = notificationConfiguration.sound != null, + ringtone = notificationConfiguration.sound?.toString(), + light = light, + vibration = vibration + ) + } +} diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationSettingsUpdater.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationSettingsUpdater.kt new file mode 100644 index 0000000000..193e99c872 --- /dev/null +++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationSettingsUpdater.kt @@ -0,0 +1,34 @@ +package com.fsck.k9.notification + +import android.os.Build +import androidx.annotation.RequiresApi +import com.fsck.k9.Account +import com.fsck.k9.Preferences + +/** + * Update accounts with notification settings read from their "Messages" `NotificationChannel`. + */ +class NotificationSettingsUpdater( + private val preferences: Preferences, + private val notificationChannelManager: NotificationChannelManager, + private val notificationConfigurationConverter: NotificationConfigurationConverter +) { + fun updateNotificationSettings(accountUuids: Collection) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return + + accountUuids + .mapNotNull { accountUuid -> preferences.getAccount(accountUuid) } + .forEach { account -> updateNotificationSettings(account) } + } + + @RequiresApi(Build.VERSION_CODES.O) + private fun updateNotificationSettings(account: Account) { + val notificationConfiguration = notificationChannelManager.getNotificationConfiguration(account) + val notificationSettings = notificationConfigurationConverter.convert(account, notificationConfiguration) + + if (notificationSettings != account.notificationSettings) { + account.updateNotificationSettings { notificationSettings } + preferences.saveAccount(account) + } + } +} diff --git a/app/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt b/app/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt index 8d1ca4c88a..0d92a2ad2c 100644 --- a/app/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt +++ b/app/core/src/main/java/com/fsck/k9/preferences/KoinModule.kt @@ -11,7 +11,8 @@ val preferencesModule = module { contentResolver = get(), preferences = get(), folderSettingsProvider = get(), - folderRepository = get() + folderRepository = get(), + notificationSettingsUpdater = get() ) } factory { FolderSettingsProvider(folderRepository = get()) } diff --git a/app/core/src/main/java/com/fsck/k9/preferences/SettingsExporter.kt b/app/core/src/main/java/com/fsck/k9/preferences/SettingsExporter.kt index 7362d9d731..39983b1391 100644 --- a/app/core/src/main/java/com/fsck/k9/preferences/SettingsExporter.kt +++ b/app/core/src/main/java/com/fsck/k9/preferences/SettingsExporter.kt @@ -10,6 +10,7 @@ import com.fsck.k9.AccountPreferenceSerializer.Companion.IDENTITY_EMAIL_KEY import com.fsck.k9.AccountPreferenceSerializer.Companion.IDENTITY_NAME_KEY import com.fsck.k9.Preferences import com.fsck.k9.mailstore.FolderRepository +import com.fsck.k9.notification.NotificationSettingsUpdater import com.fsck.k9.preferences.ServerTypeConverter.fromServerSettingsType import com.fsck.k9.preferences.Settings.InvalidSettingValueException import com.fsck.k9.preferences.Settings.SettingsDescription @@ -24,10 +25,13 @@ class SettingsExporter( private val contentResolver: ContentResolver, private val preferences: Preferences, private val folderSettingsProvider: FolderSettingsProvider, - private val folderRepository: FolderRepository + private val folderRepository: FolderRepository, + private val notificationSettingsUpdater: NotificationSettingsUpdater ) { @Throws(SettingsImportExportException::class) fun exportToUri(includeGlobals: Boolean, accountUuids: Set, uri: Uri) { + updateNotificationSettings(accountUuids) + try { contentResolver.openOutputStream(uri)!!.use { outputStream -> exportPreferences(outputStream, includeGlobals, accountUuids) @@ -37,6 +41,16 @@ class SettingsExporter( } } + private fun updateNotificationSettings(accountUuids: Set) { + try { + notificationSettingsUpdater.updateNotificationSettings(accountUuids) + } catch (e: Exception) { + // An error here could mean we export notification settings that don't reflect the current configuration + // of the notification channels. But we prefer stale data over failing the export. + Timber.w(e, "Error while updating accounts with notification configuration from system") + } + } + @Throws(SettingsImportExportException::class) fun exportPreferences(outputStream: OutputStream, includeGlobals: Boolean, accountUuids: Set) { try { diff --git a/app/core/src/test/java/com/fsck/k9/preferences/SettingsExporterTest.kt b/app/core/src/test/java/com/fsck/k9/preferences/SettingsExporterTest.kt index b92e017ba3..e3177f8256 100644 --- a/app/core/src/test/java/com/fsck/k9/preferences/SettingsExporterTest.kt +++ b/app/core/src/test/java/com/fsck/k9/preferences/SettingsExporterTest.kt @@ -11,6 +11,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Test import org.koin.core.component.inject +import org.mockito.kotlin.mock import org.robolectric.RuntimeEnvironment class SettingsExporterTest : K9RobolectricTest() { @@ -22,7 +23,8 @@ class SettingsExporterTest : K9RobolectricTest() { contentResolver, preferences, folderSettingsProvider, - folderRepository + folderRepository, + notificationSettingsUpdater = mock() ) @Test -- GitLab From a11c993491ddcd34c7347321603584b540218970 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 10 Mar 2022 18:16:44 +0100 Subject: [PATCH 47/50] Add support for duplicate "charset" parameters with matching values --- .../k9/mail/internet/MessageExtractor.java | 3 +- .../k9/mail/internet/MimeParameterDecoder.kt | 20 +++++++ .../fsck/k9/mail/internet/PartExtensions.kt | 24 +++++++++ .../k9/mail/internet/PartExtensionsTest.kt | 54 +++++++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 mail/common/src/main/java/com/fsck/k9/mail/internet/PartExtensions.kt create mode 100644 mail/common/src/test/java/com/fsck/k9/mail/internet/PartExtensionsTest.kt diff --git a/mail/common/src/main/java/com/fsck/k9/mail/internet/MessageExtractor.java b/mail/common/src/main/java/com/fsck/k9/mail/internet/MessageExtractor.java index e45aa18e34..c54f469be8 100644 --- a/mail/common/src/main/java/com/fsck/k9/mail/internet/MessageExtractor.java +++ b/mail/common/src/main/java/com/fsck/k9/mail/internet/MessageExtractor.java @@ -23,7 +23,6 @@ import org.apache.commons.io.input.BoundedInputStream; import timber.log.Timber; import static com.fsck.k9.mail.internet.CharsetSupport.fixupCharset; -import static com.fsck.k9.mail.internet.MimeUtility.getHeaderParameter; import static com.fsck.k9.mail.internet.MimeUtility.isSameMimeType; import static com.fsck.k9.mail.internet.Viewable.Alternative; import static com.fsck.k9.mail.internet.Viewable.Html; @@ -77,7 +76,7 @@ public class MessageExtractor { /* * We've got a text part, so let's see if it needs to be processed further. */ - String charset = getHeaderParameter(part.getContentType(), "charset"); + String charset = PartExtensions.getCharset(part); /* * determine the charset from HTML message. */ diff --git a/mail/common/src/main/java/com/fsck/k9/mail/internet/MimeParameterDecoder.kt b/mail/common/src/main/java/com/fsck/k9/mail/internet/MimeParameterDecoder.kt index 7dcf29a6d6..5f4fc4d446 100644 --- a/mail/common/src/main/java/com/fsck/k9/mail/internet/MimeParameterDecoder.kt +++ b/mail/common/src/main/java/com/fsck/k9/mail/internet/MimeParameterDecoder.kt @@ -54,6 +54,26 @@ object MimeParameterDecoder { ) } + fun decodeBasic(headerBody: String): MimeValue { + val parser = MimeHeaderParser(headerBody) + + val value = parser.readHeaderValue() + parser.skipCFWS() + if (parser.endReached()) { + return MimeValue(value) + } + + val (basicParameters, duplicateParameters, parserErrorIndex) = readBasicParameters(parser) + val parameters = basicParameters.mapValues { (_, parameterValue) -> parameterValue.value } + + return MimeValue( + value = value, + parameters = parameters, + ignoredParameters = duplicateParameters, + parserErrorIndex = parserErrorIndex + ) + } + @JvmStatic fun extractHeaderValue(headerBody: String): String { val parser = MimeHeaderParser(headerBody) diff --git a/mail/common/src/main/java/com/fsck/k9/mail/internet/PartExtensions.kt b/mail/common/src/main/java/com/fsck/k9/mail/internet/PartExtensions.kt new file mode 100644 index 0000000000..a91d7db833 --- /dev/null +++ b/mail/common/src/main/java/com/fsck/k9/mail/internet/PartExtensions.kt @@ -0,0 +1,24 @@ +@file:JvmName("PartExtensions") +package com.fsck.k9.mail.internet + +import com.fsck.k9.mail.Part + +/** + * Return the `charset` parameter value of this [Part]'s `Content-Type` header. + */ +val Part.charset: String? + get() { + val contentTypeHeader = this.contentType ?: return null + val (_, parameters, duplicateParameters) = MimeParameterDecoder.decodeBasic(contentTypeHeader) + return parameters["charset"] ?: extractNonConflictingCharsetValue(duplicateParameters) + } + +// If there are multiple "charset" parameters, but they all agree on the value, we use that value. +private fun extractNonConflictingCharsetValue(duplicateParameters: List>): String? { + val charsets = duplicateParameters.asSequence() + .filter { (parameterName, _) -> parameterName == "charset" } + .map { (_, charset) -> charset.lowercase() } + .toSet() + + return if (charsets.size == 1) charsets.first() else null +} diff --git a/mail/common/src/test/java/com/fsck/k9/mail/internet/PartExtensionsTest.kt b/mail/common/src/test/java/com/fsck/k9/mail/internet/PartExtensionsTest.kt new file mode 100644 index 0000000000..f5afbdec56 --- /dev/null +++ b/mail/common/src/test/java/com/fsck/k9/mail/internet/PartExtensionsTest.kt @@ -0,0 +1,54 @@ +package com.fsck.k9.mail.internet + +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class PartExtensionsTest { + @Test + fun `get charset without charset parameter`() { + assertGetCharset(headerValue = "text/plain", expectedCharset = null) + } + + @Test + fun `get charset with single charset parameter`() { + assertGetCharset(headerValue = "text/plain; charset=UTF-8", expectedCharset = "utf-8") + } + + @Test + fun `get charset with single quoted charset parameter`() { + assertGetCharset(headerValue = "text/plain; charset=\"iso-8859-1\"", expectedCharset = "ISO-8859-1") + } + + @Test + fun `get charset with two charset parameters where values match exactly`() { + assertGetCharset(headerValue = "text/plain; charset=utf-8; charset=utf-8", expectedCharset = "utf-8") + } + + @Test + fun `get charset with two charset parameters where values differ in case`() { + assertGetCharset(headerValue = "text/plain; charset=utf-8; charset=UTF-8", expectedCharset = "utf-8") + } + + @Test + fun `get charset with two charset parameters where values differ in quoting`() { + assertGetCharset(headerValue = "text/plain; charset=utf-8; charset=\"utf-8\"", expectedCharset = "utf-8") + } + + @Test + fun `get charset with two charset parameters with conflicting values`() { + assertGetCharset(headerValue = "text/plain; charset=utf-8; charset=iso-8859-1", expectedCharset = null) + } + + @Test + fun `get charset with extended parameter syntax`() { + assertGetCharset(headerValue = "text/plain; charset*=us-ascii'en-us'utf-8", expectedCharset = null) + } + + private fun assertGetCharset(headerValue: String, expectedCharset: String?) { + val part = MimeBodyPart.create(null, headerValue) + + val charset = part.charset + + assertThat(charset).ignoringCase().isEqualTo(expectedCharset) + } +} -- GitLab From 075324af14caa3e3c849bd42e18ace346f7e3ef6 Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 11 Mar 2022 00:08:31 +0100 Subject: [PATCH 48/50] Clean up code to update notification channels --- .../NotificationChannelManager.kt | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) 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 a2349ed74b..ad33d10d28 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 @@ -58,7 +58,7 @@ class NotificationChannelManager( accounts: List ) { for (account in accounts) { - val groupId = account.uuid + val groupId = account.notificationChannelGroupId val group = NotificationChannelGroup(groupId, account.displayName) val channelMessages = getChannelMessages(account) @@ -75,25 +75,13 @@ class NotificationChannelManager( notificationManager: NotificationManager, accounts: List ) { - val existingAccounts = HashMap() - for (account in accounts) { - existingAccounts[account.uuid] = account - } + val accountUuids = accounts.map { it.uuid }.toSet() val groups = notificationManager.notificationChannelGroups for (group in groups) { - val groupId = group.id - - var shouldDelete = false - if (!existingAccounts.containsKey(groupId)) { - shouldDelete = true - } else if (existingAccounts[groupId]?.displayName != group.name.toString()) { - // There is no way to change group names. Deleting group, so it is re-generated. - shouldDelete = true - } - - if (shouldDelete) { - notificationManager.deleteNotificationChannelGroup(groupId) + val accountUuid = group.id.toAccountUuid() + if (accountUuid !in accountUuids) { + notificationManager.deleteNotificationChannelGroup(group.id) } } } @@ -248,6 +236,11 @@ class NotificationChannelManager( enableVibration(notificationSettings.vibration.isEnabled) } + private val Account.notificationChannelGroupId: String + get() = uuid + + private fun String.toAccountUuid(): String = this + private val Account.messagesNotificationChannelSuffix: String get() = messagesNotificationChannelVersion.let { version -> if (version == 0) "" else "_$version" } -- GitLab From 09ebe08f338b542ae420e293a39c44c35468493c Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 11 Mar 2022 18:49:31 +0100 Subject: [PATCH 49/50] Update translations --- app/ui/legacy/src/main/res/values-fa/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/ui/legacy/src/main/res/values-fa/strings.xml b/app/ui/legacy/src/main/res/values-fa/strings.xml index 9311fb2a44..1166630dc7 100644 --- a/app/ui/legacy/src/main/res/values-fa/strings.xml +++ b/app/ui/legacy/src/main/res/values-fa/strings.xml @@ -231,6 +231,7 @@ در صورت وجود، نام گیرنده را از لیست مخاطبان نمایش بده رنگی‌کردن مخاطبان نام‌های موجود در لیست مخاطبان رنگی شود + رنگ نام مخاطب قلم‌های تک‌عرض هنگام نمایش پیام‌های متنی از قلم تک‌عرض استفاده کن جاگیری خودکار پیام‌ها -- GitLab From 40e446c442241c73953f90a35621567857ae927f Mon Sep 17 00:00:00 2001 From: cketti Date: Fri, 11 Mar 2022 19:10:51 +0100 Subject: [PATCH 50/50] Version 5.913 --- app/k9mail/build.gradle | 4 ++-- app/ui/legacy/src/main/res/raw/changelog_master.xml | 9 +++++++++ fastlane/metadata/android/en-US/changelogs/29013.txt | 7 +++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/29013.txt diff --git a/app/k9mail/build.gradle b/app/k9mail/build.gradle index 222f55712f..f57db07af9 100644 --- a/app/k9mail/build.gradle +++ b/app/k9mail/build.gradle @@ -47,8 +47,8 @@ android { applicationId "com.fsck.k9" testApplicationId "com.fsck.k9.tests" - versionCode 29012 - versionName '5.913-SNAPSHOT' + versionCode 29013 + versionName '5.913' // Keep in sync with the resource string array 'supported_languages' resConfigs "in", "br", "ca", "cs", "cy", "da", "de", "et", "en", "en_GB", "es", "eo", "eu", "fr", "gd", "gl", diff --git a/app/ui/legacy/src/main/res/raw/changelog_master.xml b/app/ui/legacy/src/main/res/raw/changelog_master.xml index e724cf1d0b..0dc11a4e17 100644 --- a/app/ui/legacy/src/main/res/raw/changelog_master.xml +++ b/app/ui/legacy/src/main/res/raw/changelog_master.xml @@ -5,6 +5,15 @@ Locale-specific versions are kept in res/raw-/changelog.xml. --> + + Don't create notifications when manually refreshing the message list + Fixed import and export of notification settings + Fixed bug that could lead to multiple notifications being created for the same message + Fixed bug that sometimes crashed the app after changing the notification sound + Added support for messages that specified the character set in multiple ways + A lot of internal improvements + Updated translations + Fixed bugs where notifications weren't removed when they should have been Added a setting to configure the notification sound on Android 8+ (because some vendor-specific Android versions removed this feature from their user interface) diff --git a/fastlane/metadata/android/en-US/changelogs/29013.txt b/fastlane/metadata/android/en-US/changelogs/29013.txt new file mode 100644 index 0000000000..38ce474597 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/29013.txt @@ -0,0 +1,7 @@ +- Don't create notifications when manually refreshing the message list +- Fixed import and export of notification settings +- Fixed bug that could lead to multiple notifications being created for the same message +- Fixed bug that sometimes crashed the app after changing the notification sound +- Added support for messages that specified the character set in multiple ways +- A lot of internal improvements +- Updated translations -- GitLab