Repository: thunderbird/thunderbird-android (language: kotlin)

Issue:
Notifications removed
### Expected behavior

If there are multiple notifications for mails, the user expands the multiline 'compressed' notification and clicks on one of the mails, this mail should be opened in K-9 and should be removed from the notifications. The other mails should stay in the notifications.
### Actual behavior

If I open one mail from the notification bar, K-9 opens this mail but all mails are removed from the notifications.
### Steps to reproduce
1. Get multiple new mails (=notifications)
2. Expand the summarized notification to get all mails
3. Klick on one mail
### Environment

K-9 Mail version: 5.111

Android version: 7.0 (Nexus 5X)

Account type (IMAP, POP3, WebDAV/Exchange): IMAP



Discussion hints:
Possibly this is an enhancement. There is a difference of opinion as to whether notifications should clear:
- On opening the app
- On viewing the unified inbox / inbox for that account
- On viewing the message itself

We may need a setting for this.

Yes, I am totally with you. I also think a setting would be very helpful.


Pull request: Be more selective when removing notifications
- When opening a message for viewing, only the notification for that particular message is removed.
- When displaying the Unified Inbox, notifications for messages in the Unified Inbox are removed.
- When displaying the "new messages" list, notifications for all messages in that account are removed.
- When displaying the message list of a single folder, notifications for messages in that folder are removed.

Fixes #1664

Test command: ./gradlew :app:core:testDebugUnitTest --tests "com.fsck.k9.notification.NotificationDataStoreTest" --tests "com.fsck.k9.notification.NewMailNotificationManagerTest"

