feat: handle discarding pending calls from linked devices

This commit is contained in:
jubb
2021-11-22 16:58:28 +11:00
parent 51e7109649
commit 5fbace70b5
6 changed files with 41 additions and 23 deletions

View File

@@ -25,7 +25,7 @@ object CallModule {
@Provides @Provides
@Singleton @Singleton
fun provideCallManager(@ApplicationContext context: Context, audioManagerCompat: AudioManagerCompat) = fun provideCallManager(@ApplicationContext context: Context, audioManagerCompat: AudioManagerCompat, storage: Storage) =
CallManager(context, audioManagerCompat) CallManager(context, audioManagerCompat, storage)
} }

View File

@@ -51,7 +51,6 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
const val ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO" const val ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO"
const val ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO" const val ACTION_SET_MUTE_VIDEO = "SET_MUTE_VIDEO"
const val ACTION_FLIP_CAMERA = "FLIP_CAMERA" const val ACTION_FLIP_CAMERA = "FLIP_CAMERA"
const val ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE"
const val ACTION_UPDATE_AUDIO = "UPDATE_AUDIO" const val ACTION_UPDATE_AUDIO = "UPDATE_AUDIO"
const val ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE" const val ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE"
const val ACTION_SCREEN_OFF = "SCREEN_OFF" 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_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent)
action == ACTION_ANSWER_CALL -> handleAnswerCall(intent) action == ACTION_ANSWER_CALL -> handleAnswerCall(intent)
action == ACTION_DENY_CALL -> handleDenyCall(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_REMOTE_HANGUP -> handleRemoteHangup(intent)
action == ACTION_SET_MUTE_AUDIO -> handleSetMuteAudio(intent) action == ACTION_SET_MUTE_AUDIO -> handleSetMuteAudio(intent)
action == ACTION_SET_MUTE_VIDEO -> handleSetMuteVideo(intent) action == ACTION_SET_MUTE_VIDEO -> handleSetMuteVideo(intent)
action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent) action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent)
action == ACTION_BLUETOOTH_CHANGE -> handleBluetoothChange(intent)
action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent) action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent)
action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent) action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent)
action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent) action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent)
@@ -444,8 +442,9 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
terminate() terminate()
} }
private fun handleLocalHangup(intent: Intent) { private fun handleLocalHangup(intent: Intent, sendHangup: Boolean) {
callManager.handleLocalHangup() // TODO: check current call ID and recipient == expected
callManager.handleLocalHangup(sendHangup)
terminate() terminate()
} }
@@ -496,6 +495,10 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
private fun handleResponseMessage(intent: Intent) { private fun handleResponseMessage(intent: Intent) {
try { try {
val recipient = getRemoteRecipient(intent) val recipient = getRemoteRecipient(intent)
if (callManager.isCurrentUser(recipient) && callManager.currentConnectionState == STATE_LOCAL_RINGING) {
handleLocalHangup(intent, false)
return
}
val callId = getCallId(intent) val callId = getCallId(intent)
val description = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) val description = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION)
callManager.handleResponseMessage(recipient, callId, SessionDescription(SessionDescription.Type.ANSWER, 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)) { if (callId == getCallId(intent) && callState !in arrayOf(STATE_CONNECTED)) {
Log.w(TAG, "Timing out call: $callId") Log.w(TAG, "Timing out call: $callId")
handleLocalHangup(intent) handleLocalHangup(intent, true)
} }
} }

View File

