diff --git a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt index 93f97a3b13..c536a3e456 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/calls/WebRtcCallActivity.kt @@ -10,6 +10,7 @@ import android.os.Bundle import android.view.MenuItem import android.view.WindowManager import androidx.activity.viewModels +import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch @@ -17,6 +18,7 @@ import network.loki.messenger.R import org.session.libsession.utilities.Address import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.permissions.Permissions +import org.thoughtcrime.securesms.service.WebRtcCallService import org.thoughtcrime.securesms.webrtc.CallViewModel import org.webrtc.IceCandidate import java.util.* @@ -72,7 +74,11 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() { // repeat on start or something } - + if (intent.action == ACTION_ANSWER) { + // answer via ViewModel + val answerIntent = WebRtcCallService.acceptCallIntent(this) + startService(answerIntent) + } registerReceiver(object: BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt index 1858cda48f..35f1ff5c6c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -13,6 +13,8 @@ import android.telephony.PhoneStateListener import android.telephony.TelephonyManager import androidx.core.content.ContextCompat import dagger.hilt.android.AndroidEntryPoint +import org.session.libsession.messaging.messages.control.CallMessage +import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.utilities.Address import org.session.libsession.utilities.FutureTaskListener import org.session.libsession.utilities.Util @@ -46,7 +48,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { private val TAG = Log.tag(WebRtcCallService::class.java) - const val ACTION_INCOMING_CALL = "CALL_INCOMING" + const val ACTION_INCOMING_RING = "RING_INCOMING" const val ACTION_OUTGOING_CALL = "CALL_OUTGOING" const val ACTION_ANSWER_CALL = "ANSWER_CALL" const val ACTION_DENY_CALL = "DENY_CALL" @@ -90,16 +92,35 @@ class WebRtcCallService: Service(), PeerConnection.Observer { .setAction(ACTION_OUTGOING_CALL) .putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address) - fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID) = + fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID, callTime: Long) = Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_INCOMING_CALL) + .setAction(ACTION_INCOMING_RING) .putExtra(EXTRA_RECIPIENT_ADDRESS, address) .putExtra(EXTRA_CALL_ID, callId) .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) - .putExtra(EXTRA_TIMESTAMP, System.currentTimeMillis()) + .putExtra(EXTRA_TIMESTAMP, callTime) + + fun incomingAnswer(context: Context, address: Address, sdp: String, callId: UUID) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_RESPONSE_MESSAGE) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) + + fun iceCandidates(context: Context, iceCandidates: List, callId: UUID) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_ICE_MESSAGE) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray()) + .putExtra(EXTRA_ICE_SDP_LINE_INDEX, iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray()) + .putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray()) fun denyCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL) + fun remoteHangupIntent(context: Context, callId: UUID) = Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_REMOTE_HANGUP) + .putExtra(EXTRA_CALL_ID, callId) + fun hangupIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_LOCAL_HANGUP) fun sendAudioManagerCommand(context: Context, command: AudioManagerCommand) { @@ -149,10 +170,11 @@ class WebRtcCallService: Service(), PeerConnection.Observer { if (intent == null || intent.action == null) return START_NOT_STICKY serviceExecutor.execute { val action = intent.action + Log.d("Loki", "Handling ${intent.action}") when { - action == ACTION_INCOMING_CALL && isBusy() -> handleBusyCall(intent) + action == ACTION_INCOMING_RING && isBusy() -> handleBusyCall(intent) action == ACTION_REMOTE_BUSY -> handleBusyMessage(intent) - action == ACTION_INCOMING_CALL -> handleIncomingCall(intent) + action == ACTION_INCOMING_RING && isIdle() -> handleIncomingRing(intent) action == ACTION_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent) action == ACTION_ANSWER_CALL -> handleAnswerCall(intent) action == ACTION_DENY_CALL -> handleDenyCall(intent) @@ -204,7 +226,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer { private fun handleBusyCall(intent: Intent) { val recipient = getRemoteRecipient(intent) - val callId = getCallId(intent) val callState = callManager.currentConnectionState if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -215,7 +236,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer { STATE_ANSWERING -> setCallInProgressNotification(TYPE_INCOMING_CONNECTING, callManager.recipient) STATE_LOCAL_RINGING -> setCallInProgressNotification(TYPE_INCOMING_RINGING, callManager.recipient) STATE_CONNECTED -> setCallInProgressNotification(TYPE_ESTABLISHED, callManager.recipient) - else -> throw AssertionError() } } @@ -244,48 +264,19 @@ class WebRtcCallService: Service(), PeerConnection.Observer { }, WebRtcCallActivity.BUSY_SIGNAL_DELAY_FINISH) } - private fun handleIncomingCall(intent: Intent) { - if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Incoming on non-idle") + private fun handleIncomingRing(intent: Intent) { + if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Incoming ring on non-idle") - val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return - callManager.postConnectionEvent(STATE_ANSWERING) val callId = getCallId(intent) - callManager.callId = callId - callManager.clearPendingIceUpdates() val recipient = getRemoteRecipient(intent) - callManager.recipient = recipient - if (isIncomingMessageExpired(intent)) { - insertMissedCall(recipient, true) - terminate() - return - } - + val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return + val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, -1) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient) - } - - timeoutExecutor.schedule(TimeoutRunnable(callId, this), 2, TimeUnit.MINUTES) - - callManager.initializeVideo(this) - - val expectedState = callManager.currentConnectionState - val expectedCallId = callManager.callId - - try { - val answerFuture = callManager.onIncomingCall(offer, this) // add is always turn here - answerFuture.fail { e -> - if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) { - Log.e(TAG, e) - insertMissedCall(recipient, true) - terminate() - } - } - callManager.postViewModelState(CallViewModel.State.CALL_INCOMING) - // lock manager update phone state processing here - } catch (e: Exception) { - Log.e(TAG,e) - terminate() + setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient) } + callManager.onIncomingRing(offer, callId, recipient, timestamp) + callManager.clearPendingIceUpdates() + callManager.postConnectionEvent(STATE_LOCAL_RINGING) } private fun handleOutgoingCall(intent: Intent) { @@ -304,7 +295,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING) setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient) // TODO: DatabaseComponent.get(this).insertOutgoingCall(callManager.recipient!!.address) - timeoutExecutor.schedule(TimeoutRunnable(callId, this), 2, TimeUnit.MINUTES) + timeoutExecutor.schedule(TimeoutRunnable(callId, this), 5, TimeUnit.MINUTES) val expectedState = callManager.currentConnectionState val expectedCallId = callManager.callId @@ -330,17 +321,50 @@ class WebRtcCallService: Service(), PeerConnection.Observer { return } - if (callManager.callNotSetup()) { - throw AssertionError("assert") - } - - // DatabaseComponent.get(this).smsDatabase().insertReceivedCall(recipient) - - val (callId, recipient) = callManager.handleAnswerCall() + val pending = callManager.pendingOffer ?: return + val callId = callManager.callId ?: return + val recipient = callManager.recipient ?: return + val timestamp = callManager.pendingOfferTime intent.putExtra(EXTRA_CALL_ID, callId) intent.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address) - handleCallConnected(intent) + intent.putExtra(EXTRA_REMOTE_DESCRIPTION, pending) + intent.putExtra(EXTRA_TIMESTAMP, timestamp) + + callManager.postConnectionEvent(STATE_ANSWERING) + + if (isIncomingMessageExpired(intent)) { + insertMissedCall(recipient, true) + terminate() + return + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient) + } + + timeoutExecutor.schedule(TimeoutRunnable(callId, this), 5, TimeUnit.MINUTES) + + callManager.initializeVideo(this) + + val expectedState = callManager.currentConnectionState + val expectedCallId = callManager.callId + + try { + val answerFuture = callManager.onIncomingCall(this) // add is always turn here + answerFuture.fail { e -> + if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) { + Log.e(TAG, e) + insertMissedCall(recipient, true) + terminate() + } + } + callManager.postViewModelState(CallViewModel.State.CALL_INCOMING) + lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING) + } catch (e: Exception) { + Log.e(TAG,e) + terminate() + } + // DatabaseComponent.get(this).smsDatabase().insertReceivedCall(recipient) } private fun handleDenyCall(intent: Intent) { @@ -367,16 +391,18 @@ class WebRtcCallService: Service(), PeerConnection.Observer { private fun handleRemoteHangup(intent: Intent) { if (callManager.callId != getCallId(intent)) { Log.e(TAG, "Hangup for non-active call...") + terminate() return } callManager.handleRemoteHangup() - if (callManager.currentConnectionState in arrayOf(STATE_ANSWERING, STATE_LOCAL_RINGING)) { + if (callManager.currentConnectionState in arrayOf(STATE_REMOTE_RINGING, STATE_ANSWERING, STATE_LOCAL_RINGING)) { callManager.recipient?.let { recipient -> insertMissedCall(recipient, true) } } + terminate() } private fun handleSetMuteAudio(intent: Intent) { @@ -445,7 +471,10 @@ class WebRtcCallService: Service(), PeerConnection.Observer { } private fun handleIceConnected(intent: Intent) { - + if (callManager.currentConnectionState == STATE_ANSWERING) { + val recipient = callManager.recipient ?: return + callManager.postConnectionEvent(STATE_CONNECTED) + } } private fun handleCallConnected(intent: Intent) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 1297c56144..1ce83db390 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -9,9 +9,11 @@ import kotlinx.serialization.json.put import nl.komponents.kovenant.Promise import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.sending_receiving.MessageSender +import org.session.libsession.utilities.Debouncer import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.protos.SignalServiceProtos +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger @@ -86,12 +88,15 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne private var localCameraState: CameraState = CameraState.UNKNOWN private var bluetoothAvailable = false - val currentConnectionState = (_connectionEvents.value as StateEvent.CallStateUpdate).state + val currentConnectionState + get() = (_connectionEvents.value as StateEvent.CallStateUpdate).state private val networkExecutor = Executors.newSingleThreadExecutor() private var eglBase: EglBase? = null + var pendingOffer: String? = null + var pendingOfferTime: Long = -1 var callId: UUID? = null var recipient: Recipient? = null @@ -103,6 +108,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne private val pendingOutgoingIceUpdates = ArrayDeque() private val pendingIncomingIceUpdates = ArrayDeque() + private val outgoingIceDebouncer = Debouncer(2_000L) + private var localRenderer: SurfaceViewRenderer? = null private var remoteRenderer: SurfaceViewRenderer? = null private var peerConnectionFactory: PeerConnectionFactory? = null @@ -117,6 +124,9 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne } fun startOutgoingRinger(ringerType: OutgoingRinger.Type) { + if (ringerType == OutgoingRinger.Type.RINGING) { + signalAudioManager.handleCommand(AudioManagerCommand.UpdateAudioDeviceState) + } signalAudioManager.handleCommand(AudioManagerCommand.StartOutgoingRinger(ringerType)) } @@ -209,16 +219,49 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne peerConnectionObservers.forEach { listener -> listener.onIceGatheringChange(newState) } } - override fun onIceCandidate(iceCandidate: IceCandidate?) { + override fun onIceCandidate(iceCandidate: IceCandidate) { peerConnectionObservers.forEach { listener -> listener.onIceCandidate(iceCandidate) } + val expectedCallId = this.callId ?: return + val expectedRecipient = this.recipient ?: return + pendingOutgoingIceUpdates.add(iceCandidate) + outgoingIceDebouncer.publish { + val currentCallId = this.callId ?: return@publish + val currentRecipient = this.recipient ?: return@publish + if (currentCallId == expectedCallId && expectedRecipient == currentRecipient) { + val currentPendings = mutableSetOf() + while (pendingOutgoingIceUpdates.isNotEmpty()) { + currentPendings.add(pendingOutgoingIceUpdates.pop()) + } + val sdps = currentPendings.map { it.sdp } + val sdpMLineIndexes = currentPendings.map { it.sdpMLineIndex } + val sdpMids = currentPendings.map { it.sdpMid } + + MessageSender.sendNonDurably(CallMessage( + ICE_CANDIDATES, + sdps = sdps, + sdpMLineIndexes = sdpMLineIndexes, + sdpMids = sdpMids, + currentCallId + ), currentRecipient.address) + } + } } override fun onIceCandidatesRemoved(candidates: Array?) { peerConnectionObservers.forEach { listener -> listener.onIceCandidatesRemoved(candidates) } } - override fun onAddStream(p0: MediaStream?) { - peerConnectionObservers.forEach { listener -> listener.onAddStream(p0) } + override fun onAddStream(stream: MediaStream) { + peerConnectionObservers.forEach { listener -> listener.onAddStream(stream) } + for (track in stream.audioTracks) { + track.setEnabled(true) + } + + if (stream.videoTracks != null && stream.videoTracks.size == 1) { + val videoTrack = stream.videoTracks.first() + videoTrack.setEnabled(true) + videoTrack.addSink(remoteRenderer) + } } override fun onRemoveStream(p0: MediaStream?) { @@ -254,13 +297,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne signalAudioManager.handleCommand(AudioManagerCommand()) } - private fun CallMessage.iceCandidates(): List { - val candidateSize = sdpMids.size - return (0 until candidateSize).map { i -> - IceCandidate(sdpMids[i], sdpMLineIndexes[i], sdps[i]) - } - } - private fun CallState.withState(vararg expected: CallState, transition: () -> Unit) { if (this in expected) transition() else Log.w(TAG,"Tried to transition state $this but expected $expected") @@ -293,9 +329,19 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne localCameraState = newCameraState } - fun onIncomingCall(offer: String, context: Context, isAlwaysTurn: Boolean = false): Promise { + fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long) { + if (currentConnectionState != CallState.STATE_IDLE) return + + this.callId = callId + this.recipient = recipient + this.pendingOffer = offer + this.pendingOfferTime = callTime + } + + fun onIncomingCall(context: Context, isAlwaysTurn: Boolean = false): Promise { val callId = callId ?: return Promise.ofFail(NullPointerException("callId is null")) val recipient = recipient ?: return Promise.ofFail(NullPointerException("recipient is null")) + val offer = pendingOffer ?: return Promise.ofFail(NullPointerException("pendingOffer is null")) val factory = peerConnectionFactory ?: return Promise.ofFail(NullPointerException("peerConnectionFactory is null")) val local = localRenderer ?: return Promise.ofFail(NullPointerException("localRenderer is null")) val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null")) @@ -310,6 +356,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne ) peerConnection = connection localCameraState = connection.getCameraState() + val dataChannel = connection.createDataChannel(DATA_CHANNEL_NAME) + dataChannel.registerObserver(this) connection.setRemoteDescription(SessionDescription(SessionDescription.Type.OFFER, offer)) val answer = connection.createAnswer(MediaConstraints()) connection.setLocalDescription(answer) @@ -323,7 +371,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne val candidate = pendingIncomingIceUpdates.pop() ?: break connection.addIceCandidate(candidate) } - return answerMessage // TODO: maybe add success state update + return answerMessage.success { + pendingOffer = null + pendingOfferTime = -1 + } // TODO: maybe add success state update } fun onOutgoingCall(context: Context, isAlwaysTurn: Boolean = false): Promise { @@ -346,6 +397,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne isAlwaysTurn ) + peerConnection = connection localCameraState = connection.getCameraState() val dataChannel = connection.createDataChannel(DATA_CHANNEL_NAME) dataChannel.registerObserver(this) @@ -363,14 +415,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne fun callNotSetup(): Boolean = peerConnection == null || dataChannel == null || recipient == null || callId == null - fun handleAnswerCall(): Pair { - peerConnection?.let { connection -> - connection.setAudioEnabled(true) - connection.setVideoEnabled(true) - } - return callId!! to recipient!! - } - fun handleDenyCall() { val callId = callId ?: return val recipient = recipient ?: return @@ -476,6 +520,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne fun handleRemoteIceCandidate(iceCandidates: List, callId: UUID) { if (callId != this.callId) { Log.w(TAG, "Got remote ice candidates for a call that isn't active") + return } peerConnection?.let { connection -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt index 1d777bb27b..27c6af4002 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -1,18 +1,18 @@ package org.thoughtcrime.securesms.webrtc import android.content.Context +import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle -import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.coroutineScope import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.utilities.WebRtcUtils import org.session.libsession.utilities.Address -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.OFFER +import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.* +import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.service.WebRtcCallService -import javax.inject.Inject +import org.webrtc.IceCandidate class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { @@ -21,25 +21,75 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { lifecycle.coroutineScope.launch { while (isActive) { val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive() - when { - // TODO: handle messages as they come in - nextMessage.type == OFFER -> incomingCall(nextMessage) + Log.d("Loki", nextMessage.toString()) + when (nextMessage.type) { + OFFER -> incomingCall(nextMessage) + ANSWER -> incomingAnswer(nextMessage) + END_CALL -> incomingHangup(nextMessage) + ICE_CANDIDATES -> handleIceCandidates(nextMessage) + PRE_OFFER -> incomingCall(nextMessage) + PROVISIONAL_ANSWER -> {} // TODO: if necessary } } } } + private fun incomingHangup(callMessage: CallMessage) { + val callId = callMessage.callId ?: return + val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId) + context.startService(hangupIntent) + } + + private fun incomingAnswer(callMessage: CallMessage) { + val recipientAddress = callMessage.sender ?: return + val callId = callMessage.callId ?: return + val sdp = callMessage.sdps.firstOrNull() ?: return + val answerIntent = WebRtcCallService.incomingAnswer( + context = context, + address = Address.fromSerialized(recipientAddress), + sdp = sdp, + callId = callId + ) + context.startService(answerIntent) + } + + private fun handleIceCandidates(callMessage: CallMessage) { + val callId = callMessage.callId ?: return + + val iceCandidates = callMessage.iceCandidates() + if (iceCandidates.isEmpty()) return + + val iceIntent = WebRtcCallService.iceCandidates( + context = context, + iceCandidates = iceCandidates, + callId = callId + ) + context.startService(iceIntent) + } + private fun incomingCall(callMessage: CallMessage) { - val recipientAddress = callMessage.recipient ?: return + val recipientAddress = callMessage.sender ?: return val callId = callMessage.callId ?: return val sdp = callMessage.sdps.firstOrNull() ?: return val incomingIntent = WebRtcCallService.incomingCall( context = context, address = Address.fromSerialized(recipientAddress), sdp = sdp, - callId = callId + callId = callId, + callTime = callMessage.sentTimestamp ?: -1L ) - context.startService(incomingIntent) + ContextCompat.startForegroundService(context, incomingIntent) + + } + + private fun CallMessage.iceCandidates(): List { + if (sdpMids.size != sdpMLineIndexes.size || sdpMLineIndexes.size != sdps.size) { + return listOf() // uneven sdp numbers + } + val candidateSize = sdpMids.size + return (0 until candidateSize).map { i -> + IceCandidate(sdpMids[i], sdpMLineIndexes[i], sdps[i]) + } } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt index 351e4673ca..0ca743a0da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallViewModel.kt @@ -1,14 +1,8 @@ package org.thoughtcrime.securesms.webrtc import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onEach -import org.session.libsession.messaging.messages.control.CallMessage -import org.webrtc.* import javax.inject.Inject @HiltViewModel