mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-25 17:37:57 +00:00
feat: handle discarding pending calls from linked devices
This commit is contained in:
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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,16 +477,20 @@ 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)
|
||||||
|
if (sendHangup) {
|
||||||
MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address)
|
MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleRemoteHangup() {
|
fun handleRemoteHangup() {
|
||||||
when (currentConnectionState) {
|
when (currentConnectionState) {
|
||||||
|
@@ -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")
|
||||||
|
@@ -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"
|
||||||
|
@@ -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
|
||||||
|
Reference in New Issue
Block a user