Merge pull request #6190 from element-hq/feature/bma/fallbackNotificationCleanup

Fallback notification cleanup
This commit is contained in:
Benoit Marty 2026-02-12 19:00:39 +01:00 committed by GitHub
commit 17cf0efd13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 375 additions and 73 deletions

View file

@ -1238,7 +1238,7 @@ class MessagesPresenterTest {
}
@Test
fun `present - shows a "world_readable" icon if the room is encrypted and history is world_readable`() = runTest {
fun `present - shows a 'world_readable' icon if the room is encrypted and history is world_readable`() = runTest {
val presenter = createMessagesPresenter(
joinedRoom = FakeJoinedRoom(
baseRoom = FakeBaseRoom(

View file

@ -22,12 +22,20 @@ import io.element.android.libraries.matrix.ui.media.ImageLoaderHolder
import io.element.android.libraries.push.api.notifications.NotificationCleaner
import io.element.android.libraries.push.api.notifications.NotificationIdProvider
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
import io.element.android.libraries.push.impl.notifications.model.FallbackNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.InviteNotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableEvent
import io.element.android.libraries.push.impl.notifications.model.shouldIgnoreEventInRoom
import io.element.android.libraries.push.impl.notifications.model.NotifiableMessageEvent
import io.element.android.libraries.push.impl.notifications.model.NotifiableRingingCallEvent
import io.element.android.libraries.push.impl.notifications.model.SimpleNotifiableEvent
import io.element.android.libraries.sessionstorage.api.observer.SessionListener
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.NavigationState
import io.element.android.services.appnavstate.api.currentRoomId
import io.element.android.services.appnavstate.api.currentSessionId
import io.element.android.services.appnavstate.api.currentThreadId
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -71,7 +79,10 @@ class DefaultNotificationDrawerManager(
private fun onAppNavigationStateChange(navigationState: NavigationState) {
when (navigationState) {
NavigationState.Root -> {}
is NavigationState.Session -> {}
is NavigationState.Session -> {
// Cleanup the fallback notification
clearFallbackForSession(navigationState.sessionId)
}
is NavigationState.Room -> {
// Cleanup notification for current room
clearMessagesForRoom(
@ -94,14 +105,11 @@ class DefaultNotificationDrawerManager(
* Events might be grouped and there might not be one notification per event!
*/
suspend fun onNotifiableEventReceived(notifiableEvent: NotifiableEvent) {
if (notifiableEvent.shouldIgnoreEventInRoom(appNavigationStateService.appNavigationState.value)) {
return
}
renderEvents(listOf(notifiableEvent))
onNotifiableEventsReceived(listOf(notifiableEvent))
}
suspend fun onNotifiableEventsReceived(notifiableEvents: List<NotifiableEvent>) {
val eventsToNotify = notifiableEvents.filter { !it.shouldIgnoreEventInRoom(appNavigationStateService.appNavigationState.value) }
val eventsToNotify = notifiableEvents.filter { !appNavigationStateService.appNavigationState.value.shouldIgnoreEvent(it) }
renderEvents(eventsToNotify)
}
@ -121,6 +129,17 @@ class DefaultNotificationDrawerManager(
.forEach { notificationDisplayer.cancelNotification(it.tag, it.id) }
}
/**
* Remove the fallback notification for the session.
*/
fun clearFallbackForSession(sessionId: SessionId) {
notificationDisplayer.cancelNotification(
DefaultNotificationDataFactory.FALLBACK_NOTIFICATION_TAG,
NotificationIdProvider.getFallbackNotificationId(sessionId),
)
clearSummaryNotificationIfNeeded(sessionId)
}
/**
* Should be called when the application is currently opened and showing timeline for the given [roomId].
* Used to ignore events related to that room (no need to display notification) and clean any existing notification on this room.
@ -192,3 +211,30 @@ class DefaultNotificationDrawerManager(
}
}
}
/**
* Used to check if a notifiableEvent should be ignored based on the current application navigation state.
*/
private fun AppNavigationState.shouldIgnoreEvent(event: NotifiableEvent): Boolean {
if (!isInForeground) return false
return navigationState.currentSessionId() == event.sessionId &&
when (event) {
is NotifiableRingingCallEvent -> {
// Never ignore ringing call notifications
// Note that NotifiableRingingCallEvent are not handled by DefaultNotificationDrawerManager
false
}
is FallbackNotifiableEvent -> {
// Ignore if the room list is currently displayed
navigationState is NavigationState.Session
}
is InviteNotifiableEvent,
is SimpleNotifiableEvent -> {
event.roomId == navigationState.currentRoomId()
}
is NotifiableMessageEvent -> {
event.roomId == navigationState.currentRoomId() &&
event.threadId == navigationState.currentThreadId()
}
}
}

View file

@ -144,12 +144,12 @@ class DefaultNotificationDataFactory(
.getFallbackNotification(notificationAccountParams.user.userId)
?.notification
val notification = notificationCreator.createFallbackNotification(
existingNotification,
notificationAccountParams,
fallback,
existingNotification = existingNotification,
notificationAccountParams = notificationAccountParams,
fallbackNotifiableEvents = fallback,
)
return OneShotNotification(
tag = "FALLBACK",
tag = FALLBACK_NOTIFICATION_TAG,
notification = notification,
isNoisy = false,
timestamp = fallback.first().timestamp
@ -174,6 +174,10 @@ class DefaultNotificationDataFactory(
)
}
}
companion object {
const val FALLBACK_NOTIFICATION_TAG = "FALLBACK"
}
}
data class RoomNotification(

View file

@ -15,10 +15,6 @@ import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.EventType
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.currentRoomId
import io.element.android.services.appnavstate.api.currentSessionId
import io.element.android.services.appnavstate.api.currentThreadId
data class NotifiableMessageEvent(
override val sessionId: SessionId,
@ -56,24 +52,3 @@ data class NotifiableMessageEvent(
val imageUri: Uri?
get() = imageUriString?.toUri()
}
/**
* Used to check if a notification should be ignored based on the current app and navigation state.
*/
fun NotifiableEvent.shouldIgnoreEventInRoom(appNavigationState: AppNavigationState): Boolean {
val currentSessionId = appNavigationState.navigationState.currentSessionId() ?: return false
return when (val currentRoomId = appNavigationState.navigationState.currentRoomId()) {
null -> false
else -> {
// Never ignore ringing call notifications
if (this is NotifiableRingingCallEvent) {
false
} else {
appNavigationState.isInForeground &&
sessionId == currentSessionId &&
roomId == currentRoomId &&
(this as? NotifiableMessageEvent)?.threadId == appNavigationState.navigationState.currentThreadId()
}
}
}
}

View file

@ -15,8 +15,11 @@ import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.features.enterprise.test.FakeEnterpriseService
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_ROOM_ID_2
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_THREAD_ID
import io.element.android.libraries.matrix.test.A_THREAD_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.FakeMatrixClientProvider
import io.element.android.libraries.matrix.ui.components.aMatrixUser
@ -28,23 +31,26 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeNotificatio
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.FakeSummaryGroupMessageCreator
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.aSimpleNotifiableEvent
import io.element.android.libraries.push.impl.notifications.fixtures.anInviteNotifiableEvent
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.observer.SessionObserver
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
import io.element.android.libraries.sessionstorage.test.observer.FakeSessionObserver
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.NavigationState
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import io.element.android.services.appnavstate.test.aNavigationState
import io.element.android.services.appnavstate.test.anAppNavigationState
import io.element.android.tests.testutils.lambda.any
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@ -92,26 +98,25 @@ class DefaultNotificationDrawerManagerTest {
@Test
fun `react to applicationStateChange`() = runTest {
// For now just call all the API. Later, add more valuable tests.
val appNavigationStateFlow: MutableStateFlow<AppNavigationState> = MutableStateFlow(
AppNavigationState(
navigationState = NavigationState.Root,
isInForeground = true,
)
)
val appNavigationStateService = FakeAppNavigationStateService(appNavigationState = appNavigationStateFlow)
val appNavigationStateService = FakeAppNavigationStateService()
createDefaultNotificationDrawerManager(
appNavigationStateService = appNavigationStateService
)
appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true))
appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(), isInForeground = true))
runCurrent()
appNavigationStateFlow.emit(AppNavigationState(aNavigationState(A_SESSION_ID), isInForeground = true))
appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(A_SESSION_ID), isInForeground = true))
runCurrent()
appNavigationStateFlow.emit(AppNavigationState(aNavigationState(A_SESSION_ID, A_ROOM_ID), isInForeground = true))
appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(A_SESSION_ID, A_ROOM_ID), isInForeground = true))
runCurrent()
appNavigationStateFlow.emit(AppNavigationState(aNavigationState(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID), isInForeground = true))
appNavigationStateService.emitNavigationState(
AppNavigationState(
aNavigationState(A_SESSION_ID, A_ROOM_ID, A_THREAD_ID),
isInForeground = true
)
)
runCurrent()
// Like a user sign out
appNavigationStateFlow.emit(AppNavigationState(aNavigationState(), isInForeground = true))
appNavigationStateService.emitNavigationState(AppNavigationState(aNavigationState(), isInForeground = true))
runCurrent()
}
@ -234,6 +239,262 @@ class DefaultNotificationDrawerManagerTest {
listOf(value(null), value(summaryId)),
)
}
@Test
fun `when the application is in background, all events trigger a notification`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID),
isInForeground = false,
),
notifiableEvents = listOf(
aFallbackNotifiableEvent(sessionId = A_SESSION_ID),
aFallbackNotifiableEvent(sessionId = A_SESSION_ID_2),
anInviteNotifiableEvent(sessionId = A_SESSION_ID),
anInviteNotifiableEvent(sessionId = A_SESSION_ID_2),
aSimpleNotifiableEvent(sessionId = A_SESSION_ID),
aSimpleNotifiableEvent(sessionId = A_SESSION_ID_2),
aNotifiableMessageEvent(sessionId = A_SESSION_ID),
aNotifiableMessageEvent(sessionId = A_SESSION_ID_2),
aNotifiableMessageEvent(sessionId = A_SESSION_ID, threadId = A_THREAD_ID),
aNotifiableMessageEvent(sessionId = A_SESSION_ID_2, threadId = A_THREAD_ID_2),
),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 2,
)
@Test
fun `fallback event is ignored when the room list is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID),
),
notifiableEvents = listOf(aFallbackNotifiableEvent(sessionId = A_SESSION_ID)),
shouldEmitNotification = false,
)
@Test
fun `fallback event is not ignored when a room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID),
),
notifiableEvents = listOf(aFallbackNotifiableEvent(sessionId = A_SESSION_ID)),
shouldEmitNotification = true,
)
@Test
fun `fallback event for other session is not ignored when the room list is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID_2),
),
notifiableEvents = listOf(aFallbackNotifiableEvent(sessionId = A_SESSION_ID)),
shouldEmitNotification = true,
)
@Test
fun `invite notifiable event is emits a notification when the room list is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID),
),
notifiableEvents = listOf(anInviteNotifiableEvent(sessionId = A_SESSION_ID)),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
@Test
fun `invite notifiable event does not emit a notification when the same room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID),
),
notifiableEvents = listOf(
anInviteNotifiableEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
)
),
shouldEmitNotification = false,
)
@Test
fun `invite notifiable event emits a notification when another room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID_2),
),
notifiableEvents = listOf(
anInviteNotifiableEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
)
),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
@Test
fun `simple notifiable event is emits a notification when the room list is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID),
),
notifiableEvents = listOf(aSimpleNotifiableEvent(sessionId = A_SESSION_ID)),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
@Test
fun `simple notifiable event does not emit a notification when the same room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID),
),
notifiableEvents = listOf(
aSimpleNotifiableEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
)
),
shouldEmitNotification = false,
)
@Test
fun `simple notifiable event emits a notification when another room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID_2),
),
notifiableEvents = listOf(
aSimpleNotifiableEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
)
),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
@Test
fun `notifiable event is emits a notification when the room list is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID),
),
notifiableEvents = listOf(aNotifiableMessageEvent(sessionId = A_SESSION_ID)),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
@Test
fun `notifiable event does not emit a notification when the same room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID),
),
notifiableEvents = listOf(
aNotifiableMessageEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
)
),
shouldEmitNotification = false,
)
@Test
fun `notifiable event for a thread emits a notification when the same room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID),
),
notifiableEvents = listOf(
aNotifiableMessageEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
threadId = A_THREAD_ID,
)
),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
@Test
fun `notifiable event for a thread does not emit a notification when the same thread is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID),
),
notifiableEvents = listOf(
aNotifiableMessageEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
threadId = A_THREAD_ID,
)
),
shouldEmitNotification = false,
)
@Test
fun `notifiable event for a thread emits a notification when another thread is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID, threadId = A_THREAD_ID_2),
),
notifiableEvents = listOf(
aNotifiableMessageEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
threadId = A_THREAD_ID,
)
),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
@Test
fun `notifiable event for a thread emits a notification when a thread of another room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID_2, threadId = A_THREAD_ID_2),
),
notifiableEvents = listOf(
aNotifiableMessageEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
threadId = A_THREAD_ID,
)
),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
@Test
fun `notifiable event emits a notification when another room is displayed`() = testOnNotifiableEventReceived(
appNavigationState = anAppNavigationState(
navigationState = aNavigationState(sessionId = A_SESSION_ID, roomId = A_ROOM_ID_2),
),
notifiableEvents = listOf(
aNotifiableMessageEvent(
sessionId = A_SESSION_ID,
roomId = A_ROOM_ID,
)
),
shouldEmitNotification = true,
extraInvocationsForNotificationSummary = 1,
)
private fun testOnNotifiableEventReceived(
appNavigationState: AppNavigationState,
notifiableEvents: List<NotifiableEvent>,
shouldEmitNotification: Boolean,
extraInvocationsForNotificationSummary: Int = 0,
) = runTest {
val showNotificationResult = lambdaRecorder<String?, Int, Notification, Boolean> { _, _, _ ->
true
}
val defaultNotificationDrawerManager = createDefaultNotificationDrawerManager(
appNavigationStateService = FakeAppNavigationStateService(
initialAppNavigationState = appNavigationState,
),
notificationDisplayer = FakeNotificationDisplayer(
showNotificationResult = showNotificationResult,
)
)
defaultNotificationDrawerManager.onNotifiableEventsReceived(notifiableEvents)
showNotificationResult.assertions().isCalledExactly(
if (shouldEmitNotification) {
notifiableEvents.size + extraInvocationsForNotificationSummary
} else {
0
}
)
}
}
fun TestScope.createDefaultNotificationDrawerManager(
@ -251,7 +512,7 @@ fun TestScope.createDefaultNotificationDrawerManager(
return DefaultNotificationDrawerManager(
notificationDisplayer = notificationDisplayer,
notificationRenderer = notificationRenderer ?: NotificationRenderer(
notificationDisplayer = FakeNotificationDisplayer(),
notificationDisplayer = notificationDisplayer,
notificationDataFactory = DefaultNotificationDataFactory(
notificationCreator = FakeNotificationCreator(),
roomGroupMessageCreator = roomGroupMessageCreator,

View file

@ -144,8 +144,10 @@ fun aNotifiableCallEvent(
rtcNotificationType = rtcNotificationType,
)
fun aFallbackNotifiableEvent() = FallbackNotifiableEvent(
sessionId = A_SESSION_ID,
fun aFallbackNotifiableEvent(
sessionId: SessionId = A_SESSION_ID,
) = FallbackNotifiableEvent(
sessionId = sessionId,
roomId = A_ROOM_ID,
eventId = AN_EVENT_ID,
editedEventId = null,

View file

@ -43,9 +43,9 @@ class DefaultAnalyticsRoomListStateWatcherTest {
runCurrent()
// Make sure it's warm by changing its internal state
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
runCurrent()
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
runCurrent()
// The transaction should be present now
@ -63,9 +63,9 @@ class DefaultAnalyticsRoomListStateWatcherTest {
@Test
fun `Opening the app in a cold state does nothing`() = runTest {
val navigationStateService = FakeAppNavigationStateService().apply {
appNavigationState.emit(AppNavigationState(NavigationState.Root, false))
}
val navigationStateService = FakeAppNavigationStateService(
initialAppNavigationState = AppNavigationState(NavigationState.Root, false)
)
val roomListService = FakeRoomListService().apply {
postState(RoomListService.State.Idle)
}
@ -110,9 +110,9 @@ class DefaultAnalyticsRoomListStateWatcherTest {
runCurrent()
// Make sure it's warm by changing its internal state
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
runCurrent()
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
runCurrent()
// The transaction should be present now
@ -145,9 +145,9 @@ class DefaultAnalyticsRoomListStateWatcherTest {
runCurrent()
// Make sure it's warm by changing its internal state
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = false))
runCurrent()
navigationStateService.appNavigationState.emit(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
navigationStateService.emitNavigationState(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
runCurrent()
// The transaction was never added

View file

@ -31,7 +31,6 @@ import io.sentry.Sentry
import io.sentry.SentryTracer
import io.sentry.protocol.SentryId
import io.sentry.protocol.SentryTransaction
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Test
import org.junit.runner.RunWith
@ -149,7 +148,7 @@ class SentryAnalyticsProviderTest {
)
},
appNavigationStateService = FakeAppNavigationStateService(
MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true))
initialAppNavigationState = AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)
)
).run {
init()
@ -182,7 +181,7 @@ class SentryAnalyticsProviderTest {
)
},
appNavigationStateService = FakeAppNavigationStateService(
MutableStateFlow(AppNavigationState(navigationState = NavigationState.Root, isInForeground = true))
initialAppNavigationState = AppNavigationState(navigationState = NavigationState.Root, isInForeground = true)
)
).run {
init()
@ -203,7 +202,7 @@ class SentryAnalyticsProviderTest {
)
},
appNavigationStateService = FakeAppNavigationStateService(
MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true))
initialAppNavigationState = AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)
)
).run {
init()
@ -221,7 +220,7 @@ class SentryAnalyticsProviderTest {
buildMeta: BuildMeta = aBuildMeta(),
getDatabaseSizesUseCase: GetDatabaseSizesUseCase = GetDatabaseSizesUseCase { Result.success(SdkStoreSizes(null, null, null, null)) },
appNavigationStateService: FakeAppNavigationStateService = FakeAppNavigationStateService(
MutableStateFlow(AppNavigationState(navigationState = NavigationState.Session("owner", A_SESSION_ID), isInForeground = true))
initialAppNavigationState = AppNavigationState(NavigationState.Session("owner", A_SESSION_ID), isInForeground = true)
)
) = SentryAnalyticsProvider(
context = InstrumentationRegistry.getInstrumentation().targetContext,

View file

@ -11,6 +11,7 @@ package io.element.android.services.appnavstate.test
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.ThreadId
import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.NavigationState
const val A_SESSION_OWNER = "aSessionOwner"
@ -35,3 +36,11 @@ fun aNavigationState(
}
return NavigationState.Thread(A_THREAD_OWNER, threadId, room)
}
fun anAppNavigationState(
navigationState: NavigationState = aNavigationState(),
isInForeground: Boolean = true,
) = AppNavigationState(
navigationState = navigationState,
isInForeground = isInForeground,
)

View file

@ -15,15 +15,21 @@ import io.element.android.services.appnavstate.api.AppNavigationState
import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.NavigationState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeAppNavigationStateService(
override val appNavigationState: MutableStateFlow<AppNavigationState> = MutableStateFlow(
AppNavigationState(
navigationState = NavigationState.Root,
isInForeground = true,
)
initialAppNavigationState: AppNavigationState = AppNavigationState(
navigationState = NavigationState.Root,
isInForeground = true,
),
) : AppNavigationStateService {
private val _appNavigationState: MutableStateFlow<AppNavigationState> = MutableStateFlow(initialAppNavigationState)
override val appNavigationState = _appNavigationState.asStateFlow()
fun emitNavigationState(state: AppNavigationState) {
_appNavigationState.value = state
}
override fun onNavigateToSession(owner: String, sessionId: SessionId) = Unit
override fun onLeavingSession(owner: String) = Unit