diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index efeb31b525..c451a65d63 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -84,6 +84,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.runningFold import kotlinx.coroutines.launch import kotlinx.coroutines.withTimeoutOrNull import kotlinx.parcelize.Parcelize @@ -138,6 +139,7 @@ class RootFlowNode( } } observeIncomingVerificationRequests() + observeRemovedSessions() super.onBuilt() } @@ -230,6 +232,22 @@ class RootFlowNode( } } + /** + * Remove from the cache the sessions which have been removed from the session store, for instance after a logout. + */ + private fun observeRemovedSessions() { + sessionStore.sessionsFlow() + .map { sessions -> sessions.map { SessionId(it.userId) }.toSet() } + .runningFold(emptySet() to emptySet()) { (_, previous), current -> previous to current } + .onEach { (previous, current) -> + (previous - current).forEach { sessionId -> + Timber.d("Session $sessionId removed, remove it from the cache") + matrixSessionCache.remove(sessionId) + } + } + .launchIn(lifecycleScope) + } + private fun observeIncomingVerificationRequests() { incomingVerificationRequestObserver.incomingVerificationRequests() .onEach(::onIncomingVerificationRequest) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/verification/IncomingVerificationRequestObserver.kt b/appnav/src/main/kotlin/io/element/android/appnav/verification/IncomingVerificationRequestObserver.kt index fbf3e70a0b..bfc952334a 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/verification/IncomingVerificationRequestObserver.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/verification/IncomingVerificationRequestObserver.kt @@ -9,6 +9,7 @@ package io.element.android.appnav.verification import dev.zacsweers.metro.Inject import io.element.android.appnav.session.MatrixSessionCache +import io.element.android.libraries.core.coroutine.withPreviousValue import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener import io.element.android.libraries.matrix.api.verification.VerificationRequest @@ -41,8 +42,13 @@ class IncomingVerificationRequestObserver( */ fun incomingVerificationRequests(): Flow = channelFlow { launch { - matrixSessionCache.matrixClients.collect { matrixClients -> - matrixClients.forEach { matrixClient -> + matrixSessionCache.matrixClients.withPreviousValue().collect { (previousMatrixClients, matrixClients) -> + val previousMatrixClientSet = previousMatrixClients.orEmpty().toSet() + // Remove the listener of the clients which are not in the cache anymore + (previousMatrixClientSet - matrixClients.toSet()).forEach { matrixClient -> + matrixClient.sessionVerificationService.setListener(null) + } + (matrixClients - previousMatrixClientSet).forEach { matrixClient -> val sessionId = matrixClient.sessionId matrixClient.sessionVerificationService.setListener( object : SessionVerificationServiceListener { diff --git a/appnav/src/test/kotlin/io/element/android/appnav/verification/IncomingVerificationRequestObserverTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/verification/IncomingVerificationRequestObserverTest.kt index feb17940c1..04522629cd 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/verification/IncomingVerificationRequestObserverTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/verification/IncomingVerificationRequestObserverTest.kt @@ -63,6 +63,26 @@ class IncomingVerificationRequestObserverTest { assertThat(sessionVerificationService2.listener).isNull() } + @Test + fun `listener is removed when the session is removed from the cache`() = runTest { + val matrixSessionCache = createMatrixSessionCache() + val observer = IncomingVerificationRequestObserver( + matrixSessionCache = matrixSessionCache, + appForegroundStateService = FakeAppForegroundStateService(), + ) + observer.incomingVerificationRequests().test { + matrixSessionCache.getOrRestore(A_SESSION_ID) + matrixSessionCache.getOrRestore(A_SESSION_ID_2) + runCurrent() + assertThat(sessionVerificationService1.listener).isNotNull() + assertThat(sessionVerificationService2.listener).isNotNull() + matrixSessionCache.remove(A_SESSION_ID) + runCurrent() + assertThat(sessionVerificationService1.listener).isNull() + assertThat(sessionVerificationService2.listener).isNotNull() + } + } + @Test fun `incoming verification request is emitted when the app goes to foreground`() = runTest { val matrixSessionCache = createMatrixSessionCache() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index 760937c567..a96d5ec214 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -245,6 +245,7 @@ class RustSessionVerificationService( fun destroy() { Timber.d("Destroying RustSessionVerificationService") + listener = null verificationStateListenerTaskHandle.cancelAndDestroy() recoveryStateListenerTaskHandle.cancelAndDestroy() if (this::verificationController.isInitialized) {