mirror of
https://github.com/element-hq/element-android
synced 2024-11-24 10:25:35 +03:00
Call transfer: makes call transfer working properly
This commit is contained in:
parent
90ccc3006d
commit
bcc360692e
7 changed files with 55 additions and 54 deletions
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
Loading…
Reference in a new issue