@@ -9,10 +9,14 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put import kotlinx.serialization.json.put
import nl.komponents.kovenant.Promise import nl.komponents.kovenant.Promise
import nl.komponents.kovenant.combine.and
import nl.komponents.kovenant.functional.bind 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.messages.control.CallMessage
import org.session.libsession.messaging.sending_receiving.MessageSender 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.Debouncer
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.Util import org.session.libsession.utilities.Util
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
@@ -31,7 +35,7 @@ import java.nio.ByteBuffer
import java.util.* import java.util.*
import java.util.concurrent.Executors 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, SignalAudioManager.EventListener,
CallDataListener, CameraEventListener, DataChannel.Observer { CallDataListener, CameraEventListener, DataChannel.Observer {
@@ -170,6 +174,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
fun isIdle() = currentConnectionState == CallState.STATE_IDLE fun isIdle() = currentConnectionState == CallState.STATE_IDLE
fun isCurrentUser(recipient: Recipient) = recipient.address.serialize() == storage.getUserPublicKey()
fun initializeVideo(context: Context) { fun initializeVideo(context: Context) {
Util.runOnMainSync { Util.runOnMainSync {
val base = EglBase.create() val base = EglBase.create()
@@ -369,7 +375,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
val answer = connection.createAnswer(MediaConstraints().apply { val answer = connection.createAnswer(MediaConstraints().apply {
mandatory.add(MediaConstraints.KeyValuePair("IceRestart", "true")) 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) { 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)) connection.setRemoteDescription(SessionDescription(SessionDescription.Type.OFFER, offer))
val answer = connection.createAnswer(MediaConstraints()) val answer = connection.createAnswer(MediaConstraints())
connection.setLocalDescription(answer) connection.setLocalDescription(answer)
val answerMessage = CallMessage.answer(answer.description, callId)
val answerMessage = MessageSender.sendNonDurably(CallMessage.answer( 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, answer.description,
callId callId
), recipient.address) ), recipient.address)
@@ -416,7 +425,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
val candidate = pendingIncomingIceUpdates.pop() ?: break val candidate = pendingIncomingIceUpdates.pop() ?: break
connection.addIceCandidate(candidate) connection.addIceCandidate(candidate)
} }
return answerMessage.success { return sendAnswerMessage.success {
pendingOffer = null pendingOffer = null
pendingOfferTime = -1 pendingOfferTime = -1
} }
@@ -468,15 +477,19 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
fun handleDenyCall() { fun handleDenyCall() {
val callId = callId ?: return val callId = callId ?: return
val recipient = recipient ?: 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) MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address)
} }
fun handleLocalHangup() { fun handleLocalHangup(sendHangup: Boolean) {
val recipient = recipient ?: return val recipient = recipient ?: return
val callId = callId ?: return val callId = callId ?: return
postViewModelState(CallViewModel.State.CALL_DISCONNECTED) postViewModelState(CallViewModel.State.CALL_DISCONNECTED)
MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address) if (sendHangup) {
MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address)
}
} }
fun handleRemoteHangup() { fun handleRemoteHangup() {

View File

@@ -4,6 +4,7 @@ import android.content.Context
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.messages.control.CallMessage
@@ -19,7 +20,7 @@ import org.webrtc.IceCandidate
class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) { class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) {
init { init {
lifecycle.coroutineScope.launch { lifecycle.coroutineScope.launch(IO) {
while (isActive) { while (isActive) {
val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive() val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive()
Log.d("Loki", nextMessage.type?.name ?: "CALL MESSAGE RECEIVED") Log.d("Loki", nextMessage.type?.name ?: "CALL MESSAGE RECEIVED")

View File

@@ -18,7 +18,7 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
</FrameLayout> </FrameLayout>
<com.makeramen.roundedimageview.RoundedImageView <ImageView
android:id="@+id/remote_recipient" android:id="@+id/remote_recipient"
app:layout_constraintStart_toStartOf="@id/remote_parent" app:layout_constraintStart_toStartOf="@id/remote_parent"
app:layout_constraintEnd_toEndOf="@id/remote_parent" app:layout_constraintEnd_toEndOf="@id/remote_parent"

View File

@@ -1,6 +1,7 @@
package org.session.libsession.messaging.messages.control package org.session.libsession.messaging.messages.control
import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.protos.SignalServiceProtos
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.*
import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Log
import java.util.* import java.util.*
@@ -11,12 +12,12 @@ class CallMessage(): ControlMessage() {
var sdpMids: List<String> = listOf() var sdpMids: List<String> = listOf()
var callId: UUID? = null 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 val ttl: Long = 300000L // 30s
override fun isValid(): Boolean = super.isValid() && type != null && callId != null 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, constructor(type: SignalServiceProtos.CallMessage.Type,
sdps: List<String>, sdps: List<String>,
@@ -33,28 +34,28 @@ class CallMessage(): ControlMessage() {
companion object { companion object {
const val TAG = "CallMessage" 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(sdp),
listOf(), listOf(),
listOf(), listOf(),
callId callId
) )
fun preOffer(callId: UUID) = CallMessage(SignalServiceProtos.CallMessage.Type.PRE_OFFER, fun preOffer(callId: UUID) = CallMessage(PRE_OFFER,
listOf(), listOf(),
listOf(), listOf(),
listOf(), listOf(),
callId callId
) )
fun offer(sdp: String, callId: UUID) = CallMessage(SignalServiceProtos.CallMessage.Type.OFFER, fun offer(sdp: String, callId: UUID) = CallMessage(OFFER,
listOf(sdp), listOf(sdp),
listOf(), listOf(),
listOf(), listOf(),
callId 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? { fun fromProto(proto: SignalServiceProtos.Content): CallMessage? {
val callMessageProto = if (proto.hasCallMessage()) proto.callMessage else return null val callMessageProto = if (proto.hasCallMessage()) proto.callMessage else return null