Skip to content

Commit

Permalink
Merge branch 'develop' into dependabot/gradle/okio-3.10.2
Browse files Browse the repository at this point in the history
  • Loading branch information
yamilmedina authored Feb 18, 2025
2 parents 6ca6cb3 + 7c462ea commit 9cc5541
Show file tree
Hide file tree
Showing 15 changed files with 197 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,21 @@ actual fun mapMLSException(exception: Exception): MLSFailure =
is MlsException.StaleCommit -> MLSFailure.StaleCommit
is MlsException.ConversationAlreadyExists -> MLSFailure.ConversationAlreadyExists
is MlsException.MessageEpochTooOld -> MLSFailure.MessageEpochTooOld
else -> MLSFailure.Generic(exception)

is MlsException.Other -> {
if ((exception.v1 as MlsException.Other).v1
.startsWith(COMMIT_FOR_MISSING_PROPOSAL)
) {
MLSFailure.CommitForMissingProposal
} else {
MLSFailure.Other
}
}

is MlsException.OrphanWelcome -> MLSFailure.Generic(exception)
}
} else {
MLSFailure.Generic(exception)
}

private const val COMMIT_FOR_MISSING_PROPOSAL = "Incoming message is a commit for which we have not yet received all the proposals"
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ sealed class NetworkFailure : CoreFailure {
data object FeatureNotSupported : NetworkFailure()
}

