From 03b9904b07d3fdfdd5f3a358e92fa55ca8b9b5ce Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 28 May 2020 15:29:43 +0200 Subject: [PATCH] Create a MxCall interface to better handle call --- .../android/api/session/call/CallService.kt | 29 +---- .../android/api/session/call/CallsListener.kt | 2 +- .../matrix/android/api/session/call/MxCall.kt | 65 ++++++++++ .../session/call/DefaultCallService.kt | 118 +++++------------- .../internal/session/call/model/MxCallImpl.kt | 108 ++++++++++++++++ .../riotx/features/call/VectorCallActivity.kt | 10 +- .../features/call/VectorCallViewModel.kt | 3 +- .../call/WebRtcPeerConnectionManager.kt | 67 +++++----- .../call/service/CallHeadsUpService.kt | 7 +- .../call/service/CallHeadsUpServiceArgs.kt | 2 +- 10 files changed, 244 insertions(+), 167 deletions(-) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/MxCall.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/model/MxCallImpl.kt diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallService.kt index b16747048d..d94e84c7b9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallService.kt @@ -17,40 +17,15 @@ package im.vector.matrix.android.api.session.call import im.vector.matrix.android.api.MatrixCallback -import org.webrtc.IceCandidate -import org.webrtc.SessionDescription interface CallService { fun getTurnServer(callback: MatrixCallback) /** - * Start a call - * Send offer SDP to the other participant. - * @param callId a callId that the caller can create, it will be used to identify the call for other methods + * Create an outgoing call */ - fun startCall(callId: String, roomId: String, sdp: SessionDescription, callback: MatrixCallback) - - /** - * Accept an incoming call - * Send answer SDP to the other participant. - */ - fun pickUp(callId: String, roomId: String, sdp: SessionDescription, callback: MatrixCallback) - - /** - * Send Ice candidate to the other participant. - */ - fun sendLocalIceCandidates(callId: String, roomId: String, candidates: List) - - /** - * Send removed ICE candidates to the other participant. - */ - fun sendLocalIceCandidateRemovals(callId: String, roomId: String, candidates: List) - - /** - * Send a hangup event - */ - fun hangup(callId: String, roomId: String) + fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall fun addCallListener(listener: CallsListener) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallsListener.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallsListener.kt index 21ab8dbe93..3ab68ceb28 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallsListener.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/CallsListener.kt @@ -41,7 +41,7 @@ interface CallsListener { // */ // fun onCallHangUp(peerSignalingClient: PeerSignalingClient) - fun onCallInviteReceived(signalingRoomId: String, fromUserId: String, callInviteContent: CallInviteContent) + fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/MxCall.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/MxCall.kt new file mode 100644 index 0000000000..afb89b3bb8 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/call/MxCall.kt @@ -0,0 +1,65 @@ +/* + * 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 im.vector.matrix.android.api.session.call + +import org.webrtc.IceCandidate +import org.webrtc.SessionDescription + +interface MxCallDetail { + val isOutgoing: Boolean + val roomId: String + val otherUserId: String + val isVideoCall: Boolean +} + +/** + * Define both an incoming call and on outgoing call + */ +interface MxCall : MxCallDetail { + /** + * Pick Up the incoming call + * It has no effect on outgoing call + */ + fun accept(sdp: SessionDescription) + + /** + * Reject an incoming call + * It's an alias to hangUp + */ + fun reject() = hangUp() + + /** + * End the call + */ + fun hangUp() + + /** + * Start a call + * Send offer SDP to the other participant. + */ + fun offerSdp(sdp: SessionDescription) + + /** + * Send Ice candidate to the other participant. + */ + fun sendLocalIceCandidates(candidates: List) + + /** + * Send removed ICE candidates to the other participant. + */ + fun sendLocalIceCandidateRemovals(candidates: List) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/DefaultCallService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/DefaultCallService.kt index 55560228cb..b0c9ccc2c4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/DefaultCallService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/DefaultCallService.kt @@ -20,24 +20,20 @@ import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.extensions.tryThis import im.vector.matrix.android.api.session.call.CallService import im.vector.matrix.android.api.session.call.CallsListener +import im.vector.matrix.android.api.session.call.MxCall import im.vector.matrix.android.api.session.call.TurnServer -import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType -import im.vector.matrix.android.api.session.events.model.LocalEcho -import im.vector.matrix.android.api.session.events.model.UnsignedData -import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.room.model.call.CallAnswerContent -import im.vector.matrix.android.api.session.room.model.call.CallCandidatesContent import im.vector.matrix.android.api.session.room.model.call.CallHangupContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.session.SessionScope +import im.vector.matrix.android.internal.session.call.model.MxCallImpl import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.RoomEventSender -import org.webrtc.IceCandidate -import org.webrtc.SessionDescription +import java.util.UUID import javax.inject.Inject @SessionScope @@ -54,71 +50,17 @@ internal class DefaultCallService @Inject constructor( TODO("not implemented") // To change body of created functions use File | Settings | File Templates. } - override fun startCall(callId: String, roomId: String, sdp: SessionDescription, callback: MatrixCallback) { - val eventContent = CallInviteContent( - callId = callId, - lifetime = CALL_TIMEOUT_MS, - offer = CallInviteContent.Offer(sdp = sdp.description) + override fun createOutgoingCall(roomId: String, otherUserId: String, isVideoCall: Boolean): MxCall { + return MxCallImpl( + callId = UUID.randomUUID().toString(), + isOutgoing = true, + roomId = roomId, + userId = userId, + otherUserId = otherUserId, + isVideoCall = isVideoCall, + localEchoEventFactory = localEchoEventFactory, + roomEventSender = roomEventSender ) - - createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = eventContent.toContent()).let { event -> - roomEventSender.sendEvent(event) -// sendEventTask -// .configureWith( -// SendEventTask.Params(event = event, cryptoService = cryptoService) -// ) { -// this.callback = callback -// }.executeBy(taskExecutor) - } - } - - override fun pickUp(callId: String, roomId: String, sdp: SessionDescription, callback: MatrixCallback) { - val eventContent = CallAnswerContent( - callId = callId, - answer = CallAnswerContent.Answer(sdp = sdp.description) - ) - - createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = eventContent.toContent()).let { event -> - roomEventSender.sendEvent(event) -// sendEventTask -// .configureWith( -// SendEventTask.Params(event = event, cryptoService = cryptoService) -// ) { -// this.callback = callback -// }.executeBy(taskExecutor) - } - } - - override fun sendLocalIceCandidates(callId: String, roomId: String, candidates: List) { - val eventContent = CallCandidatesContent( - callId = callId, - candidates = candidates.map { - CallCandidatesContent.Candidate( - sdpMid = it.sdpMid, - sdpMLineIndex = it.sdpMLineIndex.toString(), - candidate = it.sdp - ) - } - ) - createEventAndLocalEcho(type = EventType.CALL_CANDIDATES, roomId = roomId, content = eventContent.toContent()).let { event -> - roomEventSender.sendEvent(event) -// sendEventTask -// .configureWith( -// SendEventTask.Params(event = event, cryptoService = cryptoService) -// ) { -// this.callback = callback -// }.executeBy(taskExecutor) - } - } - - override fun sendLocalIceCandidateRemovals(callId: String, roomId: String, candidates: List) { - } - - override fun hangup(callId: String, roomId: String) { - val eventContent = CallHangupContent(callId = callId) - createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = eventContent.toContent()).let { event -> - roomEventSender.sendEvent(event) - } } override fun addCallListener(listener: CallsListener) { @@ -137,8 +79,18 @@ internal class DefaultCallService @Inject constructor( } } EventType.CALL_INVITE -> { - event.getClearContent().toModel()?.let { - onCallInvite(event.roomId ?: "", event.senderId ?: "", it) + event.getClearContent().toModel()?.let { content -> + val incomingCall = MxCallImpl( + callId = content.callId ?: return@let, + isOutgoing = false, + roomId = event.roomId ?: return@let, + userId = userId, + otherUserId = event.senderId ?: return@let, + isVideoCall = content.isVideo(), + localEchoEventFactory = localEchoEventFactory, + roomEventSender = roomEventSender + ) + onCallInvite(incomingCall, content) } } EventType.CALL_HANGUP -> { @@ -165,31 +117,17 @@ internal class DefaultCallService @Inject constructor( } } - private fun onCallInvite(roomId: String, fromUserId: String, invite: CallInviteContent) { + private fun onCallInvite(incomingCall: MxCall, invite: CallInviteContent) { // Ignore the invitation from current user - if (fromUserId == userId) return + if (incomingCall.otherUserId == userId) return callListeners.toList().forEach { tryThis { - it.onCallInviteReceived(roomId, fromUserId, invite) + it.onCallInviteReceived(incomingCall, invite) } } } - private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event { - return Event( - roomId = roomId, - originServerTs = System.currentTimeMillis(), - senderId = userId, - eventId = localId, - type = type, - content = content, - unsignedData = UnsignedData(age = null, transactionId = localId) - ).also { - localEchoEventFactory.createLocalEcho(it) - } - } - companion object { const val CALL_TIMEOUT_MS = 120_000 } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/model/MxCallImpl.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/model/MxCallImpl.kt new file mode 100644 index 0000000000..82537b2e5b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/call/model/MxCallImpl.kt @@ -0,0 +1,108 @@ +/* + * 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 im.vector.matrix.android.internal.session.call.model + +import im.vector.matrix.android.api.session.call.MxCall +import im.vector.matrix.android.api.session.events.model.Content +import im.vector.matrix.android.api.session.events.model.Event +import im.vector.matrix.android.api.session.events.model.EventType +import im.vector.matrix.android.api.session.events.model.LocalEcho +import im.vector.matrix.android.api.session.events.model.UnsignedData +import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.call.CallAnswerContent +import im.vector.matrix.android.api.session.room.model.call.CallCandidatesContent +import im.vector.matrix.android.api.session.room.model.call.CallHangupContent +import im.vector.matrix.android.api.session.room.model.call.CallInviteContent +import im.vector.matrix.android.internal.session.call.DefaultCallService +import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory +import im.vector.matrix.android.internal.session.room.send.RoomEventSender +import org.webrtc.IceCandidate +import org.webrtc.SessionDescription + +internal class MxCallImpl( + val callId: String, + override val isOutgoing: Boolean, + override val roomId: String, + private val userId: String, + override val otherUserId: String, + override val isVideoCall: Boolean, + private val localEchoEventFactory: LocalEchoEventFactory, + private val roomEventSender: RoomEventSender +) : MxCall { + + override fun offerSdp(sdp: SessionDescription) { + if (!isOutgoing) return + + CallInviteContent( + callId = callId, + lifetime = DefaultCallService.CALL_TIMEOUT_MS, + offer = CallInviteContent.Offer(sdp = sdp.description) + ) + .let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) } + .also { roomEventSender.sendEvent(it) } + } + + override fun sendLocalIceCandidates(candidates: List) { + CallCandidatesContent( + callId = callId, + candidates = candidates.map { + CallCandidatesContent.Candidate( + sdpMid = it.sdpMid, + sdpMLineIndex = it.sdpMLineIndex.toString(), + candidate = it.sdp + ) + } + ) + .let { createEventAndLocalEcho(type = EventType.CALL_CANDIDATES, roomId = roomId, content = it.toContent()) } + .also { roomEventSender.sendEvent(it) } + } + + override fun sendLocalIceCandidateRemovals(candidates: List) { + } + + override fun hangUp() { + CallHangupContent( + callId = callId + ) + .let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) } + .also { roomEventSender.sendEvent(it) } + } + + override fun accept(sdp: SessionDescription) { + if (isOutgoing) return + + CallAnswerContent( + callId = callId, + answer = CallAnswerContent.Answer(sdp = sdp.description) + ) + .let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) } + .also { roomEventSender.sendEvent(it) } + } + + private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event { + return Event( + roomId = roomId, + originServerTs = System.currentTimeMillis(), + senderId = userId, + eventId = localId, + type = type, + content = content, + unsignedData = UnsignedData(age = null, transactionId = localId) + ) + .also { localEchoEventFactory.createLocalEcho(it) } + } +} diff --git a/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt b/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt index ba32959973..af1bea1d6c 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/VectorCallActivity.kt @@ -32,6 +32,7 @@ import butterknife.BindView import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel import im.vector.matrix.android.api.session.call.EglUtils +import im.vector.matrix.android.api.session.call.MxCallDetail import im.vector.riotx.R import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseActivity @@ -92,8 +93,6 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis private val iceCandidateSource: PublishSubject = PublishSubject.create() - - var callHeadsUpService: CallHeadsUpService? = null private val serviceConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { @@ -363,7 +362,7 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis //peerConnectionManager.answerReceived("", sdp) // peerConnection?.setRemoteDescription(object : SdpObserverAdapter() {}, sdp) } - is VectorCallViewEvents.CallHangup -> { + is VectorCallViewEvents.CallHangup -> { finish() } } @@ -416,9 +415,9 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis // mandatory.add(MediaConstraints.KeyValuePair("googHighpassFilter", "true")) // } - fun newIntent(context: Context, roomId: String, participantUserId: String, isIncomingCall: Boolean, isVideoCall: Boolean): Intent { + fun newIntent(context: Context, mxCall: MxCallDetail): Intent { return Intent(context, VectorCallActivity::class.java).apply { - putExtra(MvRx.KEY_ARG, CallArgs(roomId, participantUserId, isIncomingCall, isVideoCall)) + putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.otherUserId, !mxCall.isOutgoing, mxCall.isVideoCall)) } } } @@ -447,6 +446,5 @@ class VectorCallActivity : VectorBaseActivity(), WebRtcPeerConnectionManager.Lis } override fun sendOffer(sessionDescription: SessionDescription) { - } } diff --git a/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt b/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt index 7e7a9eef8e..296dfe5582 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/VectorCallViewModel.kt @@ -23,6 +23,7 @@ import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.AssistedInject import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.call.CallsListener +import im.vector.matrix.android.api.session.call.MxCall import im.vector.matrix.android.api.session.room.model.call.CallAnswerContent import im.vector.matrix.android.api.session.room.model.call.CallHangupContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent @@ -62,7 +63,7 @@ class VectorCallViewModel @AssistedInject constructor( } } - override fun onCallInviteReceived(signalingRoomId: String, fromUserId: String, callInviteContent: CallInviteContent) { + override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) { } override fun onCallHangupReceived(callHangupContent: CallHangupContent) { diff --git a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt index a45dadc122..a283414310 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/WebRtcPeerConnectionManager.kt @@ -22,10 +22,11 @@ import android.content.Intent import android.content.ServiceConnection import android.os.IBinder import androidx.core.content.ContextCompat -import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.extensions.tryThis import im.vector.matrix.android.api.session.call.CallsListener import im.vector.matrix.android.api.session.call.EglUtils +import im.vector.matrix.android.api.session.call.MxCall +import im.vector.matrix.android.api.session.call.MxCallDetail import im.vector.matrix.android.api.session.room.model.call.CallAnswerContent import im.vector.matrix.android.api.session.room.model.call.CallHangupContent import im.vector.matrix.android.api.session.room.model.call.CallInviteContent @@ -53,7 +54,6 @@ import org.webrtc.VideoSource import org.webrtc.VideoTrack import timber.log.Timber import java.lang.ref.WeakReference -import java.util.UUID import java.util.concurrent.Executors import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -114,10 +114,7 @@ class WebRtcPeerConnectionManager @Inject constructor( var callHeadsUpService: CallHeadsUpService? = null - private var callId: String? = null - private var signalingRoomId: String? = null - private var participantUserId: String? = null - private var isVideoCall: Boolean? = null + private var currentCall: MxCall? = null private val serviceConnection = object : ServiceConnection { override fun onServiceDisconnected(name: ComponentName?) { @@ -275,11 +272,8 @@ class WebRtcPeerConnectionManager @Inject constructor( .subscribe { // omit empty :/ if (it.isNotEmpty()) { - Timber.v("## Sending local ice candidates to callId: $callId roomId: $signalingRoomId") - sessionHolder - .getActiveSession() - .callService() - .sendLocalIceCandidates(callId ?: "", signalingRoomId ?: "", it) + Timber.v("## Sending local ice candidates to call") + currentCall?.sendLocalIceCandidates(it) } } } @@ -304,11 +298,8 @@ class WebRtcPeerConnectionManager @Inject constructor( peerConnection?.setLocalDescription(object : SdpObserverAdapter() { override fun onSetSuccess() { Timber.v("## setLocalDescription success") - val id = UUID.randomUUID().toString() - callId = id - Timber.v("## sending offer to callId: $id roomId: $signalingRoomId") - sessionHolder.getActiveSession().callService().startCall(id, signalingRoomId - ?: "", sessionDescription, object : MatrixCallback {}) + Timber.v("## sending offer") + currentCall?.offerSdp(sessionDescription) } }, sessionDescription) } @@ -330,7 +321,7 @@ class WebRtcPeerConnectionManager @Inject constructor( localMediaStream = peerConnectionFactory?.createLocalMediaStream("ARDAMS") // magic value? - if (isVideoCall == true) { + if (currentCall?.isVideoCall == true) { val cameraIterator = if (Camera2Enumerator.isSupported(context)) Camera2Enumerator(context) else Camera1Enumerator(false) val frontCamera = cameraIterator.deviceNames ?.firstOrNull { cameraIterator.isFrontFacing(it) } @@ -417,55 +408,55 @@ class WebRtcPeerConnectionManager @Inject constructor( } } - fun startOutgoingCall(context: Context, signalingRoomId: String, participantUserId: String, isVideoCall: Boolean) { - this.signalingRoomId = signalingRoomId - this.participantUserId = participantUserId - this.isVideoCall = isVideoCall + fun startOutgoingCall(context: Context, signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) { + val createdCall = sessionHolder.getSafeActiveSession()?.callService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return + currentCall = createdCall - startHeadsUpService(signalingRoomId, sessionHolder.getActiveSession().myUserId, false, isVideoCall) - context.startActivity(VectorCallActivity.newIntent(context, signalingRoomId, participantUserId, false, isVideoCall)) + startHeadsUpService(createdCall) + context.startActivity(VectorCallActivity.newIntent(context, createdCall)) startCall() sendSdpOffer() } - override fun onCallInviteReceived(signalingRoomId: String, fromUserId: String, callInviteContent: CallInviteContent) { - this.callId = callInviteContent.callId - this.signalingRoomId = signalingRoomId - this.participantUserId = fromUserId - this.isVideoCall = callInviteContent.isVideo() + override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) { + // TODO What if a call is currently active? + if (currentCall != null) { + Timber.w("TODO: Automatically reject incoming call?") + return + } - startHeadsUpService(signalingRoomId, fromUserId, true, callInviteContent.isVideo()) - context.startActivity(VectorCallActivity.newIntent(context, signalingRoomId, fromUserId, false, callInviteContent.isVideo())) + currentCall = mxCall + + startHeadsUpService(mxCall) + context.startActivity(VectorCallActivity.newIntent(context, mxCall)) startCall() } - private fun startHeadsUpService(roomId: String, participantUserId: String, isIncomingCall: Boolean, isVideoCall: Boolean) { - val callHeadsUpServiceIntent = CallHeadsUpService.newInstance(context, roomId, participantUserId, isIncomingCall, isVideoCall) + private fun startHeadsUpService(mxCall: MxCallDetail) { + val callHeadsUpServiceIntent = CallHeadsUpService.newInstance(context, mxCall) ContextCompat.startForegroundService(context, callHeadsUpServiceIntent) context.bindService(Intent(context, CallHeadsUpService::class.java), serviceConnection, 0) } fun endCall() { - if (callId != null && signalingRoomId != null) { - sessionHolder.getActiveSession().callService().hangup(callId!!, signalingRoomId!!) - } + currentCall?.hangUp() + currentCall = null close() } override fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) { - this.callId = callAnswerContent.callId - executor.execute { - Timber.v("## answerReceived $callId") + Timber.v("## answerReceived") val sdp = SessionDescription(SessionDescription.Type.ANSWER, callAnswerContent.answer.sdp) peerConnection?.setRemoteDescription(object : SdpObserverAdapter() {}, sdp) } } override fun onCallHangupReceived(callHangupContent: CallHangupContent) { + currentCall = null close() } } diff --git a/vector/src/main/java/im/vector/riotx/features/call/service/CallHeadsUpService.kt b/vector/src/main/java/im/vector/riotx/features/call/service/CallHeadsUpService.kt index 653ac90f0d..f8ec1c6ee1 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/service/CallHeadsUpService.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/service/CallHeadsUpService.kt @@ -30,6 +30,7 @@ import android.os.Binder import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat +import im.vector.matrix.android.api.session.call.MxCallDetail import im.vector.riotx.R import im.vector.riotx.features.call.VectorCallActivity @@ -50,7 +51,7 @@ class CallHeadsUpService : Service() { createNotificationChannel() - val title = callHeadsUpServiceArgs?.participantUserId ?: "" + val title = callHeadsUpServiceArgs?.otherUserId ?: "" val description = when { callHeadsUpServiceArgs?.isIncomingCall == false -> getString(R.string.call_ring) callHeadsUpServiceArgs?.isVideoCall == true -> getString(R.string.incoming_video_call) @@ -133,8 +134,8 @@ class CallHeadsUpService : Service() { private const val NOTIFICATION_ID = 999 - fun newInstance(context: Context, roomId: String, participantUserId: String, isIncomingCall: Boolean, isVideoCall: Boolean): Intent { - val args = CallHeadsUpServiceArgs(roomId, participantUserId, isIncomingCall, isVideoCall) + fun newInstance(context: Context, mxCall: MxCallDetail): Intent { + val args = CallHeadsUpServiceArgs(mxCall.roomId, mxCall.otherUserId, !mxCall.isOutgoing, mxCall.isVideoCall) return Intent(context, CallHeadsUpService::class.java).apply { putExtra(EXTRA_CALL_HEADS_UP_SERVICE_PARAMS, args) } diff --git a/vector/src/main/java/im/vector/riotx/features/call/service/CallHeadsUpServiceArgs.kt b/vector/src/main/java/im/vector/riotx/features/call/service/CallHeadsUpServiceArgs.kt index 381975a2ee..1bc857e4a7 100644 --- a/vector/src/main/java/im/vector/riotx/features/call/service/CallHeadsUpServiceArgs.kt +++ b/vector/src/main/java/im/vector/riotx/features/call/service/CallHeadsUpServiceArgs.kt @@ -22,7 +22,7 @@ import kotlinx.android.parcel.Parcelize @Parcelize data class CallHeadsUpServiceArgs( val roomId: String, - val participantUserId: String, + val otherUserId: String, val isIncomingCall: Boolean, val isVideoCall: Boolean ) : Parcelable