From 5fbace70b5a05a39e822669ee41383d0d1f27d48 Mon Sep 17 00:00:00 2001 From: jubb Date: Mon, 22 Nov 2021 16:58:28 +1100 Subject: [PATCH] feat: handle discarding pending calls from linked devices --- .../securesms/dependencies/CallModule.kt | 4 +-- .../securesms/service/WebRtcCallService.kt | 15 ++++++----- .../securesms/webrtc/CallManager.kt | 27 ++++++++++++++----- .../securesms/webrtc/CallMessageProcessor.kt | 3 ++- app/src/main/res/layout/activity_webrtc.xml | 2 +- .../messaging/messages/control/CallMessage.kt | 13 ++++----- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt index 1f2a1228e..da15c2f6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/CallModule.kt @@ -25,7 +25,7 @@ object CallModule { @Provides @Singleton - fun provideCallManager(@ApplicationContext context: Context, audioManagerCompat: AudioManagerCompat) = - CallManager(context, audioManagerCompat) + fun provideCallManager(@ApplicationContext context: Context, audioManagerCompat: AudioManagerCompat, storage: Storage) = + CallManager(context, audioManagerCompat, storage) } \ No newline at end of file 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 ed80a84dc..8b766530f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -51,7 +51,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer { const val ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO" const val ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO" const val ACTION_FLIP_CAMERA = "FLIP_CAMERA" - const val ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE" const val ACTION_UPDATE_AUDIO = "UPDATE_AUDIO" const val ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE" const val ACTION_SCREEN_OFF = "SCREEN_OFF" @@ -206,12 +205,11 @@ class WebRtcCallService: Service(), PeerConnection.Observer { action == ACTION_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent) action == ACTION_ANSWER_CALL -> handleAnswerCall(intent) action == ACTION_DENY_CALL -> handleDenyCall(intent) - action == ACTION_LOCAL_HANGUP -> handleLocalHangup(intent) + action == ACTION_LOCAL_HANGUP -> handleLocalHangup(intent, true) action == ACTION_REMOTE_HANGUP -> handleRemoteHangup(intent) action == ACTION_SET_MUTE_AUDIO -> handleSetMuteAudio(intent) action == ACTION_SET_MUTE_VIDEO -> handleSetMuteVideo(intent) action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent) - action == ACTION_BLUETOOTH_CHANGE -> handleBluetoothChange(intent) action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent) action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent) action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent) @@ -444,8 +442,9 @@ class WebRtcCallService: Service(), PeerConnection.Observer { terminate() } - private fun handleLocalHangup(intent: Intent) { - callManager.handleLocalHangup() + private fun handleLocalHangup(intent: Intent, sendHangup: Boolean) { + // TODO: check current call ID and recipient == expected + callManager.handleLocalHangup(sendHangup) terminate() } @@ -496,6 +495,10 @@ class WebRtcCallService: Service(), PeerConnection.Observer { private fun handleResponseMessage(intent: Intent) { try { val recipient = getRemoteRecipient(intent) + if (callManager.isCurrentUser(recipient) && callManager.currentConnectionState == STATE_LOCAL_RINGING) { + handleLocalHangup(intent, false) + return + } val callId = getCallId(intent) val description = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) callManager.handleResponseMessage(recipient, callId, SessionDescription(SessionDescription.Type.ANSWER, description)) @@ -555,7 +558,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer { if (callId == getCallId(intent) && callState !in arrayOf(STATE_CONNECTED)) { Log.w(TAG, "Timing out call: $callId") - handleLocalHangup(intent) + handleLocalHangup(intent, true) } } 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 0780c1257..2ea2fbb24 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -9,10 +9,14 @@ import kotlinx.serialization.json.Json import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.put import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.combine.and import nl.komponents.kovenant.functional.bind +import org.session.libsession.database.StorageProtocol 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.Debouncer +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.Util import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.protos.SignalServiceProtos @@ -31,7 +35,7 @@ import java.nio.ByteBuffer import java.util.* import java.util.concurrent.Executors -class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConnection.Observer, +class CallManager(context: Context, audioManager: AudioManagerCompat, private val storage: StorageProtocol): PeerConnection.Observer, SignalAudioManager.EventListener, CallDataListener, CameraEventListener, DataChannel.Observer { @@ -170,6 +174,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne fun isIdle() = currentConnectionState == CallState.STATE_IDLE + fun isCurrentUser(recipient: Recipient) = recipient.address.serialize() == storage.getUserPublicKey() + fun initializeVideo(context: Context) { Util.runOnMainSync { val base = EglBase.create() @@ -369,7 +375,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne val answer = connection.createAnswer(MediaConstraints().apply { mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true")) }) - return MessageSender.sendNonDurably(CallMessage.answer(answer.description, callId), recipient.address) + val answerMessage = CallMessage.answer(answer.description, callId) + return MessageSender.sendNonDurably(answerMessage, recipient.address) } fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long) { @@ -406,8 +413,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne connection.setRemoteDescription(SessionDescription(SessionDescription.Type.OFFER, offer)) val answer = connection.createAnswer(MediaConstraints()) connection.setLocalDescription(answer) - - val answerMessage = MessageSender.sendNonDurably(CallMessage.answer( + val answerMessage = CallMessage.answer(answer.description, callId) + val userAddress = storage.getUserPublicKey() ?: return Promise.ofFail(NullPointerException("No user public key")) + MessageSender.sendNonDurably(answerMessage, Address.fromSerialized(userAddress)) + val sendAnswerMessage = MessageSender.sendNonDurably(CallMessage.answer( answer.description, callId ), recipient.address) @@ -416,7 +425,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne val candidate = pendingIncomingIceUpdates.pop() ?: break connection.addIceCandidate(candidate) } - return answerMessage.success { + return sendAnswerMessage.success { pendingOffer = null pendingOfferTime = -1 } @@ -468,15 +477,19 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne fun handleDenyCall() { val callId = callId ?: return val recipient = recipient ?: return + val userAddress = storage.getUserPublicKey() ?: return + MessageSender.sendNonDurably(CallMessage.endCall(callId), Address.fromSerialized(userAddress)) MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address) } - fun handleLocalHangup() { + fun handleLocalHangup(sendHangup: Boolean) { val recipient = recipient ?: return val callId = callId ?: return postViewModelState(CallViewModel.State.CALL_DISCONNECTED) - MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address) + if (sendHangup) { + MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address) + } } fun handleRemoteHangup() { 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 0b393651b..fd3b6839a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.core.content.ContextCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.coroutineScope +import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import org.session.libsession.messaging.messages.control.CallMessage @@ -19,7 +20,7 @@ import org.webrtc.IceCandidate class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { init { - lifecycle.coroutineScope.launch { + lifecycle.coroutineScope.launch(IO) { while (isActive) { val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive() Log.d("Loki", nextMessage.type?.name ?: "CALL MESSAGE RECEIVED") diff --git a/app/src/main/res/layout/activity_webrtc.xml b/app/src/main/res/layout/activity_webrtc.xml index e41d35b3b..82d3d314e 100644 --- a/app/src/main/res/layout/activity_webrtc.xml +++ b/app/src/main/res/layout/activity_webrtc.xml @@ -18,7 +18,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent" /> - = listOf() var callId: UUID? = null - override val isSelfSendValid: Boolean = type in arrayOf(SignalServiceProtos.CallMessage.Type.END_CALL,SignalServiceProtos.CallMessage.Type.ANSWER) + override val isSelfSendValid: Boolean get() = type in arrayOf(ANSWER, END_CALL) override val ttl: Long = 300000L // 30s override fun isValid(): Boolean = super.isValid() && type != null && callId != null - && (!sdps.isNullOrEmpty() || type in listOf(SignalServiceProtos.CallMessage.Type.END_CALL,SignalServiceProtos.CallMessage.Type.PRE_OFFER)) + && (!sdps.isNullOrEmpty() || type in listOf(END_CALL, PRE_OFFER)) constructor(type: SignalServiceProtos.CallMessage.Type, sdps: List, @@ -33,28 +34,28 @@ class CallMessage(): ControlMessage() { companion object { const val TAG = "CallMessage" - fun answer(sdp: String, callId: UUID) = CallMessage(SignalServiceProtos.CallMessage.Type.ANSWER, + fun answer(sdp: String, callId: UUID) = CallMessage(ANSWER, listOf(sdp), listOf(), listOf(), callId ) - fun preOffer(callId: UUID) = CallMessage(SignalServiceProtos.CallMessage.Type.PRE_OFFER, + fun preOffer(callId: UUID) = CallMessage(PRE_OFFER, listOf(), listOf(), listOf(), callId ) - fun offer(sdp: String, callId: UUID) = CallMessage(SignalServiceProtos.CallMessage.Type.OFFER, + fun offer(sdp: String, callId: UUID) = CallMessage(OFFER, listOf(sdp), listOf(), listOf(), callId ) - fun endCall(callId: UUID) = CallMessage(SignalServiceProtos.CallMessage.Type.END_CALL, emptyList(), emptyList(), emptyList(), callId) + fun endCall(callId: UUID) = CallMessage(END_CALL, emptyList(), emptyList(), emptyList(), callId) fun fromProto(proto: SignalServiceProtos.Content): CallMessage? { val callMessageProto = if (proto.hasCallMessage()) proto.callMessage else return null