mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Ensure that noisy notifications are still noisy when content is hidden when a PIN code is protecting the application.
Closes #7575
This commit is contained in:
+2
-1
@@ -30,6 +30,7 @@ fun aNotificationData(
|
|||||||
senderIsNameAmbiguous: Boolean = false,
|
senderIsNameAmbiguous: Boolean = false,
|
||||||
roomDisplayName: String? = A_ROOM_NAME,
|
roomDisplayName: String? = A_ROOM_NAME,
|
||||||
roomJoinRule: JoinRule? = null,
|
roomJoinRule: JoinRule? = null,
|
||||||
|
isNoisy: Boolean = false,
|
||||||
): NotificationData {
|
): NotificationData {
|
||||||
return NotificationData(
|
return NotificationData(
|
||||||
sessionId = A_SESSION_ID,
|
sessionId = A_SESSION_ID,
|
||||||
@@ -45,7 +46,7 @@ fun aNotificationData(
|
|||||||
isDm = false,
|
isDm = false,
|
||||||
isSpace = isSpace,
|
isSpace = isSpace,
|
||||||
isEncrypted = false,
|
isEncrypted = false,
|
||||||
isNoisy = false,
|
isNoisy = isNoisy,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
content = content,
|
content = content,
|
||||||
hasMention = hasMention,
|
hasMention = hasMention,
|
||||||
|
|||||||
+1
@@ -298,6 +298,7 @@ class DefaultNotifiableEventResolver(
|
|||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
cause = "Unable to decrypt event content",
|
cause = "Unable to decrypt event content",
|
||||||
|
noisy = isNoisy,
|
||||||
)
|
)
|
||||||
ResolvedPushEvent.Event(fallbackNotifiableEvent)
|
ResolvedPushEvent.Event(fallbackNotifiableEvent)
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -274,6 +274,12 @@ private fun NotifiableEvent.toFallbackNotifiableEvent(): FallbackNotifiableEvent
|
|||||||
is FallbackNotifiableEvent -> timestamp
|
is FallbackNotifiableEvent -> timestamp
|
||||||
is NotifiableRingingCallEvent -> return null
|
is NotifiableRingingCallEvent -> return null
|
||||||
}
|
}
|
||||||
|
val noisy = when (this) {
|
||||||
|
is NotifiableMessageEvent -> noisy
|
||||||
|
is InviteNotifiableEvent -> noisy
|
||||||
|
is SimpleNotifiableEvent -> noisy
|
||||||
|
is FallbackNotifiableEvent -> noisy
|
||||||
|
}
|
||||||
return FallbackNotifiableEvent(
|
return FallbackNotifiableEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
@@ -283,6 +289,7 @@ private fun NotifiableEvent.toFallbackNotifiableEvent(): FallbackNotifiableEvent
|
|||||||
canBeReplaced = false,
|
canBeReplaced = false,
|
||||||
isRedacted = false,
|
isRedacted = false,
|
||||||
isUpdated = false,
|
isUpdated = false,
|
||||||
|
noisy = noisy,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
cause = null,
|
cause = null,
|
||||||
)
|
)
|
||||||
|
|||||||
+2
@@ -24,6 +24,7 @@ class FallbackNotificationFactory(
|
|||||||
roomId: RoomId,
|
roomId: RoomId,
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
cause: String?,
|
cause: String?,
|
||||||
|
noisy: Boolean,
|
||||||
): FallbackNotifiableEvent = FallbackNotifiableEvent(
|
): FallbackNotifiableEvent = FallbackNotifiableEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
@@ -32,6 +33,7 @@ class FallbackNotificationFactory(
|
|||||||
canBeReplaced = true,
|
canBeReplaced = true,
|
||||||
isRedacted = false,
|
isRedacted = false,
|
||||||
isUpdated = false,
|
isUpdated = false,
|
||||||
|
noisy = noisy,
|
||||||
timestamp = clock.epochMillis(),
|
timestamp = clock.epochMillis(),
|
||||||
description = "",
|
description = "",
|
||||||
cause = cause,
|
cause = cause,
|
||||||
|
|||||||
+1
@@ -170,6 +170,7 @@ class DefaultNotificationResultProcessor(
|
|||||||
roomId = RoomId(request.roomId),
|
roomId = RoomId(request.roomId),
|
||||||
eventId = EventId(request.eventId),
|
eventId = EventId(request.eventId),
|
||||||
cause = exception.message,
|
cause = exception.message,
|
||||||
|
noisy = false,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -317,7 +317,7 @@ class DefaultNotificationCreator(
|
|||||||
val fallbackNotifiableEvent = fallbackNotifiableEvents.first()
|
val fallbackNotifiableEvent = fallbackNotifiableEvents.first()
|
||||||
val channelId = notificationChannels.getChannelIdForMessage(
|
val channelId = notificationChannels.getChannelIdForMessage(
|
||||||
sessionId = fallbackNotifiableEvent.sessionId,
|
sessionId = fallbackNotifiableEvent.sessionId,
|
||||||
noisy = false,
|
noisy = fallbackNotifiableEvents.any { it.noisy },
|
||||||
)
|
)
|
||||||
val existingCounter = existingNotification
|
val existingCounter = existingNotification
|
||||||
?.extras
|
?.extras
|
||||||
|
|||||||
+1
@@ -25,6 +25,7 @@ data class FallbackNotifiableEvent(
|
|||||||
override val canBeReplaced: Boolean,
|
override val canBeReplaced: Boolean,
|
||||||
override val isRedacted: Boolean,
|
override val isRedacted: Boolean,
|
||||||
override val isUpdated: Boolean,
|
override val isUpdated: Boolean,
|
||||||
|
val noisy: Boolean,
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
val cause: String?,
|
val cause: String?,
|
||||||
) : NotifiableEvent
|
) : NotifiableEvent
|
||||||
|
|||||||
+35
@@ -712,6 +712,41 @@ class DefaultNotifiableEventResolverTest : RobolectricTest() {
|
|||||||
canBeReplaced = true,
|
canBeReplaced = true,
|
||||||
isRedacted = false,
|
isRedacted = false,
|
||||||
isUpdated = false,
|
isUpdated = false,
|
||||||
|
noisy = false,
|
||||||
|
timestamp = A_FAKE_TIMESTAMP,
|
||||||
|
cause = "Unable to decrypt event content",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(result.getEvent(request)).isEqualTo(Result.success(expectedResult))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve RoomEncrypted noisy`() = runTest {
|
||||||
|
val sut = createDefaultNotifiableEventResolver(
|
||||||
|
notificationResult = Result.success(
|
||||||
|
mapOf(
|
||||||
|
AN_EVENT_ID to Result.success(
|
||||||
|
aNotificationData(
|
||||||
|
content = NotificationContent.MessageLike.RoomEncrypted,
|
||||||
|
isNoisy = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val request = aPushRequest(A_SESSION_ID, A_ROOM_ID, AN_EVENT_ID, "firebase")
|
||||||
|
val result = sut.resolveEvents(A_SESSION_ID, listOf(request))
|
||||||
|
val expectedResult = ResolvedPushEvent.Event(
|
||||||
|
FallbackNotifiableEvent(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
editedEventId = null,
|
||||||
|
description = "",
|
||||||
|
canBeReplaced = true,
|
||||||
|
isRedacted = false,
|
||||||
|
isUpdated = false,
|
||||||
|
noisy = true,
|
||||||
timestamp = A_FAKE_TIMESTAMP,
|
timestamp = A_FAKE_TIMESTAMP,
|
||||||
cause = "Unable to decrypt event content",
|
cause = "Unable to decrypt event content",
|
||||||
)
|
)
|
||||||
|
|||||||
+142
-1
@@ -15,28 +15,35 @@ import io.element.android.features.enterprise.api.EnterpriseService
|
|||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.features.lockscreen.api.LockScreenService
|
import io.element.android.features.lockscreen.api.LockScreenService
|
||||||
import io.element.android.features.lockscreen.test.FakeLockScreenService
|
import io.element.android.features.lockscreen.test.FakeLockScreenService
|
||||||
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
import io.element.android.libraries.matrix.test.A_ROOM_ID_2
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||||
import io.element.android.libraries.matrix.test.A_THREAD_ID_2
|
import io.element.android.libraries.matrix.test.A_THREAD_ID_2
|
||||||
|
import io.element.android.libraries.matrix.test.A_TIMESTAMP
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
|
||||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder
|
import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder
|
||||||
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
|
||||||
|
import io.element.android.libraries.push.impl.notifications.factories.NotificationAccountParams
|
||||||
|
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams
|
import io.element.android.libraries.push.impl.notifications.factories.aNotificationAccountParams
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
|
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
|
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationCreator
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
|
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
|
import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMessageCreator
|
||||||
import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator
|
import io.element.android.libraries.push.impl.notifications.fake.FakeSummaryGroupMessageCreator
|
||||||
|
import io.element.android.libraries.push.impl.notifications.fixtures.A_NOTIFICATION
|
||||||
import io.element.android.libraries.push.impl.notifications.fixtures.aFallbackNotifiableEvent
|
import io.element.android.libraries.push.impl.notifications.fixtures.aFallbackNotifiableEvent
|
||||||
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.libraries.push.impl.notifications.model.FallbackNotifiableEvent
|
||||||
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.api.observer.SessionObserver
|
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
|
||||||
@@ -48,6 +55,8 @@ import io.element.android.services.appnavstate.api.AppNavigationStateService
|
|||||||
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.appnavstate.test.anAppNavigationState
|
import io.element.android.services.appnavstate.test.anAppNavigationState
|
||||||
|
import io.element.android.services.toolbox.test.systemclock.A_FAKE_TIMESTAMP
|
||||||
|
import io.element.android.tests.testutils.lambda.LambdaThreeParamsRecorder
|
||||||
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
|
||||||
@@ -472,6 +481,137 @@ class DefaultNotificationDrawerManagerTest {
|
|||||||
extraInvocationsForNotificationSummary = 1,
|
extraInvocationsForNotificationSummary = 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, a noisy message event produces a noisy fallback notification`() = testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent = aNotifiableMessageEvent(noisy = true),
|
||||||
|
expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = true, timestamp = A_TIMESTAMP),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, a silent message event produces a silent fallback notification`() = testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent = aNotifiableMessageEvent(noisy = false),
|
||||||
|
expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = false, timestamp = A_TIMESTAMP),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, a noisy invite event produces a noisy fallback notification`() = testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent = anInviteNotifiableEvent(noisy = true),
|
||||||
|
expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = true, timestamp = 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, a silent invite event produces a silent fallback notification`() = testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent = anInviteNotifiableEvent(noisy = false),
|
||||||
|
expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = false, timestamp = 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, a noisy simple event produces a noisy fallback notification`() = testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent = aSimpleNotifiableEvent(noisy = true),
|
||||||
|
expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = true, timestamp = 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, a silent simple event produces a silent fallback notification`() = testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent = aSimpleNotifiableEvent(noisy = false),
|
||||||
|
expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = false, timestamp = 0),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, a noisy fallback event stays noisy`() = testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent = aFallbackNotifiableEvent(noisy = true),
|
||||||
|
expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = true, timestamp = A_FAKE_TIMESTAMP),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, a silent fallback event stays silent`() = testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent = aFallbackNotifiableEvent(noisy = false),
|
||||||
|
expectedFallbackEvent = anExpectedFallbackNotifiableEvent(noisy = false, timestamp = A_FAKE_TIMESTAMP),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is locked, all the events are grouped into a single fallback notification`() = runTest {
|
||||||
|
val createFallbackNotificationResult = aCreateFallbackNotificationRecorder()
|
||||||
|
val sut = createAppLockedNotificationDrawerManager(createFallbackNotificationResult)
|
||||||
|
sut.onNotifiableEventsReceived(
|
||||||
|
listOf(
|
||||||
|
aNotifiableMessageEvent(noisy = false),
|
||||||
|
aNotifiableMessageEvent(eventId = AN_EVENT_ID_2, noisy = true),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
createFallbackNotificationResult.assertions()
|
||||||
|
.isCalledOnce()
|
||||||
|
.with(
|
||||||
|
value(null),
|
||||||
|
any(),
|
||||||
|
value(
|
||||||
|
listOf(
|
||||||
|
anExpectedFallbackNotifiableEvent(noisy = false, timestamp = A_TIMESTAMP),
|
||||||
|
anExpectedFallbackNotifiableEvent(eventId = AN_EVENT_ID_2, noisy = true, timestamp = A_TIMESTAMP),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when the app is not locked, no fallback notification is created`() = runTest {
|
||||||
|
val createFallbackNotificationResult = aCreateFallbackNotificationRecorder()
|
||||||
|
val sut = createDefaultNotificationDrawerManager(
|
||||||
|
appNavigationStateService = FakeAppNavigationStateService(
|
||||||
|
initialAppNavigationState = anAppNavigationState(isInForeground = false),
|
||||||
|
),
|
||||||
|
notificationCreator = FakeNotificationCreator(
|
||||||
|
createFallbackNotificationResult = createFallbackNotificationResult,
|
||||||
|
),
|
||||||
|
lockScreenService = FakeLockScreenService().apply { setIsPinSetup(false) },
|
||||||
|
)
|
||||||
|
sut.onNotifiableEventsReceived(listOf(aNotifiableMessageEvent(noisy = true)))
|
||||||
|
createFallbackNotificationResult.assertions().isNeverCalled()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content of the original event must be stripped, only the noisy flag, the ids and the timestamp are kept.
|
||||||
|
*/
|
||||||
|
private fun anExpectedFallbackNotifiableEvent(
|
||||||
|
eventId: EventId = AN_EVENT_ID,
|
||||||
|
noisy: Boolean,
|
||||||
|
timestamp: Long,
|
||||||
|
) = aFallbackNotifiableEvent(
|
||||||
|
eventId = eventId,
|
||||||
|
description = null,
|
||||||
|
canBeReplaced = false,
|
||||||
|
noisy = noisy,
|
||||||
|
timestamp = timestamp,
|
||||||
|
cause = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun aCreateFallbackNotificationRecorder() =
|
||||||
|
lambdaRecorder<Notification?, NotificationAccountParams, List<FallbackNotifiableEvent>, Notification> { _, _, _ -> A_NOTIFICATION }
|
||||||
|
|
||||||
|
private fun TestScope.createAppLockedNotificationDrawerManager(
|
||||||
|
createFallbackNotificationResult: LambdaThreeParamsRecorder<Notification?, NotificationAccountParams, List<FallbackNotifiableEvent>, Notification>,
|
||||||
|
) = createDefaultNotificationDrawerManager(
|
||||||
|
appNavigationStateService = FakeAppNavigationStateService(
|
||||||
|
initialAppNavigationState = anAppNavigationState(isInForeground = false),
|
||||||
|
),
|
||||||
|
notificationCreator = FakeNotificationCreator(
|
||||||
|
createFallbackNotificationResult = createFallbackNotificationResult,
|
||||||
|
),
|
||||||
|
lockScreenService = FakeLockScreenService().apply { setIsPinSetup(true) },
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun testAppLockedNotifiableEventReceived(
|
||||||
|
notifiableEvent: NotifiableEvent,
|
||||||
|
expectedFallbackEvent: FallbackNotifiableEvent,
|
||||||
|
) = runTest {
|
||||||
|
val createFallbackNotificationResult = aCreateFallbackNotificationRecorder()
|
||||||
|
val sut = createAppLockedNotificationDrawerManager(createFallbackNotificationResult)
|
||||||
|
sut.onNotifiableEventsReceived(listOf(notifiableEvent))
|
||||||
|
createFallbackNotificationResult.assertions()
|
||||||
|
.isCalledOnce()
|
||||||
|
.with(value(null), any(), value(listOf(expectedFallbackEvent)))
|
||||||
|
}
|
||||||
|
|
||||||
private fun testOnNotifiableEventReceived(
|
private fun testOnNotifiableEventReceived(
|
||||||
appNavigationState: AppNavigationState,
|
appNavigationState: AppNavigationState,
|
||||||
notifiableEvents: List<NotifiableEvent>,
|
notifiableEvents: List<NotifiableEvent>,
|
||||||
@@ -503,6 +643,7 @@ class DefaultNotificationDrawerManagerTest {
|
|||||||
fun TestScope.createDefaultNotificationDrawerManager(
|
fun TestScope.createDefaultNotificationDrawerManager(
|
||||||
notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(),
|
notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(),
|
||||||
notificationRenderer: NotificationRenderer? = null,
|
notificationRenderer: NotificationRenderer? = null,
|
||||||
|
notificationCreator: NotificationCreator = FakeNotificationCreator(),
|
||||||
appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(),
|
appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(),
|
||||||
roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(),
|
roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(),
|
||||||
summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(),
|
summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(),
|
||||||
@@ -519,7 +660,7 @@ fun TestScope.createDefaultNotificationDrawerManager(
|
|||||||
notificationRenderer = notificationRenderer ?: NotificationRenderer(
|
notificationRenderer = notificationRenderer ?: NotificationRenderer(
|
||||||
notificationDisplayer = notificationDisplayer,
|
notificationDisplayer = notificationDisplayer,
|
||||||
notificationDataFactory = DefaultNotificationDataFactory(
|
notificationDataFactory = DefaultNotificationDataFactory(
|
||||||
notificationCreator = FakeNotificationCreator(),
|
notificationCreator = notificationCreator,
|
||||||
roomGroupMessageCreator = roomGroupMessageCreator,
|
roomGroupMessageCreator = roomGroupMessageCreator,
|
||||||
summaryGroupMessageCreator = summaryGroupMessageCreator,
|
summaryGroupMessageCreator = summaryGroupMessageCreator,
|
||||||
activeNotificationsProvider = activeNotificationsProvider,
|
activeNotificationsProvider = activeNotificationsProvider,
|
||||||
|
|||||||
+36
@@ -267,6 +267,42 @@ class DefaultNotificationResultProcessorTest {
|
|||||||
assertThat(receivedFallbackEvent).isTrue()
|
assertThat(receivedFallbackEvent).isTrue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when an event cannot be resolved, the generated fallback event is silent`() = runTest {
|
||||||
|
val onNotifiableEventsReceived = lambdaRecorder<List<NotifiableEvent>, Unit> {}
|
||||||
|
val onPushReceivedResult = lambdaRecorder<String, EventId?, RoomId?, SessionId?, Boolean, Boolean, String?, Unit> { _, _, _, _, _, _, _ -> }
|
||||||
|
val processor = createDefaultNotificationResultProcessor(
|
||||||
|
mutableBatteryOptimizationStore = FakeMutableBatteryOptimizationStore(
|
||||||
|
showBatteryOptimizationBannerResult = lambdaRecorder<Unit> {},
|
||||||
|
),
|
||||||
|
pushHistoryService = FakePushHistoryService(
|
||||||
|
onPushReceivedResult = onPushReceivedResult,
|
||||||
|
),
|
||||||
|
onNotifiableEventsReceived = onNotifiableEventsReceived,
|
||||||
|
)
|
||||||
|
|
||||||
|
runningProcessor(processor) {
|
||||||
|
emit(mapOf(aPushRequest() to Result.failure(NotificationResolverException.UnknownError("Unable to resolve event"))))
|
||||||
|
}
|
||||||
|
|
||||||
|
advanceTimeBy(300.milliseconds)
|
||||||
|
|
||||||
|
onNotifiableEventsReceived.assertions()
|
||||||
|
.isCalledOnce()
|
||||||
|
.with(
|
||||||
|
value(
|
||||||
|
listOf(
|
||||||
|
aFallbackNotifiableEvent(
|
||||||
|
description = "",
|
||||||
|
canBeReplaced = true,
|
||||||
|
noisy = false,
|
||||||
|
cause = "Unable to resolve event",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun TestScope.runningProcessor(processor: NotificationResultProcessor, block: suspend NotificationResultProcessor.() -> Unit) {
|
private suspend fun TestScope.runningProcessor(processor: NotificationResultProcessor, block: suspend NotificationResultProcessor.() -> Unit) {
|
||||||
processor.start()
|
processor.start()
|
||||||
|
|
||||||
|
|||||||
+58
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.push.impl.notifications
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
import io.element.android.libraries.push.impl.notifications.fixtures.aFallbackNotifiableEvent
|
||||||
|
import io.element.android.services.toolbox.test.systemclock.FakeSystemClock
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class FallbackNotificationFactoryTest {
|
||||||
|
@Test
|
||||||
|
fun `create silent event`() {
|
||||||
|
val sut = FallbackNotificationFactory(FakeSystemClock())
|
||||||
|
val result = sut.create(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
cause = "A cause",
|
||||||
|
noisy = false,
|
||||||
|
)
|
||||||
|
assertThat(result).isEqualTo(
|
||||||
|
aFallbackNotifiableEvent(
|
||||||
|
description = "",
|
||||||
|
canBeReplaced = true,
|
||||||
|
noisy = false,
|
||||||
|
cause = "A cause",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `create noisy event`() {
|
||||||
|
val sut = FallbackNotificationFactory(FakeSystemClock())
|
||||||
|
val result = sut.create(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID,
|
||||||
|
eventId = AN_EVENT_ID,
|
||||||
|
cause = "A cause",
|
||||||
|
noisy = true,
|
||||||
|
)
|
||||||
|
assertThat(result).isEqualTo(
|
||||||
|
aFallbackNotifiableEvent(
|
||||||
|
description = "",
|
||||||
|
canBeReplaced = true,
|
||||||
|
noisy = true,
|
||||||
|
cause = "A cause",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+54
-1
@@ -18,6 +18,7 @@ import io.element.android.appconfig.NotificationConfig
|
|||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
import io.element.android.libraries.core.meta.BuildMeta
|
||||||
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
|
||||||
import io.element.android.libraries.matrix.test.A_COLOR_INT
|
import io.element.android.libraries.matrix.test.A_COLOR_INT
|
||||||
@@ -33,6 +34,7 @@ import io.element.android.libraries.push.impl.notifications.DefaultNotificationB
|
|||||||
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
|
import io.element.android.libraries.push.impl.notifications.NotificationActionIds
|
||||||
import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo
|
import io.element.android.libraries.push.impl.notifications.RoomEventGroupInfo
|
||||||
import io.element.android.libraries.push.impl.notifications.channels.DefaultNotificationChannels
|
import io.element.android.libraries.push.impl.notifications.channels.DefaultNotificationChannels
|
||||||
|
import io.element.android.libraries.push.impl.notifications.channels.FakeNotificationChannels
|
||||||
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
|
import io.element.android.libraries.push.impl.notifications.channels.NotificationChannels
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.action.AcceptInvitationActionFactory
|
import io.element.android.libraries.push.impl.notifications.factories.action.AcceptInvitationActionFactory
|
||||||
import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory
|
import io.element.android.libraries.push.impl.notifications.factories.action.MarkAsReadActionFactory
|
||||||
@@ -47,6 +49,8 @@ import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvid
|
|||||||
import io.element.android.services.toolbox.test.strings.FakeStringProvider
|
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 io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.lambda.value
|
||||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -82,7 +86,10 @@ class DefaultNotificationCreatorTest : RobolectricTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test createFallbackNotification`() {
|
fun `test createFallbackNotification`() {
|
||||||
val sut = createNotificationCreator()
|
val channelIdForMessage = lambdaRecorder<SessionId, Boolean, String> { _, _ -> A_CHANNEL_ID }
|
||||||
|
val sut = createNotificationCreator(
|
||||||
|
notificationChannels = FakeNotificationChannels(channelIdForMessage = channelIdForMessage),
|
||||||
|
)
|
||||||
val result = sut.createFallbackNotification(
|
val result = sut.createFallbackNotification(
|
||||||
existingNotification = null,
|
existingNotification = null,
|
||||||
notificationAccountParams = aNotificationAccountParams(),
|
notificationAccountParams = aNotificationAccountParams(),
|
||||||
@@ -93,6 +100,50 @@ class DefaultNotificationCreatorTest : RobolectricTest() {
|
|||||||
result.commonAssertions(
|
result.commonAssertions(
|
||||||
expectedCategory = null,
|
expectedCategory = null,
|
||||||
)
|
)
|
||||||
|
assertThat(result.channelId).isEqualTo(A_CHANNEL_ID)
|
||||||
|
// The notification must be posted on the silent channel.
|
||||||
|
channelIdForMessage.assertions().isCalledOnce().with(value(A_SESSION_ID), value(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test createFallbackNotification noisy`() {
|
||||||
|
val channelIdForMessage = lambdaRecorder<SessionId, Boolean, String> { _, _ -> A_CHANNEL_ID }
|
||||||
|
val sut = createNotificationCreator(
|
||||||
|
notificationChannels = FakeNotificationChannels(channelIdForMessage = channelIdForMessage),
|
||||||
|
)
|
||||||
|
val result = sut.createFallbackNotification(
|
||||||
|
existingNotification = null,
|
||||||
|
notificationAccountParams = aNotificationAccountParams(),
|
||||||
|
fallbackNotifiableEvents = listOf(
|
||||||
|
aFallbackNotifiableEvent(noisy = true),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.commonAssertions(
|
||||||
|
expectedCategory = null,
|
||||||
|
)
|
||||||
|
assertThat(result.channelId).isEqualTo(A_CHANNEL_ID)
|
||||||
|
// The notification must be posted on the noisy channel.
|
||||||
|
channelIdForMessage.assertions().isCalledOnce().with(value(A_SESSION_ID), value(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test createFallbackNotification is noisy when at least one event is noisy`() {
|
||||||
|
val channelIdForMessage = lambdaRecorder<SessionId, Boolean, String> { _, _ -> A_CHANNEL_ID }
|
||||||
|
val sut = createNotificationCreator(
|
||||||
|
notificationChannels = FakeNotificationChannels(channelIdForMessage = channelIdForMessage),
|
||||||
|
)
|
||||||
|
val result = sut.createFallbackNotification(
|
||||||
|
existingNotification = null,
|
||||||
|
notificationAccountParams = aNotificationAccountParams(),
|
||||||
|
fallbackNotifiableEvents = listOf(
|
||||||
|
aFallbackNotifiableEvent(noisy = false),
|
||||||
|
aFallbackNotifiableEvent(eventId = AN_EVENT_ID_2, noisy = true),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
result.commonAssertions(
|
||||||
|
expectedCategory = null,
|
||||||
|
)
|
||||||
|
channelIdForMessage.assertions().isCalledOnce().with(value(A_SESSION_ID), value(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -389,6 +440,8 @@ class DefaultNotificationCreatorTest : RobolectricTest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val A_CHANNEL_ID = "aChannelId"
|
||||||
|
|
||||||
const val MARK_AS_READ_ACTION_TITLE = "MarkAsReadAction"
|
const val MARK_AS_READ_ACTION_TITLE = "MarkAsReadAction"
|
||||||
const val QUICK_REPLY_ACTION_TITLE = "QuickReplyAction"
|
const val QUICK_REPLY_ACTION_TITLE = "QuickReplyAction"
|
||||||
const val ACCEPT_INVITATION_ACTION_TITLE = "AcceptInvitationAction"
|
const val ACCEPT_INVITATION_ACTION_TITLE = "AcceptInvitationAction"
|
||||||
|
|||||||
+22
-11
@@ -39,13 +39,14 @@ fun aSimpleNotifiableEvent(
|
|||||||
type: String? = null,
|
type: String? = null,
|
||||||
isRedacted: Boolean = false,
|
isRedacted: Boolean = false,
|
||||||
canBeReplaced: Boolean = false,
|
canBeReplaced: Boolean = false,
|
||||||
editedEventId: EventId? = null
|
editedEventId: EventId? = null,
|
||||||
|
noisy: Boolean = false,
|
||||||
) = SimpleNotifiableEvent(
|
) = SimpleNotifiableEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
editedEventId = editedEventId,
|
editedEventId = editedEventId,
|
||||||
noisy = false,
|
noisy = noisy,
|
||||||
title = "title",
|
title = "title",
|
||||||
description = "description",
|
description = "description",
|
||||||
type = type,
|
type = type,
|
||||||
@@ -59,14 +60,15 @@ fun anInviteNotifiableEvent(
|
|||||||
sessionId: SessionId = A_SESSION_ID,
|
sessionId: SessionId = A_SESSION_ID,
|
||||||
roomId: RoomId = A_ROOM_ID,
|
roomId: RoomId = A_ROOM_ID,
|
||||||
eventId: EventId = AN_EVENT_ID,
|
eventId: EventId = AN_EVENT_ID,
|
||||||
isRedacted: Boolean = false
|
isRedacted: Boolean = false,
|
||||||
|
noisy: Boolean = false,
|
||||||
) = InviteNotifiableEvent(
|
) = InviteNotifiableEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
roomName = A_ROOM_NAME,
|
roomName = A_ROOM_NAME,
|
||||||
editedEventId = null,
|
editedEventId = null,
|
||||||
noisy = false,
|
noisy = noisy,
|
||||||
title = "title",
|
title = "title",
|
||||||
description = "description",
|
description = "description",
|
||||||
type = null,
|
type = null,
|
||||||
@@ -89,11 +91,12 @@ fun aNotifiableMessageEvent(
|
|||||||
senderId: UserId = A_USER_ID_2,
|
senderId: UserId = A_USER_ID_2,
|
||||||
senderDisambiguatedDisplayName: String = A_USER_NAME_2,
|
senderDisambiguatedDisplayName: String = A_USER_NAME_2,
|
||||||
roomName: String? = A_ROOM_NAME,
|
roomName: String? = A_ROOM_NAME,
|
||||||
|
noisy: Boolean = false,
|
||||||
) = NotifiableMessageEvent(
|
) = NotifiableMessageEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
editedEventId = null,
|
editedEventId = null,
|
||||||
noisy = false,
|
noisy = noisy,
|
||||||
timestamp = timestamp,
|
timestamp = timestamp,
|
||||||
senderDisambiguatedDisplayName = senderDisambiguatedDisplayName,
|
senderDisambiguatedDisplayName = senderDisambiguatedDisplayName,
|
||||||
senderId = senderId,
|
senderId = senderId,
|
||||||
@@ -149,15 +152,23 @@ fun aNotifiableCallEvent(
|
|||||||
|
|
||||||
fun aFallbackNotifiableEvent(
|
fun aFallbackNotifiableEvent(
|
||||||
sessionId: SessionId = A_SESSION_ID,
|
sessionId: SessionId = A_SESSION_ID,
|
||||||
|
roomId: RoomId = A_ROOM_ID,
|
||||||
|
eventId: EventId = AN_EVENT_ID,
|
||||||
|
description: String? = "A fallback notification",
|
||||||
|
canBeReplaced: Boolean = false,
|
||||||
|
noisy: Boolean = false,
|
||||||
|
timestamp: Long = A_FAKE_TIMESTAMP,
|
||||||
|
cause: String? = "Unable to decrypt event",
|
||||||
) = FallbackNotifiableEvent(
|
) = FallbackNotifiableEvent(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
roomId = A_ROOM_ID,
|
roomId = roomId,
|
||||||
eventId = AN_EVENT_ID,
|
eventId = eventId,
|
||||||
editedEventId = null,
|
editedEventId = null,
|
||||||
description = "A fallback notification",
|
description = description,
|
||||||
canBeReplaced = false,
|
canBeReplaced = canBeReplaced,
|
||||||
isRedacted = false,
|
isRedacted = false,
|
||||||
isUpdated = false,
|
isUpdated = false,
|
||||||
timestamp = A_FAKE_TIMESTAMP,
|
noisy = noisy,
|
||||||
cause = "Unable to decrypt event",
|
timestamp = timestamp,
|
||||||
|
cause = cause,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user