Call transfer: makes call transfer working properly

This commit is contained in:
ganfra 2021-05-27 16:00:32 +02:00
parent 90ccc3006d
commit bcc360692e
7 changed files with 55 additions and 54 deletions

View file

@ -26,8 +26,12 @@ interface MxCallDetail {
val callId: String
val isOutgoing: Boolean
val roomId: String
val opponentUserId: String
val isVideoCall: Boolean
val ourPartyId: String
val opponentPartyId: Optional<String>?
val opponentVersion: Int
val opponentUserId: String
val capabilities: CallCapabilities?
}
/**
@ -39,12 +43,6 @@ interface MxCall : MxCallDetail {
const val VOIP_PROTO_VERSION = 1
}
val ourPartyId: String
var opponentPartyId: Optional<String>?
var opponentVersion: Int
var capabilities: CallCapabilities?
var state: CallState
/**

View file

@ -24,7 +24,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
@ -35,7 +34,6 @@ import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber
import java.math.BigDecimal
import javax.inject.Inject
@SessionScope
@ -192,6 +190,9 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
// Ignore remote echo
return
}
if (event.roomId == null || event.senderId == null) {
return
}
if (event.senderId == userId) {
// discard current call, it's answered by another of my session
activeCallHandler.removeCall(call.callId)
@ -201,11 +202,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
return
}
call.apply {
opponentPartyId = Optional.from(content.partyId)
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
capabilities = content.capabilities ?: CallCapabilities()
}
mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities)
callListenersDispatcher.onCallAnswerReceived(content)
}
}

View file

@ -21,15 +21,13 @@ import org.matrix.android.sdk.api.session.call.CallIdGenerator
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import java.math.BigDecimal
import java.util.UUID
import javax.inject.Inject
internal class MxCallFactory @Inject constructor(
@ -49,16 +47,13 @@ internal class MxCallFactory @Inject constructor(
roomId = roomId,
userId = userId,
ourPartyId = deviceId ?: "",
opponentUserId = opponentUserId,
isVideoCall = content.isVideo(),
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor,
matrixConfiguration = matrixConfiguration,
getProfileInfoTask = getProfileInfoTask
).apply {
opponentPartyId = Optional.from(content.partyId)
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
capabilities = content.capabilities ?: CallCapabilities()
updateOpponentData(opponentUserId, content, content.capabilities)
}
}
@ -69,12 +64,18 @@ internal class MxCallFactory @Inject constructor(
roomId = roomId,
userId = userId,
ourPartyId = deviceId ?: "",
opponentUserId = opponentUserId,
isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor,
matrixConfiguration = matrixConfiguration,
getProfileInfoTask = getProfileInfoTask
)
).apply {
// Setup with this userId, might be updated when processing the Answer event
this.opponentUserId = opponentUserId
}
}
fun updateOutgoingCallWithOpponentData(call: MxCall, userId: String, content: CallSignalingContent, callCapabilities: CallCapabilities?) {
(call as? MxCallImpl)?.updateOpponentData(userId, content, callCapabilities)
}
}

View file

@ -37,6 +37,7 @@ import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent
import org.matrix.android.sdk.api.session.room.model.call.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent
import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallSignalingContent
import org.matrix.android.sdk.api.session.room.model.call.SdpType
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
@ -44,14 +45,13 @@ import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import timber.log.Timber
import java.util.UUID
import java.math.BigDecimal
internal class MxCallImpl(
override val callId: String,
override val isOutgoing: Boolean,
override val roomId: String,
private val userId: String,
override val opponentUserId: String,
override val isVideoCall: Boolean,
override val ourPartyId: String,
private val localEchoEventFactory: LocalEchoEventFactory,
@ -62,8 +62,16 @@ internal class MxCallImpl(
override var opponentPartyId: Optional<String>? = null
override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
override lateinit var opponentUserId: String
override var capabilities: CallCapabilities? = null
fun updateOpponentData(userId: String, content: CallSignalingContent, callCapabilities: CallCapabilities?) {
opponentPartyId = Optional.from(content.partyId)
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
opponentUserId = userId
capabilities = callCapabilities ?: CallCapabilities()
}
override var state: CallState = CallState.Idle
set(value) {
field = value

View file

@ -96,8 +96,8 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
)
} else {
call?.transferToUser(action.selectedUserId, null)
_viewEvents.post(CallTransferViewEvents.Dismiss)
}
_viewEvents.post(CallTransferViewEvents.Dismiss)
} catch (failure: Throwable) {
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
}
@ -118,8 +118,8 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
)
} else {
call?.transferToUser(result.userId, result.roomId)
_viewEvents.post(CallTransferViewEvents.Dismiss)
}
_viewEvents.post(CallTransferViewEvents.Dismiss)
} catch (failure: Throwable) {
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
}

View file

@ -290,17 +290,17 @@ class WebRtcCall(val mxCall: MxCall,
}
}
suspend fun transferToUser(targetUserId: String, targetRoomId: String?) = withContext(dispatcher){
suspend fun transferToUser(targetUserId: String, targetRoomId: String?) {
mxCall.transfer(
targetUserId = targetUserId,
targetRoomId = targetRoomId,
createCallId = CallIdGenerator.generate(),
awaitCallId = null
)
endCall(true, CallHangupContent.Reason.REPLACED)
endCall(sendEndSignaling = false)
}
suspend fun transferToCall(transferTargetCall: WebRtcCall)= withContext(dispatcher) {
suspend fun transferToCall(transferTargetCall: WebRtcCall) {
val newCallId = CallIdGenerator.generate()
transferTargetCall.mxCall.transfer(
targetUserId = this@WebRtcCall.mxCall.opponentUserId,
@ -314,8 +314,8 @@ class WebRtcCall(val mxCall: MxCall,
createCallId = newCallId,
awaitCallId = null
)
this@WebRtcCall.endCall(true, CallHangupContent.Reason.REPLACED)
transferTargetCall.endCall(true, CallHangupContent.Reason.REPLACED)
this@WebRtcCall.endCall(sendEndSignaling = false)
transferTargetCall.endCall(sendEndSignaling = false)
}
fun acceptIncomingCall() {
@ -758,7 +758,7 @@ class WebRtcCall(val mxCall: MxCall,
}
}
fun endCall(originatedByMe: Boolean = true, reason: CallHangupContent.Reason? = null) {
fun endCall(sendEndSignaling: Boolean = true, reason: CallHangupContent.Reason? = null) {
if (mxCall.state == CallState.Terminated) {
return
}
@ -773,9 +773,9 @@ class WebRtcCall(val mxCall: MxCall,
mxCall.state = CallState.Terminated
sessionScope?.launch(dispatcher) {
release()
onCallEnded(callId)
}
onCallEnded(callId)
if (originatedByMe) {
if (sendEndSignaling) {
if (wasRinging) {
mxCall.reject()
} else {

View file

@ -146,6 +146,7 @@ class WebRtcCallManager @Inject constructor(
private val advertisedCalls = HashSet<String>()
private val callsByCallId = ConcurrentHashMap<String, WebRtcCall>()
private val callsByRoomId = ConcurrentHashMap<String, MutableList<WebRtcCall>>()
// Calls started as an attended transfer, ie. with the intention of transferring another
// call with a different party to this one.
// callId (target) -> call (transferee)
@ -242,30 +243,26 @@ class WebRtcCallManager @Inject constructor(
val otherCall = getCalls().lastOrNull()
currentCall.setAndNotify(otherCall)
}
// This must be done in this thread
executor.execute {
// There is no active calls
if (getCurrentCall() == null) {
Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one")
peerConnectionFactory?.dispose()
peerConnectionFactory = null
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
// did we start background sync? so we should stop it
if (isInBackground) {
if (FcmHelper.isPushSupported()) {
currentSession?.stopAnyBackgroundSync()
} else {
// for fdroid we should not stop, it should continue syncing
// maybe we should restore default timeout/delay though?
}
// There is no active calls
if (getCurrentCall() == null) {
Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one")
peerConnectionFactory?.dispose()
peerConnectionFactory = null
audioManager.setMode(CallAudioManager.Mode.DEFAULT)
// did we start background sync? so we should stop it
if (isInBackground) {
if (FcmHelper.isPushSupported()) {
currentSession?.stopAnyBackgroundSync()
} else {
// for fdroid we should not stop, it should continue syncing
// maybe we should restore default timeout/delay though?
}
}
Timber.v("## VOIP WebRtcPeerConnectionManager close() executor done")
}
}
suspend fun startOutgoingCall(nativeRoomId: String, otherUserId: String, isVideoCall: Boolean, transferee: WebRtcCall? = null) {
val signalingRoomId = callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId
val signalingRoomId = callUserMapper?.getOrCreateVirtualRoomForRoom(nativeRoomId, otherUserId) ?: nativeRoomId
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
if (getCallsByRoomId(nativeRoomId).isNotEmpty()) {
Timber.w("## VOIP you already have a call in this room")
@ -283,7 +280,7 @@ class WebRtcCallManager @Inject constructor(
val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
val webRtcCall = createWebRtcCall(mxCall, nativeRoomId)
currentCall.setAndNotify(webRtcCall)
if(transferee != null){
if (transferee != null) {
transferees[webRtcCall.callId] = transferee
}
CallService.onOutgoingCallRinging(