Fix patch:
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 ba4ca011e00..341a4bf038e 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
@@ -127,6 +127,7 @@ public class MessagingController {
     private final MemorizingMessagingListener memorizingMessagingListener = new MemorizingMessagingListener();
     private final MessageCountsProvider messageCountsProvider;
     private final DraftOperations draftOperations;
+    private final NotificationOperations notificationOperations;
 
 
     private MessagingListener checkMailListener = null;
@@ -166,6 +167,7 @@ public void run() {
         initializeControllerExtensions(controllerExtensions);
 
         draftOperations = new DraftOperations(this, messageStoreManager, saveMessageDataCreator);
+        notificationOperations = new NotificationOperations(notificationController, preferences, messageStoreManager);
     }
 
     private void initializeControllerExtensions(List<ControllerExtension> controllerExtensions) {
@@ -2553,9 +2555,9 @@ public void setCheckMailListener(MessagingListener checkMailListener) {
         }
     }
 
-    public void removeNotificationsForAccount(Account account) {
-        put("removeNotificationsForAccount", null, () -> {
-            notificationController.clearNewMailNotifications(account, false);
+    public void clearNotifications(LocalSearch search) {
+        put("clearNotifications", null, () -> {
+            notificationOperations.clearNotifications(search);
         });
     }
 
diff --git a/app/core/src/main/java/com/fsck/k9/controller/NotificationOperations.kt b/app/core/src/main/java/com/fsck/k9/controller/NotificationOperations.kt
new file mode 100644
index 00000000000..8ab0a7f4485
--- /dev/null
+++ b/app/core/src/main/java/com/fsck/k9/controller/NotificationOperations.kt
@@ -0,0 +1,63 @@
+package com.fsck.k9.controller
+
+import com.fsck.k9.Account
+import com.fsck.k9.Preferences
+import com.fsck.k9.mailstore.MessageStoreManager
+import com.fsck.k9.notification.NotificationController
+import com.fsck.k9.search.LocalSearch
+import com.fsck.k9.search.isNewMessages
+import com.fsck.k9.search.isSingleFolder
+import com.fsck.k9.search.isUnifiedInbox
+
+internal class NotificationOperations(
+    private val notificationController: NotificationController,
+    private val preferences: Preferences,
+    private val messageStoreManager: MessageStoreManager
+) {
+    fun clearNotifications(search: LocalSearch) {
+        if (search.isUnifiedInbox) {
+            clearUnifiedInboxNotifications()
+        } else if (search.isNewMessages) {
+            clearAllNotifications()
+        } else if (search.isSingleFolder) {
+            val account = search.firstAccount() ?: return
+            val folderId = search.folderIds.first()
+            clearNotifications(account, folderId)
+        } else {
+            // TODO: Remove notifications when updating the message list. That way we can easily remove only
+            //  notifications for messages that are currently displayed in the list.
+        }
+    }
+
+    private fun clearUnifiedInboxNotifications() {
+        for (account in preferences.accounts) {
+            val messageStore = messageStoreManager.getMessageStore(account)
+
+            val folderIds = messageStore.getFolders(excludeLocalOnly = true) { folderDetails ->
+                if (folderDetails.isIntegrate) folderDetails.id else null
+            }.filterNotNull().toSet()
+
+            if (folderIds.isNotEmpty()) {
+                notificationController.clearNewMailNotifications(account) { messageReferences ->
+                    messageReferences.filter { messageReference -> messageReference.folderId in folderIds }
+                }
+            }
+        }
+    }
+
+    private fun clearAllNotifications() {
+        for (account in preferences.accounts) {
+            notificationController.clearNewMailNotifications(account, clearNewMessageState = false)
+        }
+    }
+
+    private fun clearNotifications(account: Account, folderId: Long) {
+        notificationController.clearNewMailNotifications(account) { messageReferences ->
+            messageReferences.filter { messageReference -> messageReference.folderId == folderId }
+        }
+    }
+
+    private fun LocalSearch.firstAccount(): Account? {
+        return preferences.getAccount(accountUuids.first())
+    }
+}
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 9040c686aaf..19014486211 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
@@ -30,8 +30,16 @@ internal class NewMailNotificationController(
         processNewMailNotificationData(notificationData)
     }
 
-    fun removeNewMailNotification(account: Account, messageReference: MessageReference) {
-        val notificationData = newMailNotificationManager.removeNewMailNotification(account, messageReference)
+    fun removeNewMailNotifications(
+        account: Account,
+        clearNewMessageState: Boolean,
+        selector: (List<MessageReference>) -> List<MessageReference>
+    ) {
+        val notificationData = newMailNotificationManager.removeNewMailNotifications(
+            account,
+            clearNewMessageState,
+            selector
+        )
 
         if (notificationData != null) {
             processNewMailNotificationData(notificationData)
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 fbdaedc295d..8e1a8c3fe26 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
@@ -63,39 +63,36 @@ internal class NewMailNotificationManager(
         )
     }
 
-    fun removeNewMailNotification(account: Account, messageReference: MessageReference): NewMailNotificationData? {
-        val result = notificationRepository.removeNotification(account, messageReference) ?: return null
+    fun removeNewMailNotifications(
+        account: Account,
+        clearNewMessageState: Boolean,
+        selector: (List<MessageReference>) -> List<MessageReference>
+    ): NewMailNotificationData? {
+        val result = notificationRepository.removeNotifications(account, clearNewMessageState, selector) ?: return null
 
         val cancelNotificationIds = when {
-            result.shouldCancelNotification && result.notificationData.isEmpty() -> {
-                listOf(NotificationIds.getNewMailSummaryNotificationId(account), result.cancelNotificationId)
-            }
-            result.shouldCancelNotification -> {
-                listOf(result.cancelNotificationId)
+            result.notificationData.isEmpty() -> {
+                result.cancelNotificationIds + NotificationIds.getNewMailSummaryNotificationId(account)
             }
             else -> {
-                emptyList()
+                result.cancelNotificationIds
             }
         }
 
-        val singleNotificationDataList = if (result.shouldCreateNotification) {
-            listOf(
-                createSingleNotificationData(
-                    account = account,
-                    notificationId = result.notificationHolder.notificationId,
-                    content = result.notificationHolder.content,
-                    timestamp = result.notificationHolder.timestamp,
-                    addLockScreenNotification = result.notificationData.isSingleMessageNotification
-                )
+        val singleNotificationData = result.notificationHolders.map { notificationHolder ->
+            createSingleNotificationData(
+                account = account,
+                notificationId = notificationHolder.notificationId,
+                content = notificationHolder.content,
+                timestamp = notificationHolder.timestamp,
+                addLockScreenNotification = result.notificationData.isSingleMessageNotification
             )
-        } else {
-            emptyList()
         }
 
         return NewMailNotificationData(
             cancelNotificationIds = cancelNotificationIds,
             baseNotificationData = createBaseNotificationData(result.notificationData),
-            singleNotificationData = singleNotificationDataList,
+            singleNotificationData = singleNotificationData,
             summaryNotificationData = createSummaryNotificationData(result.notificationData, silent = true)
         )
     }
diff --git a/app/core/src/main/java/com/fsck/k9/notification/NotificationController.kt b/app/core/src/main/java/com/fsck/k9/notification/NotificationController.kt
index ddf27daf696..fe0144108e8 100644
--- a/app/core/src/main/java/com/fsck/k9/notification/NotificationController.kt
+++ b/app/core/src/main/java/com/fsck/k9/notification/NotificationController.kt
@@ -65,7 +65,13 @@ class NotificationController internal constructor(
     }
 
     fun removeNewMailNotification(account: Account, messageReference: MessageReference) {
-        newMailNotificationController.removeNewMailNotification(account, messageReference)
+        newMailNotificationController.removeNewMailNotifications(account, clearNewMessageState = true) {
+            listOf(messageReference)
+        }
+    }
+
+    fun clearNewMailNotifications(account: Account, selector: (List<MessageReference>) -> List<MessageReference>) {
+        newMailNotificationController.removeNewMailNotifications(account, clearNewMessageState = false, selector)
     }
 
     fun clearNewMailNotifications(account: Account, clearNewMessageState: Boolean) {
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 89b6f217f3b..c50985022b2 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
@@ -1,6 +1,7 @@
 package com.fsck.k9.notification
 
 import com.fsck.k9.Account
+import com.fsck.k9.controller.MessageReference
 
 /**
  * Holds information about active and inactive new message notifications of an account.
@@ -16,6 +17,19 @@ internal data class NotificationData(
     val isSingleMessageNotification: Boolean
         get() = activeNotifications.size == 1
 
+    @OptIn(ExperimentalStdlibApi::class)
+    val messageReferences: List<MessageReference>
+        get() {
+            return buildList(capacity = newMessagesCount) {
+                for (activeNotification in activeNotifications) {
+                    add(activeNotification.content.messageReference)
+                }
+                for (inactiveNotification in inactiveNotifications) {
+                    add(inactiveNotification.content.messageReference)
+                }
+            }
+        }
+
     fun isEmpty() = activeNotifications.isEmpty()
 
     companion object {
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 41b31adfdb4..51372b0a939 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
@@ -69,62 +69,76 @@ internal class NotificationDataStore {
     }
 
     @Synchronized
-    fun removeNotification(account: Account, messageReference: MessageReference): RemoveNotificationResult? {
+    fun removeNotifications(
+        account: Account,
+        selector: (List<MessageReference>) -> List<MessageReference>
+    ): RemoveNotificationsResult? {
         val notificationData = getNotificationData(account)
         if (notificationData.isEmpty()) return null
 
-        val notificationHolder = notificationData.activeNotifications.firstOrNull {
-            it.content.messageReference == messageReference
-        }
+        val removeMessageReferences = selector.invoke(notificationData.messageReferences)
 
-        return if (notificationHolder == null) {
-            val inactiveNotificationHolder = notificationData.inactiveNotifications.firstOrNull {
-                it.content.messageReference == messageReference
-            } ?: return null
+        val operations = mutableListOf<NotificationStoreOperation>()
+        val newNotificationHolders = mutableListOf<NotificationHolder>()
+        val cancelNotificationIds = mutableListOf<Int>()
 
-            val operations = listOf(
-                NotificationStoreOperation.Remove(messageReference)
-            )
+        for (messageReference in removeMessageReferences) {
+            val notificationHolder = notificationData.activeNotifications.firstOrNull {
+                it.content.messageReference == messageReference
+            }
 
-            val newNotificationData = notificationData.copy(
-                inactiveNotifications = notificationData.inactiveNotifications - inactiveNotificationHolder
-            )
-            notificationDataMap[account.uuid] = newNotificationData
+            if (notificationHolder == null) {
+                val inactiveNotificationHolder = notificationData.inactiveNotifications.firstOrNull {
+                    it.content.messageReference == messageReference
+                } ?: continue
 
-            RemoveNotificationResult.recreateSummaryNotification(newNotificationData, operations)
-        } else if (notificationData.inactiveNotifications.isNotEmpty()) {
-            val newNotificationHolder = notificationData.inactiveNotifications.first()
-                .toNotificationHolder(notificationHolder.notificationId)
+                operations.add(NotificationStoreOperation.Remove(messageReference))
 
-            val operations = listOf(
-                NotificationStoreOperation.Remove(messageReference),
-                NotificationStoreOperation.ChangeToActive(
-                    newNotificationHolder.content.messageReference,
-                    newNotificationHolder.notificationId
+                val newNotificationData = notificationData.copy(
+                    inactiveNotifications = notificationData.inactiveNotifications - inactiveNotificationHolder
+                )
+                notificationDataMap[account.uuid] = newNotificationData
+            } else if (notificationData.inactiveNotifications.isNotEmpty()) {
+                val newNotificationHolder = notificationData.inactiveNotifications.first()
+                    .toNotificationHolder(notificationHolder.notificationId)
+
+                newNotificationHolders.add(newNotificationHolder)
+                cancelNotificationIds.add(notificationHolder.notificationId)
+
+                operations.add(NotificationStoreOperation.Remove(messageReference))
+                operations.add(
+                    NotificationStoreOperation.ChangeToActive(
+                        newNotificationHolder.content.messageReference,
+                        newNotificationHolder.notificationId
+                    )
                 )
-            )
 
-            val newNotificationData = notificationData.copy(
-                activeNotifications = notificationData.activeNotifications - notificationHolder + newNotificationHolder,
-                inactiveNotifications = notificationData.inactiveNotifications.drop(1)
-            )
-            notificationDataMap[account.uuid] = newNotificationData
+                val newNotificationData = notificationData.copy(
+                    activeNotifications = notificationData.activeNotifications - notificationHolder +
+                        newNotificationHolder,
+                    inactiveNotifications = notificationData.inactiveNotifications.drop(1)
+                )
+                notificationDataMap[account.uuid] = newNotificationData
+            } else {
+                cancelNotificationIds.add(notificationHolder.notificationId)
 
-            RemoveNotificationResult.replaceNotification(newNotificationData, operations, newNotificationHolder)
-        } else {
-            val operations = listOf(
-                NotificationStoreOperation.Remove(messageReference),
-            )
+                operations.add(NotificationStoreOperation.Remove(messageReference))
 
-            val newNotificationData = notificationData.copy(
-                activeNotifications = notificationData.activeNotifications - notificationHolder
-            )
-            notificationDataMap[account.uuid] = newNotificationData
+                val newNotificationData = notificationData.copy(
+                    activeNotifications = notificationData.activeNotifications - notificationHolder
+                )
+                notificationDataMap[account.uuid] = newNotificationData
+            }
+        }
 
-            RemoveNotificationResult.cancelNotification(
-                newNotificationData,
-                operations,
-                notificationHolder.notificationId
+        return if (operations.isEmpty()) {
+            null
+        } else {
+            RemoveNotificationsResult(
+                notificationData = getNotificationData(account),
+                notificationStoreOperations = operations,
+                notificationHolders = newNotificationHolders,
+                cancelNotificationIds = cancelNotificationIds
             )
         }
     }
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 5ac77e94047..b18f3686aa7 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
@@ -44,9 +44,15 @@ internal class NotificationRepository(
     }
 
     @Synchronized
-    fun removeNotification(account: Account, messageReference: MessageReference): RemoveNotificationResult? {
-        return notificationDataStore.removeNotification(account, messageReference)?.also { result ->
-            persistNotificationDataStoreChanges(account, result.notificationStoreOperations)
+    fun removeNotifications(
+        account: Account,
+        clearNewMessageState: Boolean = true,
+        selector: (List<MessageReference>) -> List<MessageReference>
+    ): RemoveNotificationsResult? {
+        return notificationDataStore.removeNotifications(account, selector)?.also { result ->
+            if (clearNewMessageState) {
+                persistNotificationDataStoreChanges(account, result.notificationStoreOperations)
+            }
         }
     }
 
diff --git a/app/core/src/main/java/com/fsck/k9/notification/RemoveNotificationResult.kt b/app/core/src/main/java/com/fsck/k9/notification/RemoveNotificationResult.kt
deleted file mode 100644
index cfad5cfa1be..00000000000
--- a/app/core/src/main/java/com/fsck/k9/notification/RemoveNotificationResult.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.fsck.k9.notification
-
-internal class RemoveNotificationResult private constructor(
-    val notificationData: NotificationData,
-    val notificationStoreOperations: List<NotificationStoreOperation>,
-    private val holder: NotificationHolder?,
-    private val notificationId: Int?
-) {
-    val shouldCreateNotification: Boolean
-        get() = holder != null
-
-    val notificationHolder: NotificationHolder
-        get() = holder ?: error("shouldCreateNotification == false")
-
-    val shouldCancelNotification: Boolean
-        get() = notificationId != null
-
-    val cancelNotificationId: Int
-        get() = notificationId ?: error("shouldCancelNotification == false")
-
-    companion object {
-        fun cancelNotification(
-            notificationData: NotificationData,
-            notificationStoreOperations: List<NotificationStoreOperation>,
-            notificationId: Int
-        ): RemoveNotificationResult {
-            return RemoveNotificationResult(
-                notificationData = notificationData,
-                notificationStoreOperations = notificationStoreOperations,
-                holder = null,
-                notificationId = notificationId
-            )
-        }
-
-        fun replaceNotification(
-            notificationData: NotificationData,
-            notificationStoreOperations: List<NotificationStoreOperation>,
-            notificationHolder: NotificationHolder
-        ): RemoveNotificationResult {
-            return RemoveNotificationResult(
-                notificationData = notificationData,
-                notificationStoreOperations = notificationStoreOperations,
-                holder = notificationHolder,
-                notificationId = notificationHolder.notificationId
-            )
-        }
-
-        fun recreateSummaryNotification(
-            notificationData: NotificationData,
-            notificationStoreOperations: List<NotificationStoreOperation>
-        ): RemoveNotificationResult {
-            return RemoveNotificationResult(
-                notificationData = notificationData,
-                notificationStoreOperations = notificationStoreOperations,
-                holder = null,
-                notificationId = null
-            )
-        }
-    }
-}
diff --git a/app/core/src/main/java/com/fsck/k9/notification/RemoveNotificationsResult.kt b/app/core/src/main/java/com/fsck/k9/notification/RemoveNotificationsResult.kt
new file mode 100644
index 00000000000..f3c2d44bffa
--- /dev/null
+++ b/app/core/src/main/java/com/fsck/k9/notification/RemoveNotificationsResult.kt
@@ -0,0 +1,8 @@
+package com.fsck.k9.notification
+
+internal data class RemoveNotificationsResult(
+    val notificationData: NotificationData,
+    val notificationStoreOperations: List<NotificationStoreOperation>,
+    val notificationHolders: List<NotificationHolder>,
+    val cancelNotificationIds: List<Int>
+)
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 fefe5c7d151..77e12728265 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
@@ -2,7 +2,6 @@ package com.fsck.k9.notification
 
 import com.fsck.k9.Account
 import com.fsck.k9.K9
-import com.fsck.k9.controller.MessageReference
 
 private const val MAX_NUMBER_OF_MESSAGES_FOR_SUMMARY_NOTIFICATION = 5
 
@@ -92,17 +91,4 @@ internal class SummaryNotificationDataCreator(
 
     private val NotificationData.additionalMessagesCount: Int
         get() = (newMessagesCount - MAX_NUMBER_OF_MESSAGES_FOR_SUMMARY_NOTIFICATION).coerceAtLeast(0)
-
-    @OptIn(ExperimentalStdlibApi::class)
-    private val NotificationData.messageReferences: List<MessageReference>
-        get() {
-            return buildList(capacity = newMessagesCount) {
-                for (activeNotification in activeNotifications) {
-                    add(activeNotification.content.messageReference)
-                }
-                for (inactiveNotification in inactiveNotifications) {
-                    add(inactiveNotification.content.messageReference)
-                }
-            }
-        }
 }
diff --git a/app/core/src/main/java/com/fsck/k9/search/LocalSearchExtensions.kt b/app/core/src/main/java/com/fsck/k9/search/LocalSearchExtensions.kt
index 8e49538dac5..d528446987b 100644
--- a/app/core/src/main/java/com/fsck/k9/search/LocalSearchExtensions.kt
+++ b/app/core/src/main/java/com/fsck/k9/search/LocalSearchExtensions.kt
@@ -5,6 +5,18 @@ package com.fsck.k9.search
 import com.fsck.k9.Account
 import com.fsck.k9.Preferences
 
+val LocalSearch.isUnifiedInbox: Boolean
+    get() = id == SearchAccount.UNIFIED_INBOX
+
+val LocalSearch.isNewMessages: Boolean
+    get() = id == SearchAccount.NEW_MESSAGES
+
+val LocalSearch.isSingleAccount: Boolean
+    get() = accountUuids.size == 1
+
+val LocalSearch.isSingleFolder: Boolean
+    get() = isSingleAccount && folderIds.size == 1
+
 @JvmName("getAccountsFromLocalSearch")
 fun LocalSearch.getAccounts(preferences: Preferences): List<Account> {
     val accounts = preferences.accounts
diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
index a027d0482b9..510982e817e 100644
--- a/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
+++ b/app/ui/legacy/src/main/java/com/fsck/k9/activity/MessageList.kt
@@ -32,6 +32,7 @@ import com.fsck.k9.Preferences
 import com.fsck.k9.account.BackgroundAccountRemover
 import com.fsck.k9.activity.compose.MessageActions
 import com.fsck.k9.controller.MessageReference
+import com.fsck.k9.controller.MessagingController
 import com.fsck.k9.fragment.MessageListFragment
 import com.fsck.k9.fragment.MessageListFragment.MessageListFragmentListener
 import com.fsck.k9.helper.Contacts
@@ -44,6 +45,7 @@ import com.fsck.k9.search.SearchAccount
 import com.fsck.k9.search.SearchSpecification
 import com.fsck.k9.search.SearchSpecification.SearchCondition
 import com.fsck.k9.search.SearchSpecification.SearchField
+import com.fsck.k9.search.isUnifiedInbox
 import com.fsck.k9.ui.BuildConfig
 import com.fsck.k9.ui.K9Drawer
 import com.fsck.k9.ui.R
@@ -92,6 +94,7 @@ open class MessageList :
     private val defaultFolderProvider: DefaultFolderProvider by inject()
     private val accountRemover: BackgroundAccountRemover by inject()
     private val generalSettingsManager: GeneralSettingsManager by inject()
+    private val messagingController: MessagingController by inject()
 
     private val permissionUiHelper: PermissionUiHelper = K9PermissionUiHelper(this)
 
@@ -347,6 +350,7 @@ open class MessageList :
                     }
                 }
                 setDrawerLockState()
+                onMessageListDisplayed()
             }
         }
     }
@@ -541,6 +545,10 @@ open class MessageList :
             recreate()
         }
 
+        if (displayMode != DisplayMode.MESSAGE_VIEW) {
+            onMessageListDisplayed()
+        }
+
         if (this !is Search) {
             // necessary b/c no guarantee Search.onStop will be called before MessageList.onResume
             // when returning from search results
@@ -613,6 +621,8 @@ open class MessageList :
         search.addAllowedFolder(folderId)
 
         performSearch(search)
+
+        onMessageListDisplayed()
     }
 
     private fun openFolderImmediately(folderId: Long) {
@@ -1462,6 +1472,8 @@ open class MessageList :
 
         showDefaultTitleView()
         configureMenu(menu)
+
+        onMessageListDisplayed()
     }
 
     private fun setDrawerLockState() {
@@ -1519,6 +1531,14 @@ open class MessageList :
         }
     }
 
+    private fun onMessageListDisplayed() {
+        clearNotifications()
+    }
+
+    private fun clearNotifications() {
+        messagingController.clearNotifications(search)
+    }
+
     override fun startIntentSenderForResult(
         intent: IntentSender,
         requestCode: Int,
@@ -1591,9 +1611,6 @@ open class MessageList :
         }
     }
 
-    private val LocalSearch.isUnifiedInbox: Boolean
-        get() = id == SearchAccount.UNIFIED_INBOX
-
     private fun MessageReference.toLocalSearch(): LocalSearch {
         return LocalSearch().apply {
             addAccountUuid(accountUuid)
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 9d6ead01099..ecc400c3e98 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
@@ -475,11 +475,6 @@ class MessageListFragment :
 
         messagingController.addListener(activityListener)
 
-        for (account in localSearch.getAccounts(preferences)) {
-            // TODO: Only remove notifications for messages in the currently displayed message list
-            messagingController.removeNotificationsForAccount(account)
-        }
-
         updateTitle()
     }
 


Test patch:
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 7393229c32f..873dd7cb5f7 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
@@ -128,7 +128,9 @@ class NewMailNotificationManagerTest {
 
     @Test
     fun `remove notification when none was added before should return null`() {
-        val result = manager.removeNewMailNotification(account, createMessageReference("any"))
+        val result = manager.removeNewMailNotifications(account, clearNewMessageState = true) {
+            listOf(createMessageReference("any"))
+        }
 
         assertThat(result).isNull()
     }
@@ -144,7 +146,9 @@ class NewMailNotificationManagerTest {
         )
         manager.addNewMailNotification(account, message, silent = false)
 
-        val result = manager.removeNewMailNotification(account, createMessageReference("untracked"))
+        val result = manager.removeNewMailNotifications(account, clearNewMessageState = true) {
+            listOf(createMessageReference("untracked"))
+        }
 
         assertThat(result).isNull()
     }
@@ -160,7 +164,9 @@ class NewMailNotificationManagerTest {
         )
         manager.addNewMailNotification(account, message, silent = false)
 
-        val result = manager.removeNewMailNotification(account, createMessageReference("msg-1"))
+        val result = manager.removeNewMailNotifications(account, clearNewMessageState = true) {
+            listOf(createMessageReference("msg-1"))
+        }
 
         assertNotNull(result) { data ->
             assertThat(data.cancelNotificationIds).containsExactly(
@@ -200,7 +206,9 @@ class NewMailNotificationManagerTest {
         )
         manager.addNewMailNotification(account, messageThree, silent = true)
 
-        val result = manager.removeNewMailNotification(account, createMessageReference("msg-2"))
+        val result = manager.removeNewMailNotifications(account, clearNewMessageState = true) {
+            listOf(createMessageReference("msg-2"))
+        }
 
         assertNotNull(result) { data ->
             assertThat(data.cancelNotificationIds).isEqualTo(listOf(notificationIdTwo))
@@ -230,7 +238,9 @@ class NewMailNotificationManagerTest {
         manager.addNewMailNotification(account, message, silent = false)
         addMaximumNumberOfNotifications()
 
-        val result = manager.removeNewMailNotification(account, createMessageReference("msg-1"))
+        val result = manager.removeNewMailNotifications(account, clearNewMessageState = true) {
+            listOf(createMessageReference("msg-1"))
+        }
 
         assertNotNull(result) { data ->
             assertThat(data.cancelNotificationIds).hasSize(1)
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 082902a2486..9bb2c110bb2 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
@@ -53,12 +53,12 @@ class NotificationDataStoreTest : RobolectricTest() {
         val content = createNotificationContent("1")
         notificationDataStore.addNotification(account, content, TIMESTAMP)
 
-        val result = notificationDataStore.removeNotification(account, content.messageReference)
+        val result = notificationDataStore.removeNotifications(account) { listOf(content.messageReference) }
 
         assertNotNull(result) { removeResult ->
-            assertThat(removeResult.cancelNotificationId)
-                .isEqualTo(NotificationIds.getSingleMessageNotificationId(account, 0))
-            assertThat(removeResult.shouldCreateNotification).isFalse()
+            assertThat(removeResult.cancelNotificationIds)
+                .containsExactly(NotificationIds.getSingleMessageNotificationId(account, 0))
+            assertThat(removeResult.notificationHolders).isEmpty()
         }
     }
 
@@ -77,16 +77,16 @@ class NotificationDataStoreTest : RobolectricTest() {
         val latestContent = createNotificationContent("10")
         notificationDataStore.addNotification(account, latestContent, TIMESTAMP)
 
-        val result = notificationDataStore.removeNotification(account, latestContent.messageReference)
+        val result = notificationDataStore.removeNotifications(account) { listOf(latestContent.messageReference) }
 
         assertNotNull(result) { removeResult ->
-            assertThat(removeResult.cancelNotificationId)
-                .isEqualTo(NotificationIds.getSingleMessageNotificationId(account, 1))
-            assertThat(removeResult.shouldCreateNotification).isTrue()
-            assertNotNull(removeResult.notificationHolder) { holder ->
-                assertThat(holder.notificationId).isEqualTo(NotificationIds.getSingleMessageNotificationId(account, 1))
-                assertThat(holder.content).isEqualTo(content)
-            }
+            assertThat(removeResult.cancelNotificationIds)
+                .containsExactly(NotificationIds.getSingleMessageNotificationId(account, 1))
+            assertThat(removeResult.notificationHolders).hasSize(1)
+
+            val holder = removeResult.notificationHolders.first()
+            assertThat(holder.notificationId).isEqualTo(NotificationIds.getSingleMessageNotificationId(account, 1))
+            assertThat(holder.content).isEqualTo(content)
         }
     }
 
@@ -95,7 +95,7 @@ class NotificationDataStoreTest : RobolectricTest() {
         for (i in 1..MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS + 1) {
             val content = createNotificationContent(i.toString())
             notificationDataStore.addNotification(account, content, TIMESTAMP)
-            notificationDataStore.removeNotification(account, content.messageReference)
+            notificationDataStore.removeNotifications(account) { listOf(content.messageReference) }
         }
     }