VoIP: start to handle call transfer in SDK

This commit is contained in:
ganfra 2020-12-17 14:33:22 +01:00
parent 69cc4f83bb
commit 9c5fe81792
10 changed files with 160 additions and 9 deletions

View file

@ -35,7 +35,11 @@ data class MatrixConfiguration(
* Optional proxy to connect to the matrix servers
* You can create one using for instance Proxy(proxyType, InetSocketAddress.createUnresolved(hostname, port)
*/
val proxy: Proxy? = null
val proxy: Proxy? = null,
/**
* True to advertise support for call transfers to other parties on Matrix calls.
*/
val supportsCallTransfer: Boolean = true
) {
/**

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.api.session.call
import org.matrix.android.sdk.api.session.room.model.call.CallCandidate
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.SdpType
import org.matrix.android.sdk.api.util.Optional
@ -42,6 +43,8 @@ interface MxCall : MxCallDetail {
var opponentPartyId: Optional<String>?
var opponentVersion: Int
var capabilities: CallCapabilities?
var state: CallState
/**

View file

@ -72,6 +72,7 @@ object EventType {
const val CALL_NEGOTIATE = "m.call.negotiate"
const val CALL_REJECT = "m.call.reject"
const val CALL_HANGUP = "m.call.hangup"
const val CALL_REPLACES = "m.call.replaces"
// Key share events
const val ROOM_KEY_REQUEST = "m.room_key_request"

View file

@ -39,7 +39,11 @@ data class CallAnswerContent(
/**
* Required. The version of the VoIP specification this messages adheres to.
*/
@Json(name = "version") override val version: String?
@Json(name = "version") override val version: String?,
/**
* Capability advertisement.
*/
@Json(name= "capabilities") val capabilities: CallCapabilities? = null
): CallSignallingContent {
@JsonClass(generateAdapter = true)

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.model.call
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.extensions.orFalse
@JsonClass(generateAdapter = true)
data class CallCapabilities(
/**
* If set to true, states that the sender of the event supports the m.call.replaces event and therefore supports
* being transferred to another destination
*/
@Json(name = "m.call.transferee") val transferee: Boolean? = null
)
fun CallCapabilities?.supportCallTransfer() = this?.transferee.orFalse()

View file

@ -49,7 +49,11 @@ data class CallInviteContent(
/**
* The field should be added for all invites where the target is a specific user
*/
@Json(name = "invitee") val invitee: String? = null
@Json(name = "invitee") val invitee: String? = null,
/**
* Capability advertisement.
*/
@Json(name= "capabilities") val capabilities: CallCapabilities? = null
): CallSignallingContent {
@JsonClass(generateAdapter = true)

View file

@ -0,0 +1,81 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.room.model.call
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
/**
* This event is sent to signal the intent of a participant in a call to replace the call with another,
* such that the other participant ends up in a call with a new user.
*/
@JsonClass(generateAdapter = true)
data class CallReplacesContent(
/**
* Required. The ID of the call this event relates to.
*/
@Json(name = "call_id") override val callId: String,
/**
* Required. ID to let user identify remote echo of their own events
*/
@Json(name = "party_id") override val partyId: String? = null,
/**
* An identifier for the call replacement itself, generated by the transferor.
*/
@Json(name = "replacement_id") val replacementId: String? = null,
/**
* Optional. If specified, the transferee client waits for an invite to this room and joins it (possibly waiting for user confirmation) and then continues the transfer in this room.
* If absent, the transferee contacts the Matrix User ID given in the target_user field in a room of its choosing.
*/
@Json(name = "target_room") val targerRoomId: String? = null,
/**
* An object giving information about the transfer target
*/
@Json(name = "target_user") val targetUser: TargetUser? = null,
/**
* If specified, gives the call ID for the transferee's client to use when placing the replacement call.
* Mutually exclusive with await_call
*/
@Json(name = "create_call") val createCall: String? = null,
/**
* If specified, gives the call ID that the transferee's client should wait for.
* Mutually exclusive with create_call.
*/
@Json(name = "await_call") val awaitCall: String? = null,
/**
* Required. The version of the VoIP specification this messages adheres to.
*/
@Json(name = "version") override val version: String?
): CallSignallingContent {
@JsonClass(generateAdapter = true)
data class TargetUser(
/**
* Required. The matrix user ID of the transfer target
*/
@Json(name = "id") val id: String,
/**
* Optional. The display name of the transfer target.
*/
@Json(name = "display_name") val displayName: String,
/**
* Optional. The avatar URL of the transfer target.
*/
@Json(name = "avatar_url") val avatarUrl: String
)
}

View file

@ -24,6 +24,7 @@ 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
@ -185,6 +186,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
call.apply {
opponentPartyId = Optional.from(content.partyId)
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
capabilities = content.capabilities ?: CallCapabilities()
}
callListenersDispatcher.onCallAnswerReceived(content)
}

View file

@ -16,7 +16,9 @@
package org.matrix.android.sdk.internal.session.call
import org.matrix.android.sdk.api.MatrixConfiguration
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.internal.di.DeviceId
@ -32,6 +34,7 @@ internal class MxCallFactory @Inject constructor(
@DeviceId private val deviceId: String?,
private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor,
private val matrixConfiguration: MatrixConfiguration,
@UserId private val userId: String
) {
@ -46,10 +49,12 @@ internal class MxCallFactory @Inject constructor(
opponentUserId = opponentUserId,
isVideoCall = content.isVideo(),
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor
eventSenderProcessor = eventSenderProcessor,
matrixConfiguration = matrixConfiguration
).apply {
opponentPartyId = Optional.from(content.partyId)
opponentVersion = content.version?.let { BigDecimal(it).intValueExact() } ?: MxCall.VOIP_PROTO_VERSION
capabilities = content.capabilities ?: CallCapabilities()
}
}
@ -63,7 +68,8 @@ internal class MxCallFactory @Inject constructor(
opponentUserId = opponentUserId,
isVideoCall = isVideoCall,
localEchoEventFactory = localEchoEventFactory,
eventSenderProcessor = eventSenderProcessor
eventSenderProcessor = eventSenderProcessor,
matrixConfiguration = matrixConfiguration
)
}
}

