From c11b6b5ae1203c4306fd2805afca2484e39c1cd8 Mon Sep 17 00:00:00 2001 From: boris Date: Tue, 9 Jan 2024 10:01:53 +0200 Subject: [PATCH] feat: App soft lock when E2EI is required (WPB-5876) (#2563) --- .../notification/WireNotificationManager.kt | 48 ++++++++++++------- .../WireNotificationManagerTest.kt | 42 ++++++++++++++++ 2 files changed, 73 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/com/wire/android/notification/WireNotificationManager.kt b/app/src/main/kotlin/com/wire/android/notification/WireNotificationManager.kt index 8c0baad41c4..aaabb885f29 100644 --- a/app/src/main/kotlin/com/wire/android/notification/WireNotificationManager.kt +++ b/app/src/main/kotlin/com/wire/android/notification/WireNotificationManager.kt @@ -37,6 +37,7 @@ import com.wire.kalium.logic.data.user.UserId import com.wire.kalium.logic.feature.message.MarkMessagesAsNotifiedUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult import com.wire.kalium.logic.feature.session.GetAllSessionsResult +import com.wire.kalium.logic.feature.user.E2EIRequiredResult import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -51,6 +52,7 @@ import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex @@ -303,9 +305,17 @@ class WireNotificationManager @Inject constructor( ) { appLogger.d("$TAG observe incoming calls") - coreLogic.getSessionScope(userId) - .calls - .getIncomingCalls() + coreLogic.getSessionScope(userId).observeE2EIRequired() + .map { it is E2EIRequiredResult.NoGracePeriod } + .distinctUntilChanged() + .flatMapLatest { isBlockedByE2EIRequired -> + if (isBlockedByE2EIRequired) { + appLogger.d("$TAG calls were blocked as E2EI is required") + flowOf(listOf()) + } else { + coreLogic.getSessionScope(userId).calls.getIncomingCalls() + } + } .collect { calls -> callNotificationManager.handleIncomingCallNotifications(calls, userId) } @@ -329,33 +339,37 @@ class WireNotificationManager @Inject constructor( .distinctUntilChanged() .stateIn(scope) + val isBlockedByE2EIRequiredState = coreLogic.getSessionScope(userId).observeE2EIRequired() + .map { it is E2EIRequiredResult.NoGracePeriod } + .distinctUntilChanged() + .stateIn(scope) + coreLogic.getSessionScope(userId) .messages .getNotifications() .cancellable() + .onEach { newNotifications -> + playPingSoundIfNeeded( + currentScreen = currentScreenState.value, + notifications = newNotifications + ) + } .map { newNotifications -> // we don't want to display notifications for the Conversation that user currently in. - val notificationsList = filterAccordingToScreenAndUpdateNotifyDate( + filterAccordingToScreenAndUpdateNotifyDate( currentScreenState.value, userId, newNotifications ) - - playPingSoundIfNeeded( - currentScreen = currentScreenState.value, - notifications = newNotifications - ) - val userName = selfUserNameState.value - - // combining all the data that is necessary for Notifications into small data class, - // just to make it more readable than - // Triple, QualifiedID, String> - MessagesNotificationsData(notificationsList, userId, userName) } .cancellable() - .collect { (newNotifications, userId, userName) -> + .collect { newNotifications -> appLogger.d("$TAG got ${newNotifications.size} notifications") - messagesNotificationManager.handleNotification(newNotifications, userId, userName) + if (isBlockedByE2EIRequiredState.value) { + appLogger.d("$TAG notifications were skipped as E2EI is required") + } else { + messagesNotificationManager.handleNotification(newNotifications, userId, selfUserNameState.value) + } markMessagesAsNotified(userId) markConnectionAsNotified(userId) } diff --git a/app/src/test/kotlin/com/wire/android/notification/WireNotificationManagerTest.kt b/app/src/test/kotlin/com/wire/android/notification/WireNotificationManagerTest.kt index 1763fb0fd97..8c837d07f5f 100644 --- a/app/src/test/kotlin/com/wire/android/notification/WireNotificationManagerTest.kt +++ b/app/src/test/kotlin/com/wire/android/notification/WireNotificationManagerTest.kt @@ -57,7 +57,9 @@ import com.wire.kalium.logic.feature.session.CurrentSessionFlowUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult import com.wire.kalium.logic.feature.session.GetAllSessionsResult import com.wire.kalium.logic.feature.session.GetSessionsUseCase +import com.wire.kalium.logic.feature.user.E2EIRequiredResult import com.wire.kalium.logic.feature.user.GetSelfUserUseCase +import com.wire.kalium.logic.feature.user.ObserveE2EIRequiredUseCase import com.wire.kalium.logic.feature.user.UserScope import com.wire.kalium.logic.sync.SyncManager import io.mockk.MockKAnnotations @@ -664,6 +666,36 @@ class WireNotificationManagerTest { verify(exactly = 1) { arrangement.servicesManager.stopOngoingCallService() } } + @Test + fun givenSomeNotificationsAndUserBlockedByE2EIRequired_whenObserveCalled_thenNotificationIsNotShowed() = + runTestWithCancellation(dispatcherProvider.main()) { + val conversationId = ConversationId("conversation_value", "conversation_domain") + val (arrangement, manager) = Arrangement() + .withMessageNotifications( + listOf( + provideLocalNotificationConversation( + id = conversationId, + messages = listOf(provideLocalNotificationMessage()) + ) + ) + ).withIncomingCalls(listOf()) + .withCurrentScreen(CurrentScreen.SomeOther) + .withObserveE2EIRequired(E2EIRequiredResult.NoGracePeriod.Create) + .arrange() + + manager.observeNotificationsAndCallsWhileRunning(listOf(provideUserId(TestUser.SELF_USER.id.value)), this) + runCurrent() + + verify(exactly = 0) { + arrangement.messageNotificationManager.handleNotification( + listOf(), TestUser.SELF_USER.id, TestUser.SELF_USER.handle!! + ) + } + coVerify(atLeast = 1) { + arrangement.markMessagesAsNotified(MarkMessagesAsNotifiedUseCase.UpdateTarget.AllConversations) + } + } + private inner class Arrangement { @MockK lateinit var coreLogic: CoreLogic @@ -731,6 +763,9 @@ class WireNotificationManagerTest { @MockK lateinit var pingRinger: PingRinger + @MockK + lateinit var observeE2EIRequired: ObserveE2EIRequiredUseCase + private val currentSessionChannel = Channel(capacity = Channel.UNLIMITED) val wireNotificationManager by lazy { @@ -749,11 +784,13 @@ class WireNotificationManagerTest { init { MockKAnnotations.init(this, relaxUnitFun = true) + coEvery { observeE2EIRequired() } returns flowOf(E2EIRequiredResult.NotRequired) coEvery { userSessionScope.calls } returns callsScope coEvery { userSessionScope.messages } returns messageScope coEvery { userSessionScope.syncManager } returns syncManager coEvery { userSessionScope.conversations } returns conversationScope coEvery { userSessionScope.users } returns userScope + coEvery { userSessionScope.observeE2EIRequired } returns observeE2EIRequired coEvery { conversationScope.markConnectionRequestAsNotified } returns markConnectionRequestAsNotified coEvery { userScope.getSelfUser } returns getSelfUser coEvery { markConnectionRequestAsNotified(any()) } returns Unit @@ -801,6 +838,7 @@ class WireNotificationManagerTest { coEvery { users } returns mockk { coEvery { getSelfUser() } returns flowOf(selfUser) } + coEvery { observeE2EIRequired } returns this@Arrangement.observeE2EIRequired } } @@ -848,6 +886,10 @@ class WireNotificationManagerTest { coEvery { getSelfUser.invoke() } returns selfUserFlow } + fun withObserveE2EIRequired(result: E2EIRequiredResult) = apply { + coEvery { observeE2EIRequired.invoke() } returns flowOf(result) + } + fun arrange() = this to wireNotificationManager }