Add support for updating existing notifications

This commit is contained in:
cketti 2022-03-07 19:03:36 +01:00
parent b5d1ac7bdc
commit d784151ef0
6 changed files with 95 additions and 36 deletions

View file

@ -27,7 +27,9 @@ internal class NewMailNotificationController(
fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean) { fun addNewMailNotification(account: Account, message: LocalMessage, silent: Boolean) {
val notificationData = newMailNotificationManager.addNewMailNotification(account, message, silent) val notificationData = newMailNotificationManager.addNewMailNotification(account, message, silent)
processNewMailNotificationData(notificationData) if (notificationData != null) {
processNewMailNotificationData(notificationData)
}
} }
fun removeNewMailNotifications( fun removeNewMailNotifications(

View file

@ -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 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( val singleNotificationData = createSingleNotificationData(
account = account, account = account,

View file

@ -2,7 +2,6 @@ package com.fsck.k9.notification
import com.fsck.k9.Account import com.fsck.k9.Account
import com.fsck.k9.controller.MessageReference import com.fsck.k9.controller.MessageReference
import com.fsck.k9.core.BuildConfig
internal const val MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS = 8 internal const val MAX_NUMBER_OF_NEW_MESSAGE_NOTIFICATIONS = 8
@ -30,15 +29,45 @@ internal class NotificationDataStore {
} }
@Synchronized @Synchronized
fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult { fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult? {
val notificationData = getNotificationData(account) val notificationData = getNotificationData(account)
val messageReference = content.messageReference val messageReference = content.messageReference
if (BuildConfig.DEBUG && notificationData.contains(messageReference)) { val activeNotification = notificationData.activeNotifications.firstOrNull { notificationHolder ->
throw AssertionError("Notification for message $messageReference already exists") notificationHolder.content.messageReference == messageReference
}
val inactiveNotification = notificationData.inactiveNotifications.firstOrNull { inactiveNotificationHolder ->
inactiveNotificationHolder.content.messageReference == messageReference
} }
return if (notificationData.isMaxNumberOfActiveNotificationsReached) { return if (activeNotification != null) {
val newActiveNotification = activeNotification.copy(content = content)
val notificationHolder = activeNotification.copy(
content = content
)
val operations = emptyList<NotificationStoreOperation>()
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)
val newNotificationData = notificationData.copy(
inactiveNotifications = newInactiveNotifications
)
notificationDataMap[account.uuid] = newNotificationData
null
} else if (notificationData.isMaxNumberOfActiveNotificationsReached) {
val lastNotificationHolder = notificationData.activeNotifications.last() val lastNotificationHolder = notificationData.activeNotifications.last()
val inactiveNotificationHolder = lastNotificationHolder.toInactiveNotificationHolder() val inactiveNotificationHolder = lastNotificationHolder.toInactiveNotificationHolder()
@ -175,14 +204,15 @@ internal class NotificationDataStore {
throw AssertionError("getNewNotificationId() called with no free notification ID") 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 NotificationHolder.toInactiveNotificationHolder() = InactiveNotificationHolder(timestamp, content)
private fun InactiveNotificationHolder.toNotificationHolder(notificationId: Int): NotificationHolder { private fun InactiveNotificationHolder.toNotificationHolder(notificationId: Int): NotificationHolder {
return NotificationHolder(notificationId, timestamp, content) return NotificationHolder(notificationId, timestamp, content)
} }
private fun <T> List<T>.replace(old: T, new: T): List<T> {
return map { element ->
if (element === old) new else element
}
}
} }

View file

@ -37,8 +37,8 @@ internal class NotificationRepository(
} }
@Synchronized @Synchronized
fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult { fun addNotification(account: Account, content: NotificationContent, timestamp: Long): AddNotificationResult? {
return notificationDataStore.addNotification(account, content, timestamp).also { result -> return notificationDataStore.addNotification(account, content, timestamp)?.also { result ->
persistNotificationDataStoreChanges( persistNotificationDataStoreChanges(
account = account, account = account,
operations = result.notificationStoreOperations, operations = result.notificationStoreOperations,

View file

@ -49,6 +49,7 @@ class NewMailNotificationManagerTest {
val result = manager.addNewMailNotification(account, message, silent = false) val result = manager.addNewMailNotification(account, message, silent = false)
assertNotNull(result)
assertThat(result.singleNotificationData.first().content).isEqualTo( assertThat(result.singleNotificationData.first().content).isEqualTo(
NotificationContent( NotificationContent(
messageReference = createMessageReference("msg-1"), messageReference = createMessageReference("msg-1"),
@ -85,6 +86,7 @@ class NewMailNotificationManagerTest {
val result = manager.addNewMailNotification(account, messageTwo, silent = false) val result = manager.addNewMailNotification(account, messageTwo, silent = false)
assertNotNull(result)
assertThat(result.singleNotificationData.first().content).isEqualTo( assertThat(result.singleNotificationData.first().content).isEqualTo(
NotificationContent( NotificationContent(
messageReference = createMessageReference("msg-2"), messageReference = createMessageReference("msg-2"),
@ -121,6 +123,7 @@ class NewMailNotificationManagerTest {
val result = manager.addNewMailNotification(account, message, silent = false) val result = manager.addNewMailNotification(account, message, silent = false)
assertNotNull(result)
val notificationId = NotificationIds.getSingleMessageNotificationId(account, index = 0) val notificationId = NotificationIds.getSingleMessageNotificationId(account, index = 0)
assertThat(result.cancelNotificationIds).isEqualTo(listOf(notificationId)) assertThat(result.cancelNotificationIds).isEqualTo(listOf(notificationId))
assertThat(result.singleNotificationData.first().notificationId).isEqualTo(notificationId) assertThat(result.singleNotificationData.first().notificationId).isEqualTo(notificationId)
@ -196,6 +199,7 @@ class NewMailNotificationManagerTest {
messageUid = "msg-2" messageUid = "msg-2"
) )
val dataTwo = manager.addNewMailNotification(account, messageTwo, silent = true) val dataTwo = manager.addNewMailNotification(account, messageTwo, silent = true)
assertNotNull(dataTwo)
val notificationIdTwo = dataTwo.singleNotificationData.first().notificationId val notificationIdTwo = dataTwo.singleNotificationData.first().notificationId
val messageThree = addMessageToNotificationContentCreator( val messageThree = addMessageToNotificationContentCreator(
sender = "Alice", sender = "Alice",

View file

@ -3,12 +3,8 @@ package com.fsck.k9.notification
import com.fsck.k9.Account import com.fsck.k9.Account
import com.fsck.k9.RobolectricTest import com.fsck.k9.RobolectricTest
import com.fsck.k9.controller.MessageReference import com.fsck.k9.controller.MessageReference
import com.fsck.k9.core.BuildConfig
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
import org.junit.Assert.fail
import org.junit.Assume.assumeFalse
import org.junit.Assume.assumeTrue
import org.junit.Test import org.junit.Test
private const val ACCOUNT_UUID = "1-2-3" private const val ACCOUNT_UUID = "1-2-3"
@ -26,6 +22,7 @@ class NotificationDataStoreTest : RobolectricTest() {
val result = notificationDataStore.addNotification(account, content, TIMESTAMP) val result = notificationDataStore.addNotification(account, content, TIMESTAMP)
assertNotNull(result)
assertThat(result.shouldCancelNotification).isFalse() assertThat(result.shouldCancelNotification).isFalse()
val holder = result.notificationHolder val holder = result.notificationHolder
@ -48,6 +45,7 @@ class NotificationDataStoreTest : RobolectricTest() {
val result = notificationDataStore.addNotification(account, createNotificationContent("9"), TIMESTAMP) val result = notificationDataStore.addNotification(account, createNotificationContent("9"), TIMESTAMP)
assertNotNull(result)
assertThat(result.shouldCancelNotification).isTrue() assertThat(result.shouldCancelNotification).isTrue()
assertThat(result.cancelNotificationId).isEqualTo(NotificationIds.getSingleMessageNotificationId(account, 0)) assertThat(result.cancelNotificationId).isEqualTo(NotificationIds.getSingleMessageNotificationId(account, 0))
} }
@ -107,19 +105,23 @@ class NotificationDataStoreTest : RobolectricTest() {
fun testNewMessagesCount() { fun testNewMessagesCount() {
val contentOne = createNotificationContent("1") val contentOne = createNotificationContent("1")
val resultOne = notificationDataStore.addNotification(account, contentOne, TIMESTAMP) val resultOne = notificationDataStore.addNotification(account, contentOne, TIMESTAMP)
assertNotNull(resultOne)
assertThat(resultOne.notificationData.newMessagesCount).isEqualTo(1) assertThat(resultOne.notificationData.newMessagesCount).isEqualTo(1)
val contentTwo = createNotificationContent("2") val contentTwo = createNotificationContent("2")
val resultTwo = notificationDataStore.addNotification(account, contentTwo, TIMESTAMP) val resultTwo = notificationDataStore.addNotification(account, contentTwo, TIMESTAMP)
assertNotNull(resultTwo)
assertThat(resultTwo.notificationData.newMessagesCount).isEqualTo(2) assertThat(resultTwo.notificationData.newMessagesCount).isEqualTo(2)
} }
@Test @Test
fun testIsSingleMessageNotification() { fun testIsSingleMessageNotification() {
val resultOne = notificationDataStore.addNotification(account, createNotificationContent("1"), TIMESTAMP) val resultOne = notificationDataStore.addNotification(account, createNotificationContent("1"), TIMESTAMP)
assertNotNull(resultOne)
assertThat(resultOne.notificationData.isSingleMessageNotification).isTrue() assertThat(resultOne.notificationData.isSingleMessageNotification).isTrue()
val resultTwo = notificationDataStore.addNotification(account, createNotificationContent("2"), TIMESTAMP) val resultTwo = notificationDataStore.addNotification(account, createNotificationContent("2"), TIMESTAMP)
assertNotNull(resultTwo)
assertThat(resultTwo.notificationData.isSingleMessageNotification).isFalse() assertThat(resultTwo.notificationData.isSingleMessageNotification).isFalse()
} }
@ -128,37 +130,58 @@ class NotificationDataStoreTest : RobolectricTest() {
val content = createNotificationContent("1") val content = createNotificationContent("1")
val addResult = notificationDataStore.addNotification(account, content, TIMESTAMP) val addResult = notificationDataStore.addNotification(account, content, TIMESTAMP)
assertNotNull(addResult)
assertThat(addResult.notificationData.activeNotifications.first()).isEqualTo(addResult.notificationHolder) assertThat(addResult.notificationData.activeNotifications.first()).isEqualTo(addResult.notificationHolder)
} }
@Test @Test
fun `adding notification for same message twice should throw in debug build`() { fun `adding notification for message with active notification should update notification`() {
assumeTrue(BuildConfig.DEBUG)
val content1 = createNotificationContent("1") val content1 = createNotificationContent("1")
val content2 = createNotificationContent("1") val content2 = createNotificationContent("1")
notificationDataStore.addNotification(account, content1, TIMESTAMP)
try { val resultOne = notificationDataStore.addNotification(account, content1, TIMESTAMP)
notificationDataStore.addNotification(account, content2, TIMESTAMP) val resultTwo = notificationDataStore.addNotification(account, content2, TIMESTAMP)
fail("Exception expected")
} catch (e: AssertionError) { assertNotNull(resultOne)
assertThat(e).hasMessageThat().matches("Notification for message .+ already exists") 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 @Test
fun `adding notification for same message twice should add another notification in release build`() { fun `adding notification for message with inactive notification should update notificationData`() {
assumeFalse(BuildConfig.DEBUG) 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 resultOne = notificationDataStore.addNotification(account, content, TIMESTAMP)
val content2 = createNotificationContent("1")
val addResult1 = notificationDataStore.addNotification(account, content1, TIMESTAMP) assertThat(resultOne).isNull()
val addResult2 = notificationDataStore.addNotification(account, content2, TIMESTAMP)
assertThat(addResult1.notificationHolder.notificationId) val resultTwo = notificationDataStore.removeNotifications(account) {
.isNotEqualTo(addResult2.notificationHolder.notificationId) 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 { private fun createAccount(): Account {