Notification: implement a counter in the fallback notification.

This commit is contained in:
Benoit Marty 2026-02-11 16:43:03 +01:00
parent 71fef289d1
commit 4234600d06
19 changed files with 126 additions and 97 deletions

View file

@ -34,6 +34,7 @@ interface ActiveNotificationsProvider {
fun getMembershipNotificationForSession(sessionId: SessionId): List<StatusBarNotification> fun getMembershipNotificationForSession(sessionId: SessionId): List<StatusBarNotification>
fun getMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification> fun getMembershipNotificationForRoom(sessionId: SessionId, roomId: RoomId): List<StatusBarNotification>
fun getSummaryNotification(sessionId: SessionId): StatusBarNotification? fun getSummaryNotification(sessionId: SessionId): StatusBarNotification?
fun getFallbackNotification(sessionId: SessionId): StatusBarNotification?
fun count(sessionId: SessionId): Int fun count(sessionId: SessionId): Int
} }
@ -76,6 +77,11 @@ class DefaultActiveNotificationsProvider(
return getNotificationsForSession(sessionId).find { it.id == summaryId } return getNotificationsForSession(sessionId).find { it.id == summaryId }
} }
override fun getFallbackNotification(sessionId: SessionId): StatusBarNotification? {
val fallbackId = NotificationIdProvider.getFallbackNotificationId(sessionId)
return getNotificationsForSession(sessionId).find { it.id == fallbackId }
}
override fun count(sessionId: SessionId): Int { override fun count(sessionId: SessionId): Int {
return getNotificationsForSession(sessionId).size return getNotificationsForSession(sessionId).size
} }

View file