View file

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.call.model
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxCall
import org.matrix.android.sdk.api.session.events.model.Content
@ -27,6 +28,7 @@ import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.model.call.CallAnswerContent
import org.matrix.android.sdk.api.session.room.model.call.CallCandidate
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,8 +37,8 @@ import org.matrix.android.sdk.api.session.room.model.call.CallSelectAnswerConten
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
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
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
internal class MxCallImpl(
@ -48,11 +50,13 @@ internal class MxCallImpl(
override val isVideoCall: Boolean,
override val ourPartyId: String,
private val localEchoEventFactory: LocalEchoEventFactory,
private val eventSenderProcessor: EventSenderProcessor
private val eventSenderProcessor: EventSenderProcessor,
private val matrixConfiguration: MatrixConfiguration
) : MxCall {
override var opponentPartyId: Optional<String>? = null
override var opponentVersion: Int = MxCall.VOIP_PROTO_VERSION
override var capabilities: CallCapabilities? = null
override var state: CallState = CallState.Idle
set(value) {
@ -98,7 +102,8 @@ internal class MxCallImpl(
partyId = ourPartyId,
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
offer = CallInviteContent.Offer(sdp = sdpString),
version = MxCall.VOIP_PROTO_VERSION.toString()
version = MxCall.VOIP_PROTO_VERSION.toString(),
capabilities = buildCapabilities()
)
.let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
@ -158,7 +163,8 @@ internal class MxCallImpl(
callId = callId,
partyId = ourPartyId,
answer = CallAnswerContent.Answer(sdp = sdpString),
version = MxCall.VOIP_PROTO_VERSION.toString()
version = MxCall.VOIP_PROTO_VERSION.toString(),
capabilities = buildCapabilities()
)
.let { createEventAndLocalEcho(type = EventType.CALL_ANSWER, roomId = roomId, content = it.toContent()) }
.also { eventSenderProcessor.postEvent(it) }
@ -203,4 +209,12 @@ internal class MxCallImpl(
)
.also { localEchoEventFactory.createLocalEcho(it) }
}
private fun buildCapabilities(): CallCapabilities? {
return if (matrixConfiguration.supportsCallTransfer) {
CallCapabilities(true)
} else {
null
}
}
}