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 callId: String
val isOutgoing: Boolean val isOutgoing: Boolean
val roomId: String val roomId: String
val opponentUserId: String
val isVideoCall: Boolean 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 const val VOIP_PROTO_VERSION = 1
} }
val ourPartyId: String
var opponentPartyId: Optional<String>?
var opponentVersion: Int
var capabilities: CallCapabilities?
var state: CallState 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.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent 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.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.CallHangupContent
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
import org.matrix.android.sdk.api.session.room.model.call.CallNegotiateContent 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.di.UserId
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import timber.log.Timber import timber.log.Timber
import java.math.BigDecimal
import javax.inject.Inject import javax.inject.Inject
@SessionScope @SessionScope
@ -192,6 +190,9 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
// Ignore remote echo // Ignore remote echo
return return
} }
if (event.roomId == null || event.senderId == null) {
return
}
if (event.senderId == userId) { if (event.senderId == userId) {
// discard current call, it's answered by another of my session // discard current call, it's answered by another of my session
activeCallHandler.removeCall(call.callId) 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}") Timber.v("Ignoring answer from party ID ${content.partyId} we already have an answer from ${call.opponentPartyId}")
return return
} }
call.apply { mxCallFactory.updateOutgoingCallWithOpponentData(call, event.senderId, content, content.capabilities)
opponentPartyId = Optional.from(content.partyId)
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
capabilities = content.capabilities ?: CallCapabilities()
}
callListenersDispatcher.onCallAnswerReceived(content) 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.call.MxCall
import org.matrix.android.sdk.api.session.room.model.call.CallCapabilities 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.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.DeviceId
import org.matrix.android.sdk.internal.di.UserId 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.call.model.MxCallImpl
import org.matrix.android.sdk.internal.session.profile.GetProfileInfoTask 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.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import java.math.BigDecimal
import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
internal class MxCallFactory @Inject constructor( internal class MxCallFactory @Inject constructor(
@ -49,16 +47,13 @@ internal class MxCallFactory @Inject constructor(
roomId = roomId, roomId = roomId,
userId = userId, userId = userId,
ourPartyId = deviceId ?: "", ourPartyId = deviceId ?: "",
opponentUserId = opponentUserId,
isVideoCall = content.isVideo(), isVideoCall = content.isVideo(),
localEchoEventFactory = localEchoEventFactory, localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor, eventSenderProcessor = eventSenderProcessor,
matrixConfiguration = matrixConfiguration, matrixConfiguration = matrixConfiguration,
getProfileInfoTask = getProfileInfoTask getProfileInfoTask = getProfileInfoTask
).apply { ).apply {
opponentPartyId = Optional.from(content.partyId) updateOpponentData(opponentUserId, content, content.capabilities)
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
capabilities = content.capabilities ?: CallCapabilities()
} }
} }
@ -69,12 +64,18 @@ internal class MxCallFactory @Inject constructor(
roomId = roomId, roomId = roomId,
userId = userId, userId = userId,
ourPartyId = deviceId ?: "", ourPartyId = deviceId ?: "",
opponentUserId = opponentUserId,
isVideoCall = isVideoCall, isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory, localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor, eventSenderProcessor = eventSenderProcessor,
matrixConfiguration = matrixConfiguration, matrixConfiguration = matrixConfiguration,
getProfileInfoTask = getProfileInfoTask 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.CallRejectContent
import org.matrix.android.sdk.api.session.room.model.call.CallReplacesContent 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.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.session.room.model.call.SdpType
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService 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.LocalEchoEventFactory
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.math.BigDecimal
internal class MxCallImpl( internal class MxCallImpl(
override val callId: String, override val callId: String,
override val isOutgoing: Boolean, override val isOutgoing: Boolean,
override val roomId: String, override val roomId: String,
private val userId: String, private val userId: String,
override val opponentUserId: String,
override val isVideoCall: Boolean, override val isVideoCall: Boolean,
override val ourPartyId: String, override val ourPartyId: String,
private val localEchoEventFactory: LocalEchoEventFactory, private val localEchoEventFactory: LocalEchoEventFactory,
@ -62,8 +62,16 @@ internal class MxCallImpl(
override var opponentPartyId: Optional<String>? = null override var opponentPartyId: Optional<String>? = null
override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
override lateinit var opponentUserId: String
override var capabilities: CallCapabilities? = null 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 override var state: CallState = CallState.Idle
set(value) { set(value) {
field = value field = value

View File

@ -96,8 +96,8 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
) )
} else { } else {
call?.transferToUser(action.selectedUserId, null) call?.transferToUser(action.selectedUserId, null)
_viewEvents.post(CallTransferViewEvents.Dismiss)
} }
_viewEvents.post(CallTransferViewEvents.Dismiss)
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(CallTransferViewEvents.FailToTransfer) _viewEvents.post(CallTransferViewEvents.FailToTransfer)
} }
@ -118,8 +118,8 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
) )
} else { } else {
call?.transferToUser(result.userId, result.roomId) call?.transferToUser(result.userId, result.roomId)
_viewEvents.post(CallTransferViewEvents.Dismiss)
} }
_viewEvents.post(CallTransferViewEvents.Dismiss)
} catch (failure: Throwable) { } catch (failure: Throwable) {
_viewEvents.post(CallTransferViewEvents.FailToTransfer) _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( mxCall.transfer(
targetUserId = targetUserId, targetUserId = targetUserId,
targetRoomId = targetRoomId, targetRoomId = targetRoomId,
createCallId = CallIdGenerator.generate(), createCallId = CallIdGenerator.generate(),
awaitCallId = null 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() val newCallId = CallIdGenerator.generate()
transferTargetCall.mxCall.transfer( transferTargetCall.mxCall.transfer(
targetUserId = this@WebRtcCall.mxCall.opponentUserId, targetUserId = this@WebRtcCall.mxCall.opponentUserId,
@ -314,8 +314,8 @@ class WebRtcCall(val mxCall: MxCall,
createCallId = newCallId, createCallId = newCallId,
awaitCallId = null awaitCallId = null
) )
this@WebRtcCall.endCall(true, CallHangupContent.Reason.REPLACED) this@WebRtcCall.endCall(sendEndSignaling = false)
transferTargetCall.endCall(true, CallHangupContent.Reason.REPLACED) transferTargetCall.endCall(sendEndSignaling = false)
} }
fun acceptIncomingCall() { 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) { if (mxCall.state == CallState.Terminated) {
return return
} }
@ -773,9 +773,9 @@ class WebRtcCall(val mxCall: MxCall,
mxCall.state = CallState.Terminated mxCall.state = CallState.Terminated
sessionScope?.launch(dispatcher) { sessionScope?.launch(dispatcher) {
release() release()
onCallEnded(callId)
} }
onCallEnded(callId) if (sendEndSignaling) {
if (originatedByMe) {
if (wasRinging) { if (wasRinging) {
mxCall.reject() mxCall.reject()
} else { } else {

View File

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