session-android/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt

151 lines
6.8 KiB
Kotlin

package org.thoughtcrime.securesms.webrtc
import android.app.NotificationManager
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.database.StorageProtocol
import org.session.libsession.messaging.calls.CallMessageType
import org.session.libsession.messaging.messages.control.CallMessage
import org.session.libsession.messaging.utilities.WebRtcUtils
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ANSWER
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.END_CALL
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CANDIDATES
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.OFFER
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PRE_OFFER
import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.PROVISIONAL_ANSWER
import org.session.libsignal.utilities.Log
import org.thoughtcrime.securesms.service.WebRtcCallService
import org.thoughtcrime.securesms.util.CallNotificationBuilder
import org.webrtc.IceCandidate
class CallMessageProcessor(private val context: Context, private val textSecurePreferences: TextSecurePreferences, lifecycle: Lifecycle, private val storage: StorageProtocol) {
init {
lifecycle.coroutineScope.launch(IO) {
while (isActive) {
val nextMessage = WebRtcUtils.SIGNAL_QUEUE.receive()
Log.d("Loki", nextMessage.type?.name ?: "CALL MESSAGE RECEIVED")
val sender = nextMessage.sender ?: continue
val approvedContact = Recipient.from(context, Address.fromSerialized(sender), false).isApproved
Log.i("Loki", "Contact is approved?: $approvedContact")
if (!approvedContact && storage.getUserPublicKey() != sender) continue
if (!textSecurePreferences.isCallNotificationsEnabled()) {
Log.d("Loki","Dropping call message if call notifications disabled")
if (nextMessage.type != PRE_OFFER) continue
val sentTimestamp = nextMessage.sentTimestamp ?: continue
if (textSecurePreferences.setShownCallNotification()) {
// first time call notification encountered
val notification = CallNotificationBuilder.getFirstCallNotification(context)
context.getSystemService(NotificationManager::class.java).notify(CallNotificationBuilder.WEBRTC_NOTIFICATION, notification)
insertMissedCall(sender, sentTimestamp, isFirstCall = true)
} else {
insertMissedCall(sender, sentTimestamp)
}
continue
}
when (nextMessage.type) {
OFFER -> incomingCall(nextMessage)
ANSWER -> incomingAnswer(nextMessage)
END_CALL -> incomingHangup(nextMessage)
ICE_CANDIDATES -> handleIceCandidates(nextMessage)
PRE_OFFER -> incomingPreOffer(nextMessage)
PROVISIONAL_ANSWER, null -> {} // TODO: if necessary
}
}
}
}
private fun insertMissedCall(sender: String, sentTimestamp: Long, isFirstCall: Boolean = false) {
val currentUserPublicKey = storage.getUserPublicKey()
if (sender == currentUserPublicKey) return // don't insert a "missed" due to call notifications disabled if it's our own sender
if (isFirstCall) {
storage.insertCallMessage(sender, CallMessageType.CALL_FIRST_MISSED, sentTimestamp)
} else {
storage.insertCallMessage(sender, CallMessageType.CALL_MISSED, sentTimestamp)
}
}
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 sender = callMessage.sender ?: return
val iceCandidates = callMessage.iceCandidates()
if (iceCandidates.isEmpty()) return
val iceIntent = WebRtcCallService.iceCandidates(
context = context,
iceCandidates = iceCandidates,
callId = callId,
address = Address.fromSerialized(sender)
)
context.startService(iceIntent)
}
private fun incomingPreOffer(callMessage: CallMessage) {
// handle notification state
val recipientAddress = callMessage.sender ?: return
val callId = callMessage.callId ?: return
val incomingIntent = WebRtcCallService.preOffer(
context = context,
address = Address.fromSerialized(recipientAddress),
callId = callId,
callTime = callMessage.sentTimestamp!!
)
ContextCompat.startForegroundService(context, incomingIntent)
}
private fun incomingCall(callMessage: CallMessage) {
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,
callTime = callMessage.sentTimestamp!!
)
ContextCompat.startForegroundService(context, incomingIntent)
}
private fun CallMessage.iceCandidates(): List<IceCandidate> {
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])
}
}
}