interface MLSFailure : CoreFailure {
sealed interface MLSFailure : CoreFailure {

data object WrongEpoch : MLSFailure

Expand All @@ -200,10 +200,9 @@ interface MLSFailure : CoreFailure {
data object StaleCommit : MLSFailure
data object InternalErrors : MLSFailure
data object Disabled : MLSFailure

data class Generic(val exception: Exception) : MLSFailure {
val rootCause: Throwable get() = exception
}
data object Other : MLSFailure
data object CommitForMissingProposal : MLSFailure
data class Generic(val rootCause: Throwable) : MLSFailure
}

interface E2EIFailure : CoreFailure {
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pbandk = "0.15.0"
turbine = "1.1.0"
avs = "10.0.5"
jna = "5.14.0"
core-crypto = "3.0.2"
core-crypto = "3.1.0"
core-crypto-multiplatform = "0.6.0-rc.3-multiplatform-pre1"
completeKotlin = "1.1.0"
desugar-jdk = "2.1.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ internal class CallDataSource(
val updatedCallMetadata = callMetadataProfile.data.toMutableMap().apply {
this[conversationId] = call.copy(
participants = participants,
maxParticipants = max(call.maxParticipants, participants.size + 1),
maxParticipants = max(call.maxParticipants, participants.size),
users = updatedUsers,
screenShareMetadata = updateScreenSharingMetadata(
metadata = call.screenShareMetadata,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ 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.ObserveAskCallFeedbackUseCase
import com.wire.kalium.logic.feature.call.usecase.observeAskCallFeedbackUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCase
import com.wire.kalium.logic.feature.call.usecase.ObserveConferenceCallingEnabledUseCaseImpl
import com.wire.kalium.logic.feature.call.usecase.ObserveEndCallDueToConversationDegradationUseCase
Expand Down Expand Up @@ -229,7 +230,7 @@ class CallsScope internal constructor(
val observeEndCallDueToDegradationDialog: ObserveEndCallDueToConversationDegradationUseCase
get() = ObserveEndCallDueToConversationDegradationUseCaseImpl(EndCallResultListenerImpl)
val observeAskCallFeedbackUseCase: ObserveAskCallFeedbackUseCase
get() = ObserveAskCallFeedbackUseCase(EndCallResultListenerImpl)
get() = observeAskCallFeedbackUseCase(EndCallResultListenerImpl)

private val shouldAskCallFeedback: ShouldAskCallFeedbackUseCase by lazy {
ShouldAskCallFeedbackUseCase(userConfigRepository)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.wire.kalium.logic.feature.call.usecase
import com.wire.kalium.logic.data.call.CallMetadata
import com.wire.kalium.logic.data.call.CallRepository
import com.wire.kalium.logic.data.call.CallScreenSharingMetadata
import com.wire.kalium.logic.data.call.CallStatus
import com.wire.kalium.logic.data.call.RecentlyEndedCallMetadata
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.id.SelfTeamIdProvider
Expand Down Expand Up @@ -57,7 +58,7 @@ class CreateAndPersistRecentlyEndedCallMetadataUseCaseImpl internal constructor(
val conversationServicesCount = conversationMembers.count { member -> member.user.userType == UserType.SERVICE }
val guestsCount = conversationMembers.count { member -> member.user.userType == UserType.GUEST }
val guestsProCount = conversationMembers.count { member -> member.user.userType == UserType.GUEST && member.user.teamId != null }
val isOutgoingCall = callerId.value == selfCallUser?.id?.value
val isOutgoingCall = callStatus == CallStatus.STARTED
val callDurationInSeconds = establishedTime?.let {
DateTimeUtil.calculateMillisDifference(it, DateTimeUtil.currentIsoDateTimeString()) / MILLIS_IN_SECOND
} ?: 0L
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@
*/
package com.wire.kalium.logic.feature.call.usecase

import com.wire.kalium.logic.feature.user.ShouldAskCallFeedbackUseCaseResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow

interface EndCallResultListener {
suspend fun observeCallEndedResult(): Flow<EndCallResult>
suspend fun onCallEndedBecauseOfVerificationDegraded()
suspend fun onCallEndedAskForFeedback(shouldAsk: Boolean)
suspend fun onCallEndedAskForFeedback(shouldAskCallFeedback: ShouldAskCallFeedbackUseCaseResult)
}

/**
Expand All @@ -39,12 +40,12 @@ object EndCallResultListenerImpl : EndCallResultListener {
conversationCallEnded.emit(EndCallResult.VerificationDegraded)
}

override suspend fun onCallEndedAskForFeedback(shouldAsk: Boolean) {
conversationCallEnded.emit(EndCallResult.AskForFeedback(shouldAsk))
override suspend fun onCallEndedAskForFeedback(shouldAskCallFeedback: ShouldAskCallFeedbackUseCaseResult) {
conversationCallEnded.emit(EndCallResult.AskForFeedback(shouldAskCallFeedback))
}
}

sealed class EndCallResult {
data object VerificationDegraded : EndCallResult()
data class AskForFeedback(val shouldAsk: Boolean) : EndCallResult()
data class AskForFeedback(val shouldAskCallFeedback: ShouldAskCallFeedbackUseCaseResult) : EndCallResult()
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import com.wire.kalium.util.KaliumDispatcher
import com.wire.kalium.util.KaliumDispatcherImpl
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import kotlinx.datetime.toInstant

/**
* This use case is responsible for ending a call.
Expand Down Expand Up @@ -56,7 +57,7 @@ internal class EndCallUseCaseImpl(
* @param conversationId the id of the conversation for the call should be ended.
*/
override suspend operator fun invoke(conversationId: ConversationId) = withContext(dispatchers.default) {
callRepository.callsFlow().first().find {
val endedCall = callRepository.callsFlow().first().find {
// This use case can be invoked while joining the call or when the call is established.
it.conversationId == conversationId && it.status in listOf(
CallStatus.STARTED,
Expand All @@ -72,10 +73,11 @@ internal class EndCallUseCaseImpl(
callingLogger.d("[EndCallUseCase] -> Updating call status to CLOSED")
callRepository.updateCallStatusById(conversationId, CallStatus.CLOSED)
}
it
}

callManager.value.endCall(conversationId)
callRepository.updateIsCameraOnById(conversationId, false)
endCallListener.onCallEndedAskForFeedback(shouldAskCallFeedback())
endCallListener.onCallEndedAskForFeedback(shouldAskCallFeedback(endedCall?.establishedTime?.toInstant()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,26 @@
*/
package com.wire.kalium.logic.feature.call.usecase

import com.wire.kalium.logic.feature.user.ShouldAskCallFeedbackUseCaseResult
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
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)
* Use case to observe if we should ask for feedback after the call has ended.
*/
interface ObserveAskCallFeedbackUseCase {
/**
* @return [Flow] that emits only when the call was ended because of degradation of conversation verification status (Proteus or MLS)
* @return [Flow] that emits [ShouldAskCallFeedbackUseCaseResult] when the call has ended and we should ask for feedback.
*/
suspend operator fun invoke(): Flow<Boolean>
suspend operator fun invoke(): Flow<ShouldAskCallFeedbackUseCaseResult>
}

@Suppress("FunctionNaming")
internal fun ObserveAskCallFeedbackUseCase(
internal fun observeAskCallFeedbackUseCase(
endCallListener: EndCallResultListener
) = object : ObserveAskCallFeedbackUseCase {
override suspend fun invoke(): Flow<Boolean> =
override suspend fun invoke(): Flow<ShouldAskCallFeedbackUseCaseResult> =
endCallListener.observeCallEndedResult()
.filterIsInstance(EndCallResult.AskForFeedback::class)
.map { it.shouldAsk }
.map { it.shouldAskCallFeedback }
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*
* 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.user

Expand All @@ -24,23 +25,60 @@ import com.wire.kalium.util.DateTimeUtil
import kotlinx.datetime.Instant

/**
* Use case that returns [Boolean] if user should be asked for a feedback about call quality or not.
* Use case to determine if the call feedback should be asked.
*/
interface ShouldAskCallFeedbackUseCase {
/**
* @return [Boolean] if user should be asked for a feedback about call quality or not.
*/
suspend operator fun invoke(): Boolean
suspend operator fun invoke(
establishedTime: Instant?,
currentTime: Instant = DateTimeUtil.currentInstant()
): ShouldAskCallFeedbackUseCaseResult
}

@Suppress("FunctionNaming")
internal fun ShouldAskCallFeedbackUseCase(
userConfigRepository: UserConfigRepository
) = object : ShouldAskCallFeedbackUseCase {

override suspend fun invoke(): Boolean =
userConfigRepository.getNextTimeForCallFeedback().map {
override suspend fun invoke(
establishedTime: Instant?,
currentTime: Instant
): ShouldAskCallFeedbackUseCaseResult {
val callDurationInSeconds = establishedTime?.let {
DateTimeUtil.calculateMillisDifference(it, currentTime) / MILLIS_IN_SECOND
} ?: 0L

return when {
callDurationInSeconds in 1..<ONE_MINUTE -> {
ShouldAskCallFeedbackUseCaseResult.ShouldNotAskCallFeedback.CallDurationIsLessThanOneMinute(callDurationInSeconds)
}

!isNextTimeForCallFeedbackReached() -> {
ShouldAskCallFeedbackUseCaseResult.ShouldNotAskCallFeedback.NextTimeForCallFeedbackIsNotReached(callDurationInSeconds)
}

else -> {
ShouldAskCallFeedbackUseCaseResult.ShouldAskCallFeedback(callDurationInSeconds)
}
}
}

private suspend fun isNextTimeForCallFeedbackReached(): Boolean {
return userConfigRepository.getNextTimeForCallFeedback().map {
it > 0L && DateTimeUtil.currentInstant() > Instant.fromEpochMilliseconds(it)
}.getOrElse(true)
}
}

sealed class ShouldAskCallFeedbackUseCaseResult {
data class ShouldAskCallFeedback(val callDurationInSeconds: Long) : ShouldAskCallFeedbackUseCaseResult()
sealed class ShouldNotAskCallFeedback(val reason: String) : ShouldAskCallFeedbackUseCaseResult() {
data class CallDurationIsLessThanOneMinute(val callDurationInSeconds: Long) :
ShouldNotAskCallFeedback("Call duration is less than 1 minute")

data class NextTimeForCallFeedbackIsNotReached(val callDurationInSeconds: Long) :
ShouldNotAskCallFeedback("Next time for call feedback is not reached")
}
}

private const val MILLIS_IN_SECOND = 1_000L
private const val ONE_MINUTE = 60
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
package com.wire.kalium.logic.sync.receiver.conversation.message

import com.wire.kalium.common.error.CoreFailure
import com.wire.kalium.common.error.E2EIFailure
import com.wire.kalium.common.error.MLSFailure
import com.wire.kalium.common.error.NetworkFailure
import com.wire.kalium.common.error.ProteusFailure
import com.wire.kalium.common.error.StorageFailure

sealed class MLSMessageFailureResolution {
data object Ignore : MLSMessageFailureResolution()
Expand All @@ -31,20 +35,45 @@ internal object MLSMessageFailureHandler {
return when (failure) {
// Received messages targeting a future epoch (outside epoch bounds), we might have lost messages.
is MLSFailure.WrongEpoch -> MLSMessageFailureResolution.OutOfSync

// Received already sent or received message, can safely be ignored.
is MLSFailure.DuplicateMessage -> MLSMessageFailureResolution.Ignore
// Received message was targeting a future epoch and been buffered, can safely be ignored.
is MLSFailure.BufferedFutureMessage -> MLSMessageFailureResolution.Ignore
// Received self commit, any unmerged group has know been when merged by CoreCrypto.
is MLSFailure.SelfCommitIgnored -> MLSMessageFailureResolution.Ignore
// Message arrive in an unmerged group, it has been buffered and will be consumed later.
is MLSFailure.UnmergedPendingGroup -> MLSMessageFailureResolution.Ignore
is MLSFailure.StaleProposal -> MLSMessageFailureResolution.Ignore
is MLSFailure.StaleCommit -> MLSMessageFailureResolution.Ignore
is MLSFailure.MessageEpochTooOld -> MLSMessageFailureResolution.Ignore
is MLSFailure.InternalErrors -> MLSMessageFailureResolution.Ignore
is MLSFailure.Disabled -> MLSMessageFailureResolution.Ignore
else -> MLSMessageFailureResolution.InformUser
is MLSFailure.DuplicateMessage,
// Received message was targeting a future epoch and been buffered, can safely be ignored.
is MLSFailure.BufferedFutureMessage,
// Received self commit, any unmerged group has know been when merged by CoreCrypto.
is MLSFailure.SelfCommitIgnored,
// Message arrive in an unmerged group, it has been buffered and will be consumed later.
is MLSFailure.UnmergedPendingGroup,
is MLSFailure.StaleProposal,
is MLSFailure.StaleCommit,
is MLSFailure.MessageEpochTooOld,
is MLSFailure.InternalErrors,
is MLSFailure.Disabled,
MLSFailure.CommitForMissingProposal,
is CoreFailure.DevelopmentAPINotAllowedOnProduction -> MLSMessageFailureResolution.Ignore

MLSFailure.ConversationAlreadyExists,
MLSFailure.ConversationDoesNotSupportMLS,
is MLSFailure.Generic,
is MLSFailure.Other,
is E2EIFailure,
is CoreFailure.FeatureFailure,
CoreFailure.MissingClientRegistration,
is CoreFailure.MissingKeyPackages,
NetworkFailure.FeatureNotSupported,
is NetworkFailure.FederatedBackendFailure.ConflictingBackends,
is NetworkFailure.FederatedBackendFailure.FailedDomains,
is NetworkFailure.FederatedBackendFailure.FederationDenied,
is NetworkFailure.FederatedBackendFailure.FederationNotEnabled,
is NetworkFailure.FederatedBackendFailure.General,
is NetworkFailure.NoNetworkConnection,
is NetworkFailure.ProxyError,
is NetworkFailure.ServerMiscommunication,
is ProteusFailure,
StorageFailure.DataNotFound,
is StorageFailure.Generic,
is CoreFailure.Unknown -> MLSMessageFailureResolution.InformUser

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -575,7 +575,7 @@ class ConversationGroupRepositoryTest {
conversationGroupRepository.addService(serviceID, TestConversation.ID)
.shouldFail {
assertIs<MLSFailure.Generic>(it)
assertIs<UnsupportedOperationException>(it.exception)
assertIs<UnsupportedOperationException>(it.rootCause)
}

coVerify {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,12 +161,29 @@ class CreateAndPersistRecentlyEndedCallMetadataUseCaseTest {

fun withOutgoingCall() = apply {
every { callRepository.getCallMetadataProfile() }
.returns(CallMetadataProfile(mapOf(CONVERSATION_ID to callMetadata())))
.returns(
CallMetadataProfile(
mapOf(
CONVERSATION_ID to callMetadata().copy(
callStatus = CallStatus.STARTED
)
)
)
)
}

fun withIncomingCall() = apply {
every { callRepository.getCallMetadataProfile() }
.returns(CallMetadataProfile(mapOf(CONVERSATION_ID to callMetadata().copy(callerId = CALLER_ID.copy(value = "external")))))
.returns(
CallMetadataProfile(
mapOf(
CONVERSATION_ID to callMetadata().copy(
callerId = CALLER_ID.copy(value = "external"),
callStatus = CallStatus.INCOMING
)
)
)
)
}

suspend fun withConversationMembers() = apply {
Expand Down
Loading

0 comments on commit 9cc5541

Please sign in to comment.