Rely on the SessionObserver to detect a sign out.

This commit is contained in:
Benoit Marty 2026-02-11 17:45:23 +01:00
parent 8e0e8c3f14
commit e3ace3c87e
3 changed files with 77 additions and 48 deletions

View file

@ -24,9 +24,10 @@ import io.element.android.libraries.push.api.notifications.NotificationIdProvide
import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator import io.element.android.libraries.push.impl.notifications.factories.NotificationCreator
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.push.impl.notifications.model.shouldIgnoreEventInRoom import io.element.android.libraries.push.impl.notifications.model.shouldIgnoreEventInRoom
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.AppNavigationStateService 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.api.currentSessionId
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -46,28 +47,30 @@ class DefaultNotificationDrawerManager(
private val matrixClientProvider: MatrixClientProvider, private val matrixClientProvider: MatrixClientProvider,
private val imageLoaderHolder: ImageLoaderHolder, private val imageLoaderHolder: ImageLoaderHolder,
private val activeNotificationsProvider: ActiveNotificationsProvider, private val activeNotificationsProvider: ActiveNotificationsProvider,
sessionObserver: SessionObserver,
) : NotificationCleaner { ) : NotificationCleaner {
// TODO EAx add a setting per user for this // TODO EAx add a setting per user for this
private var useCompleteNotificationFormat = true private var useCompleteNotificationFormat = true
private val sessionListener = object : SessionListener {
override suspend fun onSessionDeleted(userId: String, wasLastSession: Boolean) {
// User signed out, clear all notifications related to the session.
clearAllEvents(SessionId(userId))
}
}
init { init {
// Observe application state // Observe application state
coroutineScope.launch { coroutineScope.launch {
appNavigationStateService.appNavigationState appNavigationStateService.appNavigationState
.collect { onAppNavigationStateChange(it.navigationState) } .collect { onAppNavigationStateChange(it.navigationState) }
} }
sessionObserver.addListener(sessionListener)
} }
private var currentAppNavigationState: NavigationState? = null
private fun onAppNavigationStateChange(navigationState: NavigationState) { private fun onAppNavigationStateChange(navigationState: NavigationState) {
when (navigationState) { when (navigationState) {
NavigationState.Root -> { NavigationState.Root -> {}
currentAppNavigationState?.currentSessionId()?.let { sessionId ->
// User signed out, clear all notifications related to the session.
clearAllEvents(sessionId)
}
}
is NavigationState.Session -> {} is NavigationState.Session -> {}
is NavigationState.Space -> {} is NavigationState.Space -> {}
is NavigationState.Room -> { is NavigationState.Room -> {
@ -85,7 +88,6 @@ class DefaultNotificationDrawerManager(
) )
} }
} }
currentAppNavigationState = navigationState
} }
/** /**

View file

@ -31,7 +31,9 @@ import io.element.android.libraries.push.impl.notifications.fake.FakeRoomGroupMe
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.aNotifiableMessageEvent import io.element.android.libraries.push.impl.notifications.fixtures.aNotifiableMessageEvent
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.test.InMemorySessionStore 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.AppNavigationState
import io.element.android.services.appnavstate.api.AppNavigationStateService import io.element.android.services.appnavstate.api.AppNavigationStateService
import io.element.android.services.appnavstate.api.NavigationState import io.element.android.services.appnavstate.api.NavigationState
@ -205,35 +207,70 @@ class DefaultNotificationDrawerManagerTest {
) )
} }
private fun TestScope.createDefaultNotificationDrawerManager( @Test
notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(), fun `when a session is signed out, clearAllEvent is invoked`() = runTest {
appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(), val cancelNotificationResult = lambdaRecorder<String?, Int, Unit> { _, _ -> }
roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(), val notificationDisplayer = FakeNotificationDisplayer(
summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(), cancelNotificationResult = cancelNotificationResult,
activeNotificationsProvider: FakeActiveNotificationsProvider = FakeActiveNotificationsProvider(), )
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(), val summaryId = NotificationIdProvider.getSummaryNotificationId(A_SESSION_ID)
sessionStore: SessionStore = InMemorySessionStore(), val activeNotificationsProvider = FakeActiveNotificationsProvider(
enterpriseService: EnterpriseService = FakeEnterpriseService(), getNotificationsForSessionResult = {
): DefaultNotificationDrawerManager { listOf(
return DefaultNotificationDrawerManager( mockk {
every { id } returns summaryId
every { tag } returns null
},
)
},
countResult = { 1 },
)
val sessionObserver = FakeSessionObserver()
createDefaultNotificationDrawerManager(
notificationDisplayer = notificationDisplayer, notificationDisplayer = notificationDisplayer,
notificationRenderer = NotificationRenderer(
notificationDisplayer = FakeNotificationDisplayer(),
notificationDataFactory = DefaultNotificationDataFactory(
notificationCreator = FakeNotificationCreator(),
roomGroupMessageCreator = roomGroupMessageCreator,
summaryGroupMessageCreator = summaryGroupMessageCreator,
activeNotificationsProvider = activeNotificationsProvider,
stringProvider = FakeStringProvider(),
),
enterpriseService = enterpriseService,
sessionStore = sessionStore,
),
appNavigationStateService = appNavigationStateService,
coroutineScope = backgroundScope,
matrixClientProvider = matrixClientProvider,
imageLoaderHolder = FakeImageLoaderHolder(),
activeNotificationsProvider = activeNotificationsProvider, activeNotificationsProvider = activeNotificationsProvider,
sessionObserver = sessionObserver,
)
// Simulate a session sign out
sessionObserver.onSessionDeleted(A_SESSION_ID.value)
// Verify we asked to cancel the notification with summaryId
cancelNotificationResult.assertions().isCalledExactly(1).withSequence(
listOf(value(null), value(summaryId)),
) )
} }
} }
fun TestScope.createDefaultNotificationDrawerManager(
notificationDisplayer: NotificationDisplayer = FakeNotificationDisplayer(),
notificationRenderer: NotificationRenderer? = null,
appNavigationStateService: AppNavigationStateService = FakeAppNavigationStateService(),
roomGroupMessageCreator: RoomGroupMessageCreator = FakeRoomGroupMessageCreator(),
summaryGroupMessageCreator: SummaryGroupMessageCreator = FakeSummaryGroupMessageCreator(),
activeNotificationsProvider: FakeActiveNotificationsProvider = FakeActiveNotificationsProvider(),
matrixClientProvider: FakeMatrixClientProvider = FakeMatrixClientProvider(),
sessionStore: SessionStore = InMemorySessionStore(),
enterpriseService: EnterpriseService = FakeEnterpriseService(),
sessionObserver: SessionObserver = FakeSessionObserver(),
): DefaultNotificationDrawerManager {
return DefaultNotificationDrawerManager(
notificationDisplayer = notificationDisplayer,
notificationRenderer = notificationRenderer ?: NotificationRenderer(
notificationDisplayer = FakeNotificationDisplayer(),
notificationDataFactory = DefaultNotificationDataFactory(
notificationCreator = FakeNotificationCreator(),
roomGroupMessageCreator = roomGroupMessageCreator,
summaryGroupMessageCreator = summaryGroupMessageCreator,
activeNotificationsProvider = activeNotificationsProvider,
stringProvider = FakeStringProvider(),
),
enterpriseService = enterpriseService,
sessionStore = sessionStore,
),
appNavigationStateService = appNavigationStateService,
coroutineScope = backgroundScope,
matrixClientProvider = matrixClientProvider,
imageLoaderHolder = FakeImageLoaderHolder(),
activeNotificationsProvider = activeNotificationsProvider,
sessionObserver = sessionObserver,
)
}

View file

@ -16,13 +16,9 @@ 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.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notification.FakeNotificationService
import io.element.android.libraries.matrix.test.notification.aNotificationData import io.element.android.libraries.matrix.test.notification.aNotificationData
import io.element.android.libraries.matrix.ui.media.test.FakeImageLoaderHolder
import io.element.android.libraries.push.impl.notifications.fake.FakeActiveNotificationsProvider
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDataFactory
import io.element.android.libraries.push.impl.notifications.fake.FakeNotificationDisplayer
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.test.notifications.FakeCallNotificationEventResolver import io.element.android.libraries.push.test.notifications.FakeCallNotificationEventResolver
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runCurrent
@ -47,16 +43,10 @@ class DefaultOnMissedCallNotificationHandlerTest {
}) })
val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler( val defaultOnMissedCallNotificationHandler = DefaultOnMissedCallNotificationHandler(
matrixClientProvider = matrixClientProvider, matrixClientProvider = matrixClientProvider,
defaultNotificationDrawerManager = DefaultNotificationDrawerManager( defaultNotificationDrawerManager = createDefaultNotificationDrawerManager(
notificationDisplayer = FakeNotificationDisplayer(),
notificationRenderer = createNotificationRenderer( notificationRenderer = createNotificationRenderer(
notificationDataFactory = dataFactory, notificationDataFactory = dataFactory,
), ),
appNavigationStateService = FakeAppNavigationStateService(),
coroutineScope = backgroundScope,
matrixClientProvider = FakeMatrixClientProvider(),
imageLoaderHolder = FakeImageLoaderHolder(),
activeNotificationsProvider = FakeActiveNotificationsProvider(),
), ),
callNotificationEventResolver = FakeCallNotificationEventResolver(resolveEventLambda = { _, _, _ -> callNotificationEventResolver = FakeCallNotificationEventResolver(resolveEventLambda = { _, _, _ ->
Result.success(aNotifiableMessageEvent()) Result.success(aNotifiableMessageEvent())