@ -12,15 +12,12 @@ import dev.zacsweers.metro.Inject
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.push.impl.R
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.services.toolbox.api.strings.StringProvider
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
@Inject @Inject
class FallbackNotificationFactory( class FallbackNotificationFactory(
private val clock: SystemClock, private val clock: SystemClock,
private val stringProvider: StringProvider,
) { ) {
fun create( fun create(
sessionId: SessionId, sessionId: SessionId,
@ -36,7 +33,7 @@ class FallbackNotificationFactory(
isRedacted = false, isRedacted = false,
isUpdated = false, isUpdated = false,
timestamp = clock.epochMillis(), timestamp = clock.epochMillis(),
description = stringProvider.getString(R.string.notification_fallback_content), description = "",
cause = cause, cause = cause,
) )
} }

View file

@ -21,7 +21,6 @@ import io.element.android.libraries.push.impl.notifications.model.FallbackNotifi
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
import io.element.android.services.toolbox.api.strings.StringProvider
interface NotificationDataFactory { interface NotificationDataFactory {
suspend fun toNotifications( suspend fun toNotifications(
@ -46,16 +45,15 @@ interface NotificationDataFactory {
@JvmName("toNotificationFallbackEvents") @JvmName("toNotificationFallbackEvents")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
fun toNotifications( fun toNotification(
fallback: List<FallbackNotifiableEvent>, fallback: List<FallbackNotifiableEvent>,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification> ): OneShotNotification?
fun createSummaryNotification( fun createSummaryNotification(
roomNotifications: List<RoomNotification>, roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>, invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>, simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
): SummaryNotification ): SummaryNotification
} }
@ -66,7 +64,6 @@ class DefaultNotificationDataFactory(
private val roomGroupMessageCreator: RoomGroupMessageCreator, private val roomGroupMessageCreator: RoomGroupMessageCreator,
private val summaryGroupMessageCreator: SummaryGroupMessageCreator, private val summaryGroupMessageCreator: SummaryGroupMessageCreator,
private val activeNotificationsProvider: ActiveNotificationsProvider, private val activeNotificationsProvider: ActiveNotificationsProvider,
private val stringProvider: StringProvider,
) : NotificationDataFactory { ) : NotificationDataFactory {
override suspend fun toNotifications( override suspend fun toNotifications(
messages: List<NotifiableMessageEvent>, messages: List<NotifiableMessageEvent>,
@ -141,25 +138,31 @@ class DefaultNotificationDataFactory(
@JvmName("toNotificationFallbackEvents") @JvmName("toNotificationFallbackEvents")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
override fun toNotifications( override fun toNotification(
fallback: List<FallbackNotifiableEvent>, fallback: List<FallbackNotifiableEvent>,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification> { ): OneShotNotification? {
return fallback.map { event -> if (fallback.isEmpty()) return null
OneShotNotification( val existingNotification = activeNotificationsProvider
tag = event.eventId.value, .getFallbackNotification(notificationAccountParams.user.userId)
notification = notificationCreator.createFallbackNotification(notificationAccountParams, event), ?.notification
isNoisy = false, val notification = notificationCreator.createFallbackNotification(
timestamp = event.timestamp existingNotification,
) notificationAccountParams,
} fallback,
)
return OneShotNotification(
tag = "FALLBACK",
notification = notification,
isNoisy = false,
timestamp = fallback.first().timestamp
)
} }
override fun createSummaryNotification( override fun createSummaryNotification(
roomNotifications: List<RoomNotification>, roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>, invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>, simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
): SummaryNotification { ): SummaryNotification {
return when { return when {
@ -169,7 +172,6 @@ class DefaultNotificationDataFactory(
roomNotifications = roomNotifications, roomNotifications = roomNotifications,
invitationNotifications = invitationNotifications, invitationNotifications = invitationNotifications,
simpleNotifications = simpleNotifications, simpleNotifications = simpleNotifications,
fallbackNotifications = fallbackNotifications,
notificationAccountParams = notificationAccountParams, notificationAccountParams = notificationAccountParams,
) )
) )

View file

@ -55,12 +55,11 @@ class NotificationRenderer(
val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, imageLoader, notificationAccountParams) val roomNotifications = notificationDataFactory.toNotifications(groupedEvents.roomEvents, imageLoader, notificationAccountParams)
val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents, notificationAccountParams) val invitationNotifications = notificationDataFactory.toNotifications(groupedEvents.invitationEvents, notificationAccountParams)
val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents, notificationAccountParams) val simpleNotifications = notificationDataFactory.toNotifications(groupedEvents.simpleEvents, notificationAccountParams)
val fallbackNotifications = notificationDataFactory.toNotifications(groupedEvents.fallbackEvents, notificationAccountParams) val fallbackNotification = notificationDataFactory.toNotification(groupedEvents.fallbackEvents, notificationAccountParams)
val summaryNotification = notificationDataFactory.createSummaryNotification( val summaryNotification = notificationDataFactory.createSummaryNotification(
roomNotifications = roomNotifications, roomNotifications = roomNotifications,
invitationNotifications = invitationNotifications, invitationNotifications = invitationNotifications,
simpleNotifications = simpleNotifications, simpleNotifications = simpleNotifications,
fallbackNotifications = fallbackNotifications,
notificationAccountParams = notificationAccountParams, notificationAccountParams = notificationAccountParams,
) )
@ -107,13 +106,12 @@ class NotificationRenderer(
} }
} }
// Show only the first fallback notification if (fallbackNotification != null) {
if (fallbackNotifications.isNotEmpty()) { Timber.tag(loggerTag.value).d("Showing or updating fallback notification")
Timber.tag(loggerTag.value).d("Showing fallback notification")
notificationDisplayer.showNotification( notificationDisplayer.showNotification(
tag = "FALLBACK", tag = fallbackNotification.tag,
id = NotificationIdProvider.getFallbackNotificationId(currentUser.userId), id = NotificationIdProvider.getFallbackNotificationId(currentUser.userId),
notification = fallbackNotifications.first().notification notification = fallbackNotification.notification,
) )
} }

View file

@ -22,7 +22,6 @@ interface SummaryGroupMessageCreator {
roomNotifications: List<RoomNotification>, roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>, invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>, simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
): Notification ): Notification
} }
@ -45,7 +44,6 @@ class DefaultSummaryGroupMessageCreator(
roomNotifications: List<RoomNotification>, roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>, invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>, simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
): Notification { ): Notification {
val summaryIsNoisy = roomNotifications.any { it.shouldBing } || val summaryIsNoisy = roomNotifications.any { it.shouldBing } ||
invitationNotifications.any { it.isNoisy } || invitationNotifications.any { it.isNoisy } ||

View file

@ -75,8 +75,9 @@ interface NotificationCreator {
): Notification ): Notification
fun createFallbackNotification( fun createFallbackNotification(
existingNotification: Notification?,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
fallbackNotifiableEvent: FallbackNotifiableEvent, fallbackNotifiableEvents: List<FallbackNotifiableEvent>,
): Notification ): Notification
/** /**
@ -240,11 +241,13 @@ class DefaultNotificationCreator(
.addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent)) .addAction(rejectInvitationActionFactory.create(inviteNotifiableEvent))
.addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent)) .addAction(acceptInvitationActionFactory.create(inviteNotifiableEvent))
// Build the pending intent for when the notification is clicked // Build the pending intent for when the notification is clicked
.setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent( .setContentIntent(
sessionId = inviteNotifiableEvent.sessionId, pendingIntentFactory.createOpenRoomPendingIntent(
roomId = inviteNotifiableEvent.roomId, sessionId = inviteNotifiableEvent.sessionId,
eventId = null, roomId = inviteNotifiableEvent.roomId,
)) eventId = null,
)
)
.apply { .apply {
if (inviteNotifiableEvent.noisy) { if (inviteNotifiableEvent.noisy) {
// Compat // Compat
@ -276,12 +279,14 @@ class DefaultNotificationCreator(
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
.configureWith(notificationAccountParams) .configureWith(notificationAccountParams)
.setAutoCancel(true) .setAutoCancel(true)
.setContentIntent(pendingIntentFactory.createOpenRoomPendingIntent( .setContentIntent(
sessionId = simpleNotifiableEvent.sessionId, pendingIntentFactory.createOpenRoomPendingIntent(
roomId = simpleNotifiableEvent.roomId, sessionId = simpleNotifiableEvent.sessionId,
eventId = null, roomId = simpleNotifiableEvent.roomId,
extras = bundleOf(ROOM_OPENED_FROM_NOTIFICATION to true), eventId = null,
)) extras = bundleOf(ROOM_OPENED_FROM_NOTIFICATION to true),
)
)
.apply { .apply {
if (simpleNotifiableEvent.noisy) { if (simpleNotifiableEvent.noisy) {
// Compat // Compat
@ -295,28 +300,35 @@ class DefaultNotificationCreator(
} }
override fun createFallbackNotification( override fun createFallbackNotification(
existingNotification: Notification?,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
fallbackNotifiableEvent: FallbackNotifiableEvent, fallbackNotifiableEvents: List<FallbackNotifiableEvent>,
): Notification { ): Notification {
val channelId = notificationChannels.getChannelIdForMessage(false) val channelId = notificationChannels.getChannelIdForMessage(false)
val existingCounter = existingNotification
?.extras
?.getInt(FALLBACK_COUNTER_EXTRA)
?: 0
val counter = existingCounter + fallbackNotifiableEvents.size
val fallbackNotifiableEvent = fallbackNotifiableEvents.first()
return NotificationCompat.Builder(context, channelId) return NotificationCompat.Builder(context, channelId)
.setOnlyAlertOnce(true) .setOnlyAlertOnce(true)
.setContentTitle(buildMeta.applicationName.annotateForDebug(7)) .setContentTitle(buildMeta.applicationName.annotateForDebug(7))
.setContentText(fallbackNotifiableEvent.description.orEmpty().annotateForDebug(8)) .setContentText(
stringProvider.getQuantityString(R.plurals.notification_fallback_n_content, counter, counter)
.annotateForDebug(8)
)
.setExtras(
bundleOf(
FALLBACK_COUNTER_EXTRA to counter
)
)
.setNumber(counter)
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL) .setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_ALL)
.configureWith(notificationAccountParams) .configureWith(notificationAccountParams)
.setAutoCancel(true) .setAutoCancel(true)
.setWhen(fallbackNotifiableEvent.timestamp) .setWhen(fallbackNotifiableEvent.timestamp)
// Ideally we'd use `createOpenRoomPendingIntent` here, but the broken notification might apply to an invite
// and the user won't have access to the room yet, resulting in an error screen.
.setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(fallbackNotifiableEvent.sessionId)) .setContentIntent(pendingIntentFactory.createOpenSessionPendingIntent(fallbackNotifiableEvent.sessionId))
.setDeleteIntent(
pendingIntentFactory.createDismissEventPendingIntent(
fallbackNotifiableEvent.sessionId,
fallbackNotifiableEvent.roomId,
fallbackNotifiableEvent.eventId
)
)
.setPriority(NotificationCompat.PRIORITY_LOW) .setPriority(NotificationCompat.PRIORITY_LOW)
.build() .build()
} }
@ -503,6 +515,7 @@ class DefaultNotificationCreator(
companion object { companion object {
const val MESSAGE_EVENT_ID = "message_event_id" const val MESSAGE_EVENT_ID = "message_event_id"
private const val FALLBACK_COUNTER_EXTRA = "COUNTER"
} }
} }

View file

@ -15,6 +15,10 @@
</plurals> </plurals>
<string name="notification_error_unified_push_unregistered_android">"The UnifiedPush notification distributor couldn\'t be registered, so you will not receive notifications anymore. Please check the notifications settings of the app and the status of the push distributor."</string> <string name="notification_error_unified_push_unregistered_android">"The UnifiedPush notification distributor couldn\'t be registered, so you will not receive notifications anymore. Please check the notifications settings of the app and the status of the push distributor."</string>
<string name="notification_fallback_content">"You have new messages."</string> <string name="notification_fallback_content">"You have new messages."</string>
<plurals name="notification_fallback_n_content">
<item quantity="one">"You have %d new message."</item>
<item quantity="other">"You have %d new messages."</item>
</plurals>
<string name="notification_incoming_call">"📹 Incoming call"</string> <string name="notification_incoming_call">"📹 Incoming call"</string>
<string name="notification_inline_reply_failed">"** Failed to send - please open room"</string> <string name="notification_inline_reply_failed">"** Failed to send - please open room"</string>
<string name="notification_invitation_action_join">"Join"</string> <string name="notification_invitation_action_join">"Join"</string>

View file

@ -153,6 +153,19 @@ class DefaultActiveNotificationsProviderTest {
assertThat(activeNotificationsProvider.getSummaryNotification(A_SESSION_ID_2)).isNull() assertThat(activeNotificationsProvider.getSummaryNotification(A_SESSION_ID_2)).isNull()
} }
@Test
fun `getFallbackNotification returns only the fallback notification for that session id if it exists`() {
val activeNotifications = listOf(
aStatusBarNotification(id = notificationIdProvider.getFallbackNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
aStatusBarNotification(id = notificationIdProvider.getSummaryNotificationId(A_SESSION_ID), groupId = A_SESSION_ID.value),
aStatusBarNotification(id = notificationIdProvider.getRoomInvitationNotificationId(A_SESSION_ID_2), groupId = A_SESSION_ID_2.value),
)
val activeNotificationsProvider = createActiveNotificationsProvider(activeNotifications = activeNotifications)
assertThat(activeNotificationsProvider.getFallbackNotification(A_SESSION_ID)).isNotNull()
assertThat(activeNotificationsProvider.getFallbackNotification(A_SESSION_ID_2)).isNull()
}
private fun aStatusBarNotification(id: Int, groupId: String, tag: String? = null) = mockk<StatusBarNotification> { private fun aStatusBarNotification(id: Int, groupId: String, tag: String? = null) = mockk<StatusBarNotification> {
every { this@mockk.id } returns id every { this@mockk.id } returns id
every { this@mockk.tag } returns tag every { this@mockk.tag } returns tag

View file

@ -56,7 +56,6 @@ import io.element.android.libraries.push.impl.notifications.model.NotifiableMess
import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent import io.element.android.libraries.push.impl.notifications.model.ResolvedPushEvent
import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver
import io.element.android.services.toolbox.impl.strings.AndroidStringProvider import io.element.android.services.toolbox.impl.strings.AndroidStringProvider
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -663,7 +662,7 @@ class DefaultNotifiableEventResolverTest {
roomId = A_ROOM_ID, roomId = A_ROOM_ID,
eventId = AN_EVENT_ID, eventId = AN_EVENT_ID,
editedEventId = null, editedEventId = null,
description = "You have new messages.", description = "",
canBeReplaced = true, canBeReplaced = true,
isRedacted = false, isRedacted = false,
isUpdated = false, isUpdated = false,
@ -895,7 +894,6 @@ class DefaultNotifiableEventResolverTest {
callNotificationEventResolver = callNotificationEventResolver, callNotificationEventResolver = callNotificationEventResolver,
fallbackNotificationFactory = FallbackNotificationFactory( fallbackNotificationFactory = FallbackNotificationFactory(
clock = FakeSystemClock(), clock = FakeSystemClock(),
stringProvider = FakeStringProvider(defaultResult = "You have new messages.")
), ),
featureFlagService = FakeFeatureFlagService(), featureFlagService = FakeFeatureFlagService(),
) )

View file

@ -37,7 +37,6 @@ import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.NavigationState import io.element.android.services.appnavstate.api.NavigationState
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import io.element.android.services.appnavstate.test.aNavigationState import io.element.android.services.appnavstate.test.aNavigationState
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
@ -224,7 +223,6 @@ class DefaultNotificationDrawerManagerTest {
roomGroupMessageCreator = roomGroupMessageCreator, roomGroupMessageCreator = roomGroupMessageCreator,
summaryGroupMessageCreator = summaryGroupMessageCreator, summaryGroupMessageCreator = summaryGroupMessageCreator,
activeNotificationsProvider = activeNotificationsProvider, activeNotificationsProvider = activeNotificationsProvider,
stringProvider = FakeStringProvider(),
), ),
enterpriseService = enterpriseService, enterpriseService = enterpriseService,
sessionStore = sessionStore, sessionStore = sessionStore,

View file

@ -47,7 +47,6 @@ class DefaultSummaryGroupMessageCreatorTest {
), ),
invitationNotifications = emptyList(), invitationNotifications = emptyList(),
simpleNotifications = emptyList(), simpleNotifications = emptyList(),
fallbackNotifications = emptyList(),
) )
notificationCreator.createSummaryListNotificationResult.assertions() notificationCreator.createSummaryListNotificationResult.assertions()

View file

@ -23,7 +23,6 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGrou
import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.aSimpleNotifiableEvent
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -47,7 +46,6 @@ class NotificationDataFactoryTest {
roomGroupMessageCreator = fakeRoomGroupMessageCreator, roomGroupMessageCreator = fakeRoomGroupMessageCreator,
summaryGroupMessageCreator = fakeSummaryGroupMessageCreator, summaryGroupMessageCreator = fakeSummaryGroupMessageCreator,
activeNotificationsProvider = activeNotificationsProvider, activeNotificationsProvider = activeNotificationsProvider,
stringProvider = FakeStringProvider(),
) )
@Test @Test

View file

@ -29,7 +29,6 @@ import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNot
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.libraries.sessionstorage.api.SessionStore import io.element.android.libraries.sessionstorage.api.SessionStore
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -57,7 +56,6 @@ class NotificationRendererTest {
roomGroupMessageCreator = roomGroupMessageCreator, roomGroupMessageCreator = roomGroupMessageCreator,
summaryGroupMessageCreator = summaryGroupMessageCreator, summaryGroupMessageCreator = summaryGroupMessageCreator,
activeNotificationsProvider = FakeActiveNotificationsProvider(), activeNotificationsProvider = FakeActiveNotificationsProvider(),
stringProvider = FakeStringProvider(),
) )
private val notificationIdProvider = NotificationIdProvider private val notificationIdProvider = NotificationIdProvider

View file

@ -82,19 +82,22 @@ class DefaultNotificationCreatorTest {
fun `test createFallbackNotification`() { fun `test createFallbackNotification`() {
val sut = createNotificationCreator() val sut = createNotificationCreator()
val result = sut.createFallbackNotification( val result = sut.createFallbackNotification(
existingNotification = null,
notificationAccountParams = aNotificationAccountParams(), notificationAccountParams = aNotificationAccountParams(),
FallbackNotifiableEvent( fallbackNotifiableEvents = listOf(
sessionId = A_SESSION_ID, FallbackNotifiableEvent(
roomId = A_ROOM_ID, sessionId = A_SESSION_ID,
eventId = AN_EVENT_ID, roomId = A_ROOM_ID,
editedEventId = null, eventId = AN_EVENT_ID,
description = "description", editedEventId = null,
canBeReplaced = false, description = "description",
isRedacted = false, canBeReplaced = false,
isUpdated = false, isRedacted = false,
timestamp = A_FAKE_TIMESTAMP, isUpdated = false,
cause = null, timestamp = A_FAKE_TIMESTAMP,
), cause = null,
),
)
) )
result.commonAssertions( result.commonAssertions(
expectedCategory = null, expectedCategory = null,

View file

@ -21,6 +21,7 @@ class FakeActiveNotificationsProvider(
private val getMembershipNotificationForSessionResult: (SessionId) -> List<StatusBarNotification> = { emptyList() }, private val getMembershipNotificationForSessionResult: (SessionId) -> List<StatusBarNotification> = { emptyList() },
private val getMembershipNotificationForRoomResult: (SessionId, RoomId) -> List<StatusBarNotification> = { _, _ -> emptyList() }, private val getMembershipNotificationForRoomResult: (SessionId, RoomId) -> List<StatusBarNotification> = { _, _ -> emptyList() },
private val getSummaryNotificationResult: (SessionId) -> StatusBarNotification? = { null }, private val getSummaryNotificationResult: (SessionId) -> StatusBarNotification? = { null },
private val getFallbackNotificationResult: (SessionId) -> StatusBarNotification? = { null },
private val countResult: (SessionId) -> Int = { 0 }, private val countResult: (SessionId) -> Int = { 0 },
) : ActiveNotificationsProvider { ) : ActiveNotificationsProvider {
override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId, threadId: ThreadId?): List<StatusBarNotification> { override fun getMessageNotificationsForRoom(sessionId: SessionId, roomId: RoomId, threadId: ThreadId?): List<StatusBarNotification> {
@ -47,6 +48,10 @@ class FakeActiveNotificationsProvider(
return getSummaryNotificationResult(sessionId) return getSummaryNotificationResult(sessionId)
} }
override fun getFallbackNotification(sessionId: SessionId): StatusBarNotification? {
return getFallbackNotificationResult(sessionId)
}
override fun count(sessionId: SessionId): Int { override fun count(sessionId: SessionId): Int {
return countResult(sessionId) return countResult(sessionId)
} }

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiab
import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder
import io.element.android.tests.testutils.lambda.LambdaListAnyParamsRecorder import io.element.android.tests.testutils.lambda.LambdaListAnyParamsRecorder
import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder
import io.element.android.tests.testutils.lambda.LambdaThreeParamsRecorder
import io.element.android.tests.testutils.lambda.LambdaTwoParamsRecorder import io.element.android.tests.testutils.lambda.LambdaTwoParamsRecorder
import io.element.android.tests.testutils.lambda.lambdaAnyRecorder import io.element.android.tests.testutils.lambda.lambdaAnyRecorder
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
@ -34,8 +35,8 @@ class FakeNotificationCreator(
lambdaRecorder { _, _ -> A_NOTIFICATION }, lambdaRecorder { _, _ -> A_NOTIFICATION },
var createSimpleNotificationResult: LambdaTwoParamsRecorder<NotificationAccountParams, SimpleNotifiableEvent, Notification> = var createSimpleNotificationResult: LambdaTwoParamsRecorder<NotificationAccountParams, SimpleNotifiableEvent, Notification> =
lambdaRecorder { _, _ -> A_NOTIFICATION }, lambdaRecorder { _, _ -> A_NOTIFICATION },
var createFallbackNotificationResult: LambdaTwoParamsRecorder<NotificationAccountParams, FallbackNotifiableEvent, Notification> = var createFallbackNotificationResult: LambdaThreeParamsRecorder<Notification?, NotificationAccountParams, List<FallbackNotifiableEvent>, Notification> =
lambdaRecorder { _, _ -> A_NOTIFICATION }, lambdaRecorder { _, _, _ -> A_NOTIFICATION },
var createSummaryListNotificationResult: LambdaFiveParamsRecorder< var createSummaryListNotificationResult: LambdaFiveParamsRecorder<
NotificationAccountParams, String, Boolean, Long, NotificationAccountParams, Notification NotificationAccountParams, String, Boolean, Long, NotificationAccountParams, Notification
> = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION }, > = lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION },
@ -75,10 +76,15 @@ class FakeNotificationCreator(
} }
override fun createFallbackNotification( override fun createFallbackNotification(
existingNotification: Notification?,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
fallbackNotifiableEvent: FallbackNotifiableEvent, fallbackNotifiableEvents: List<FallbackNotifiableEvent>,
): Notification { ): Notification {
return createFallbackNotificationResult(notificationAccountParams, fallbackNotifiableEvent) return createFallbackNotificationResult(
existingNotification,
notificationAccountParams,
fallbackNotifiableEvents,
)
} }
override fun createSummaryListNotification( override fun createSummaryListNotification(

View file

@ -19,7 +19,7 @@ import io.element.android.libraries.push.impl.notifications.model.FallbackNotifi
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder import io.element.android.tests.testutils.lambda.LambdaFourParamsRecorder
import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder import io.element.android.tests.testutils.lambda.LambdaOneParamRecorder
import io.element.android.tests.testutils.lambda.LambdaThreeParamsRecorder import io.element.android.tests.testutils.lambda.LambdaThreeParamsRecorder
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
@ -28,18 +28,17 @@ class FakeNotificationDataFactory(
var messageEventToNotificationsResult: LambdaThreeParamsRecorder< var messageEventToNotificationsResult: LambdaThreeParamsRecorder<
List<NotifiableMessageEvent>, ImageLoader, NotificationAccountParams, List<RoomNotification> List<NotifiableMessageEvent>, ImageLoader, NotificationAccountParams, List<RoomNotification>
> = lambdaRecorder { _, _, _ -> emptyList() }, > = lambdaRecorder { _, _, _ -> emptyList() },
var summaryToNotificationsResult: LambdaFiveParamsRecorder< var summaryToNotificationsResult: LambdaFourParamsRecorder<
List<RoomNotification>, List<RoomNotification>,
List<OneShotNotification>, List<OneShotNotification>,
List<OneShotNotification>, List<OneShotNotification>,
List<OneShotNotification>,
NotificationAccountParams, NotificationAccountParams,
SummaryNotification SummaryNotification
> = lambdaRecorder { _, _, _, _, _ -> SummaryNotification.Update(A_NOTIFICATION) }, > = lambdaRecorder { _, _, _, _ -> SummaryNotification.Update(A_NOTIFICATION) },
var inviteToNotificationsResult: LambdaOneParamRecorder<List<InviteNotifiableEvent>, List<OneShotNotification>> = lambdaRecorder { _ -> emptyList() }, var inviteToNotificationsResult: LambdaOneParamRecorder<List<InviteNotifiableEvent>, List<OneShotNotification>> = lambdaRecorder { _ -> emptyList() },
var simpleEventToNotificationsResult: LambdaOneParamRecorder<List<SimpleNotifiableEvent>, List<OneShotNotification>> = lambdaRecorder { _ -> emptyList() }, var simpleEventToNotificationsResult: LambdaOneParamRecorder<List<SimpleNotifiableEvent>, List<OneShotNotification>> = lambdaRecorder { _ -> emptyList() },
var fallbackEventToNotificationsResult: LambdaOneParamRecorder<List<FallbackNotifiableEvent>, List<OneShotNotification>> = var fallbackEventToNotificationsResult: LambdaOneParamRecorder<List<FallbackNotifiableEvent>, OneShotNotification?> =
lambdaRecorder { _ -> emptyList() }, lambdaRecorder { _ -> null },
) : NotificationDataFactory { ) : NotificationDataFactory {
override suspend fun toNotifications( override suspend fun toNotifications(
messages: List<NotifiableMessageEvent>, messages: List<NotifiableMessageEvent>,
@ -69,10 +68,10 @@ class FakeNotificationDataFactory(
@JvmName("toNotificationFallbackEvents") @JvmName("toNotificationFallbackEvents")
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
override fun toNotifications( override fun toNotification(
fallback: List<FallbackNotifiableEvent>, fallback: List<FallbackNotifiableEvent>,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
): List<OneShotNotification> { ): OneShotNotification? {
return fallbackEventToNotificationsResult(fallback) return fallbackEventToNotificationsResult(fallback)
} }
@ -80,14 +79,12 @@ class FakeNotificationDataFactory(
roomNotifications: List<RoomNotification>, roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>, invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>, simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
): SummaryNotification { ): SummaryNotification {
return summaryToNotificationsResult( return summaryToNotificationsResult(
roomNotifications, roomNotifications,
invitationNotifications, invitationNotifications,
simpleNotifications, simpleNotifications,
fallbackNotifications,
notificationAccountParams, notificationAccountParams,
) )
} }

View file

@ -14,27 +14,25 @@ import io.element.android.libraries.push.impl.notifications.RoomNotification
import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageCreator import io.element.android.libraries.push.impl.notifications.SummaryGroupMessageCreator
import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams
import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION
import io.element.android.tests.testutils.lambda.LambdaFiveParamsRecorder import io.element.android.tests.testutils.lambda.LambdaFourParamsRecorder
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
class FakeSummaryGroupMessageCreator( class FakeSummaryGroupMessageCreator(
var createSummaryNotificationResult: LambdaFiveParamsRecorder< var createSummaryNotificationResult: LambdaFourParamsRecorder<
NotificationAccountParams, List<RoomNotification>, List<OneShotNotification>, List<OneShotNotification>, List<OneShotNotification>, Notification> = NotificationAccountParams, List<RoomNotification>, List<OneShotNotification>, List<OneShotNotification>, Notification> =
lambdaRecorder { _, _, _, _, _ -> A_NOTIFICATION } lambdaRecorder { _, _, _, _ -> A_NOTIFICATION }
) : SummaryGroupMessageCreator { ) : SummaryGroupMessageCreator {
override fun createSummaryNotification( override fun createSummaryNotification(
notificationAccountParams: NotificationAccountParams, notificationAccountParams: NotificationAccountParams,
roomNotifications: List<RoomNotification>, roomNotifications: List<RoomNotification>,
invitationNotifications: List<OneShotNotification>, invitationNotifications: List<OneShotNotification>,
simpleNotifications: List<OneShotNotification>, simpleNotifications: List<OneShotNotification>,
fallbackNotifications: List<OneShotNotification>,
): Notification { ): Notification {
return createSummaryNotificationResult( return createSummaryNotificationResult(
notificationAccountParams, notificationAccountParams,
roomNotifications, roomNotifications,
invitationNotifications, invitationNotifications,
simpleNotifications, simpleNotifications,
fallbackNotifications,
) )
} }
} }

View file

@ -57,7 +57,6 @@ import io.element.android.libraries.pushstore.test.userpushstore.clientsecret.Fa
import io.element.android.libraries.workmanager.api.WorkManagerRequest import io.element.android.libraries.workmanager.api.WorkManagerRequest
import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler import io.element.android.libraries.workmanager.test.FakeWorkManagerScheduler
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
import io.element.android.services.toolbox.test.strings.FakeStringProvider
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
import io.element.android.tests.testutils.lambda.any import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaError
@ -724,7 +723,6 @@ class DefaultPushHandlerTest {
appCoroutineScope = backgroundScope, appCoroutineScope = backgroundScope,
fallbackNotificationFactory = FallbackNotificationFactory( fallbackNotificationFactory = FallbackNotificationFactory(
clock = FakeSystemClock(), clock = FakeSystemClock(),
stringProvider = FakeStringProvider(),
), ),
syncOnNotifiableEvent = syncOnNotifiableEvent, syncOnNotifiableEvent = syncOnNotifiableEvent,
featureFlagService = featureFlagService, featureFlagService = featureFlagService,