Skip to content

Commit

Permalink
feat: End call on verification degradation (WPB-4487) (#2199)
Browse files Browse the repository at this point in the history
* feat: End call on verification degradation

* Fixed tests

* Renamed some classes

* Review changes

* Fixed test

* Renamed useCase

* Fixed code style
  • Loading branch information
borichellow authored Nov 8, 2023
1 parent 19b3723 commit 33129c1
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import com.wire.kalium.logic.data.id.CurrentClientIdProvider
import com.wire.kalium.logic.feature.call.usecase.AnswerCallUseCase
import com.wire.kalium.logic.feature.call.usecase.AnswerCallUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ConversationClientsInCallUpdater
import com.wire.kalium.logic.feature.call.usecase.EndCallResultListenerImpl
import com.wire.kalium.logic.feature.call.usecase.EndCallOnConversationChangeUseCase
import com.wire.kalium.logic.feature.call.usecase.EndCallOnConversationChangeUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.EndCallUseCase
Expand All @@ -48,6 +49,8 @@ import com.wire.kalium.logic.feature.call.usecase.IsLastCallClosedUseCase
import com.wire.kalium.logic.feature.call.usecase.IsLastCallClosedUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.MuteCallUseCase
import com.wire.kalium.logic.feature.call.usecase.MuteCallUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveEndCallDueToConversationDegradationUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveEndCallDueToConversationDegradationUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveOngoingCallsUseCase
Expand All @@ -66,6 +69,7 @@ import com.wire.kalium.logic.feature.call.usecase.UpdateConversationClientsForCu
import com.wire.kalium.logic.feature.call.usecase.UpdateVideoStateUseCase
import com.wire.kalium.logic.featureFlags.KaliumConfigs
import com.wire.kalium.logic.sync.SyncManager
import com.wire.kalium.util.KaliumDispatcher
import com.wire.kalium.util.KaliumDispatcherImpl

@Suppress("LongParameterList")
Expand All @@ -81,7 +85,8 @@ class CallsScope internal constructor(
private val currentClientIdProvider: CurrentClientIdProvider,
private val userConfigRepository: UserConfigRepository,
private val conversationClientsInCallUpdater: ConversationClientsInCallUpdater,
private val kaliumConfigs: KaliumConfigs
private val kaliumConfigs: KaliumConfigs,
internal val dispatcher: KaliumDispatcher = KaliumDispatcherImpl
) {

val allCallsWithSortedParticipants: GetAllCallsWithSortedParticipantsUseCase
Expand Down Expand Up @@ -109,13 +114,14 @@ class CallsScope internal constructor(
callRepository = callRepository,
)

val startCall: StartCallUseCase get() = StartCallUseCase(
callManager = callManager,
syncManager = syncManager,
callRepository = callRepository,
answerCall = answerCall,
kaliumConfigs = kaliumConfigs
)
val startCall: StartCallUseCase
get() = StartCallUseCase(
callManager = callManager,
syncManager = syncManager,
callRepository = callRepository,
answerCall = answerCall,
kaliumConfigs = kaliumConfigs
)

val answerCall: AnswerCallUseCase
get() = AnswerCallUseCaseImpl(
Expand All @@ -132,7 +138,8 @@ class CallsScope internal constructor(
get() = EndCallOnConversationChangeUseCaseImpl(
callRepository = callRepository,
conversationRepository = conversationRepository,
endCallUseCase = endCall
endCallUseCase = endCall,
endCallListener = EndCallResultListenerImpl
)

val updateConversationClientsForCurrentCallUseCase: UpdateConversationClientsForCurrentCallUseCase
Expand Down Expand Up @@ -172,4 +179,7 @@ class CallsScope internal constructor(
val requestVideoStreams: RequestVideoStreamsUseCase get() = RequestVideoStreamsUseCase(callManager, KaliumDispatcherImpl)

val isEligibleToStartCall: IsEligibleToStartCallUseCase get() = IsEligibleToStartCallUseCaseImpl(userConfigRepository, callRepository)

val observeEndCallDialog: ObserveEndCallDueToConversationDegradationUseCase
get() = ObserveEndCallDueToConversationDegradationUseCaseImpl(EndCallResultListenerImpl)
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package com.wire.kalium.logic.feature.call.usecase

import com.wire.kalium.logic.data.call.CallRepository
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.conversation.ConversationDetails
import com.wire.kalium.logic.data.conversation.ConversationRepository
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.functional.getOrElse
import com.wire.kalium.logic.functional.map
import com.wire.kalium.logic.functional.onlyRight
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.scan

/**
* End call when conversation is deleted, user is not a member anymore or user is deleted.
Expand All @@ -20,34 +28,76 @@ interface EndCallOnConversationChangeUseCase {
internal class EndCallOnConversationChangeUseCaseImpl(
private val callRepository: CallRepository,
private val conversationRepository: ConversationRepository,
private val endCallUseCase: EndCallUseCase
private val endCallUseCase: EndCallUseCase,
private val endCallListener: EndCallResultListener
) : EndCallOnConversationChangeUseCase {
override suspend operator fun invoke() {
val callsFlow = callRepository.establishedCallsFlow().map { calls ->
calls.map { it.conversationId }
}.distinctUntilChanged().cancellable()

callsFlow.collectLatest { calls ->
if (calls.isNotEmpty()) {
conversationRepository.observeConversationDetailsById(calls.first()).cancellable().collect { conversationDetails ->
conversationDetails.fold({
// conversation deleted
endCallUseCase(calls.first())
}, {
if (it is ConversationDetails.Group) {
// Not a member anymore
if (!it.isSelfUserMember) {
endCallUseCase(calls.first())
}
} else if (it is ConversationDetails.OneOne) {
// Member blocked or deleted
if (it.otherUser.deleted || it.otherUser.connectionStatus == ConnectionState.BLOCKED) {
endCallUseCase(calls.first())
}
}
})
}
callsFlow.flatMapLatest { calls ->
if (calls.isEmpty()) return@flatMapLatest emptyFlow()

val currentCall = calls.first()

merge(
finishCallBecauseOfMembershipChangesFlow(currentCall),
finishCallBecauseOfVerificationDegradedFlow(currentCall)
)
}.collect { conversationId -> endCallUseCase(conversationId) }
}

private suspend fun finishCallBecauseOfMembershipChangesFlow(conversationId: ConversationId) =
conversationRepository.observeConversationDetailsById(conversationId).cancellable()
.map { conversationDetails ->
conversationDetails.map {
// Member blocked or deleted
val isOtherUserBlockedOrDeleted = it is ConversationDetails.OneOne
&& (it.otherUser.deleted || it.otherUser.connectionStatus == ConnectionState.BLOCKED)
// Not a member of group anymore
val isSelfRemovedFromGroup = it is ConversationDetails.Group && !it.isSelfUserMember

isOtherUserBlockedOrDeleted || isSelfRemovedFromGroup
}.getOrElse(true)
}
}
.filter { it }
.map { conversationId }

/**
* @return [ConversationId] only when the conversation Proteus or MLS verification status was verified in past
* but became not verified -> means need to finish the call
*/
private suspend fun finishCallBecauseOfVerificationDegradedFlow(conversationId: ConversationId) =
conversationRepository.observeConversationDetailsById(conversationId)
.cancellable()
.onlyRight()
.map {
val isProteusVerified = it.conversation.proteusVerificationStatus == Conversation.VerificationStatus.VERIFIED
val isMLSVerified = it.conversation.mlsVerificationStatus == Conversation.VerificationStatus.VERIFIED

isProteusVerified to isMLSVerified
}
.scan(ConversationVerificationStatuses()) { prevState, (isProteusVerified, isMLSVerified) ->
ConversationVerificationStatuses(
isProteusVerified = isProteusVerified,
wasProteusVerified = prevState.isProteusVerified,
isMLSVerified = isMLSVerified,
wasMLSVerified = prevState.isMLSVerified
)
}
.filter { it.shouldFinishCall() }
.map {
endCallListener.onCallEndedBecauseOfVerificationDegraded(conversationId)
conversationId
}

private data class ConversationVerificationStatuses(
val isProteusVerified: Boolean = false,
val wasProteusVerified: Boolean = false,
val isMLSVerified: Boolean = false,
val wasMLSVerified: Boolean = false
) {
fun shouldFinishCall(): Boolean = (!isProteusVerified && wasProteusVerified) || (!isMLSVerified && wasMLSVerified)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.feature.call.usecase

import com.wire.kalium.logic.data.id.ConversationId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow

interface EndCallResultListener {
suspend fun observeCallEndedBecauseOfVerificationDegraded(): Flow<ConversationId>
suspend fun onCallEndedBecauseOfVerificationDegraded(conversationId: ConversationId)
}

/**
* This singleton allow us to queue event to show dialog informing user that call was ended because of verification degradation.
*/
object EndCallResultListenerImpl : EndCallResultListener {

private val conversationCallEnded = MutableSharedFlow<ConversationId>()

override suspend fun observeCallEndedBecauseOfVerificationDegraded(): Flow<ConversationId> = conversationCallEnded

override suspend fun onCallEndedBecauseOfVerificationDegraded(conversationId: ConversationId) {
conversationCallEnded.emit(conversationId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Wire
* Copyright (C) 2023 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.feature.call.usecase

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

/**
* The useCase for observing when the ongoing call was ended because of degradation of conversation verification status (Proteus or MLS)
*/
interface ObserveEndCallDueToConversationDegradationUseCase {
/**
* @return [Flow] that emits only when the call was ended because of degradation of conversation verification status (Proteus or MLS)
*/
suspend operator fun invoke(): Flow<Unit>
}

internal class ObserveEndCallDueToConversationDegradationUseCaseImpl(
private val endCallListener: EndCallResultListener
) : ObserveEndCallDueToConversationDegradationUseCase {
override suspend fun invoke(): Flow<Unit> =
endCallListener.observeCallEndedBecauseOfVerificationDegraded().map { Unit }
}
Loading

0 comments on commit 33129c1

Please sign in to comment.