feat: add pre-offer information and action handling in web rtc call service
This commit is contained in:
parent
276f808ca3
commit
8e56f76fc1
|
@ -5,13 +5,9 @@ import android.content.BroadcastReceiver
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.BlendMode
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.media.AudioManager
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -26,7 +22,6 @@ import kotlinx.coroutines.launch
|
|||
import network.loki.messenger.R
|
||||
import org.session.libsession.avatars.ProfileContactPhoto
|
||||
import org.session.libsession.messaging.contacts.Contact
|
||||
import org.session.libsession.utilities.Address
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
|
@ -36,15 +31,14 @@ import org.thoughtcrime.securesms.util.AvatarPlaceholderGenerator
|
|||
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
|
||||
import org.thoughtcrime.securesms.webrtc.CallViewModel
|
||||
import org.thoughtcrime.securesms.webrtc.CallViewModel.State.*
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice.*
|
||||
import org.webrtc.IceCandidate
|
||||
import java.util.*
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
||||
|
||||
companion object {
|
||||
const val ACTION_PRE_OFFER = "pre-offer"
|
||||
const val ACTION_ANSWER = "answer"
|
||||
const val ACTION_END = "end-call"
|
||||
|
||||
|
@ -54,6 +48,7 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
|||
private val viewModel by viewModels<CallViewModel>()
|
||||
private val glide by lazy { GlideApp.with(this) }
|
||||
private var uiJob: Job? = null
|
||||
private var wantsToAnswer = false
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
|
@ -88,8 +83,11 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
|||
.execute()
|
||||
|
||||
if (intent.action == ACTION_ANSWER) {
|
||||
val answerIntent = WebRtcCallService.acceptCallIntent(this)
|
||||
ContextCompat.startForegroundService(this,answerIntent)
|
||||
answerCall()
|
||||
}
|
||||
|
||||
if (intent.action == ACTION_PRE_OFFER) {
|
||||
wantsToAnswer = true
|
||||
}
|
||||
|
||||
speakerPhoneButton.setOnClickListener {
|
||||
|
@ -141,6 +139,11 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
|||
|
||||
}
|
||||
|
||||
private fun answerCall() {
|
||||
val answerIntent = WebRtcCallService.acceptCallIntent(this)
|
||||
ContextCompat.startForegroundService(this,answerIntent)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
|
@ -160,6 +163,9 @@ class WebRtcCallActivity: PassphraseRequiredActionBarActivity() {
|
|||
viewModel.callState.collect { state ->
|
||||
when (state) {
|
||||
CALL_RINGING -> {
|
||||
if (wantsToAnswer) {
|
||||
answerCall()
|
||||
}
|
||||
}
|
||||
CALL_OUTGOING -> {
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.os.ResultReceiver
|
||||
import android.telephony.PhoneStateListener
|
||||
|
@ -22,6 +21,7 @@ import org.thoughtcrime.securesms.calls.WebRtcCallActivity
|
|||
import org.thoughtcrime.securesms.util.CallNotificationBuilder
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_ESTABLISHED
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_CONNECTING
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_PRE_OFFER
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_RINGING
|
||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING
|
||||
import org.thoughtcrime.securesms.webrtc.*
|
||||
|
@ -58,6 +58,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
|
|||
const val ACTION_CHECK_TIMEOUT = "CHECK_TIMEOUT"
|
||||
const val ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL"
|
||||
|
||||
const val ACTION_PRE_OFFER = "PRE_OFFER"
|
||||
const val ACTION_RESPONSE_MESSAGE = "RESPONSE_MESSAGE"
|
||||
const val ACTION_ICE_MESSAGE = "ICE_MESSAGE"
|
||||
const val ACTION_CALL_CONNECTED = "CALL_CONNECTED"
|
||||
|
@ -113,6 +114,12 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
|
|||
.putExtra(EXTRA_CALL_ID, callId)
|
||||
.putExtra(EXTRA_REMOTE_DESCRIPTION, sdp)
|
||||
|
||||
fun preOffer(context: Context, address: Address, callId: UUID) =
|
||||
Intent(context, WebRtcCallService::class.java)
|
||||
.setAction(ACTION_PRE_OFFER)
|
||||
.putExtra(EXTRA_RECIPIENT_ADDRESS, address)
|
||||
.putExtra(EXTRA_CALL_ID, callId)
|
||||
|
||||
fun iceCandidates(context: Context, address: Address, iceCandidates: List<IceCandidate>, callId: UUID) =
|
||||
Intent(context, WebRtcCallService::class.java)
|
||||
.setAction(ACTION_ICE_MESSAGE)
|
||||
|
@ -176,7 +183,10 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
|
|||
return callManager.callId == expectedCallId
|
||||
}
|
||||
|
||||
private fun isBusy() = callManager.isBusy(this)
|
||||
|
||||
private fun isPreOffer() = callManager.isPreOffer()
|
||||
|
||||
private fun isBusy(intent: Intent) = callManager.isBusy(this, getCallId(intent))
|
||||
|
||||
private fun isIdle() = callManager.isIdle()
|
||||
|
||||
|
@ -188,10 +198,11 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
|
|||
val action = intent.action
|
||||
Log.d("Loki", "Handling ${intent.action}")
|
||||
when {
|
||||
action == ACTION_INCOMING_RING && isSameCall(intent) -> handleNewOffer(intent)
|
||||
action == ACTION_INCOMING_RING && isBusy() -> handleBusyCall(intent)
|
||||
action == ACTION_INCOMING_RING && isSameCall(intent) && !isPreOffer() -> handleNewOffer(intent)
|
||||
action == ACTION_PRE_OFFER && isIdle() -> handlePreOffer(intent)
|
||||
action == ACTION_INCOMING_RING && isBusy(intent) -> handleBusyCall(intent)
|
||||
action == ACTION_REMOTE_BUSY -> handleBusyMessage(intent)
|
||||
action == ACTION_INCOMING_RING && isIdle() -> handleIncomingRing(intent)
|
||||
action == ACTION_INCOMING_RING && isPreOffer() -> handleIncomingRing(intent)
|
||||
action == ACTION_OUTGOING_CALL && isIdle() -> handleOutgoingCall(intent)
|
||||
action == ACTION_ANSWER_CALL -> handleAnswerCall(intent)
|
||||
action == ACTION_DENY_CALL -> handleDenyCall(intent)
|
||||
|
@ -295,16 +306,28 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
|
|||
callManager.onNewOffer(offer, callId, recipient)
|
||||
}
|
||||
|
||||
private fun handlePreOffer(intent: Intent) {
|
||||
if (!callManager.isIdle()) {
|
||||
Log.d(TAG, "Handling pre-offer from non-idle state")
|
||||
return
|
||||
}
|
||||
val callId = getCallId(intent)
|
||||
val recipient = getRemoteRecipient(intent)
|
||||
setCallInProgressNotification(TYPE_INCOMING_PRE_OFFER, recipient)
|
||||
callManager.onPreOffer(callId, recipient)
|
||||
callManager.postViewModelState(CallViewModel.State.CALL_PRE_INIT)
|
||||
callManager.initializeAudioForCall()
|
||||
callManager.startIncomingRinger()
|
||||
}
|
||||
|
||||
private fun handleIncomingRing(intent: Intent) {
|
||||
if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Incoming ring on non-idle")
|
||||
if (!callManager.isPreOffer() && !callManager.isIdle()) throw IllegalStateException("Incoming ring on non-idle")
|
||||
|
||||
val callId = getCallId(intent)
|
||||
val recipient = getRemoteRecipient(intent)
|
||||
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_RINGING, recipient)
|
||||
}
|
||||
setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient)
|
||||
callManager.clearPendingIceUpdates()
|
||||
callManager.onIncomingRing(offer, callId, recipient, timestamp)
|
||||
callManager.postConnectionEvent(STATE_LOCAL_RINGING)
|
||||
|
@ -404,7 +427,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
|
|||
}
|
||||
|
||||
private fun handleDenyCall(intent: Intent) {
|
||||
if (callManager.currentConnectionState != STATE_LOCAL_RINGING) {
|
||||
if (callManager.currentConnectionState != STATE_LOCAL_RINGING && !callManager.isPreOffer()) {
|
||||
Log.e(TAG,"Can only deny from ringing!")
|
||||
return
|
||||
}
|
||||
|
@ -523,7 +546,7 @@ class WebRtcCallService: Service(), PeerConnection.Observer {
|
|||
val callId = callManager.callId ?: return
|
||||
val callState = callManager.currentConnectionState
|
||||
|
||||
if (callId == getCallId(intent) && callState != STATE_CONNECTED) {
|
||||
if (callId == getCallId(intent) && callState !in arrayOf(STATE_CONNECTED)) {
|
||||
Log.w(TAG, "Timing out call: $callId")
|
||||
handleLocalHangup(intent)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ class CallNotificationBuilder {
|
|||
const val TYPE_OUTGOING_RINGING = 2
|
||||
const val TYPE_ESTABLISHED = 3
|
||||
const val TYPE_INCOMING_CONNECTING = 4
|
||||
const val TYPE_INCOMING_PRE_OFFER = 5
|
||||
|
||||
@JvmStatic
|
||||
fun getCallInProgressNotification(context: Context, type: Int, recipient: Recipient?): Notification {
|
||||
|
@ -46,6 +47,23 @@ class CallNotificationBuilder {
|
|||
builder.setContentText(context.getString(R.string.CallNotificationBuilder_connecting))
|
||||
builder.priority = NotificationCompat.PRIORITY_LOW
|
||||
}
|
||||
TYPE_INCOMING_PRE_OFFER -> {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call))
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
builder.addAction(getServiceNotificationAction(
|
||||
context,
|
||||
WebRtcCallService.ACTION_DENY_CALL,
|
||||
R.drawable.ic_close_grey600_32dp,
|
||||
R.string.NotificationBarManager__deny_call
|
||||
))
|
||||
builder.addAction(getActivityNotificationAction(
|
||||
context,
|
||||
WebRtcCallActivity.ACTION_PRE_OFFER,
|
||||
R.drawable.ic_phone_grey600_32dp,
|
||||
R.string.NotificationBarManager__answer_call
|
||||
))
|
||||
builder.priority = NotificationCompat.PRIORITY_HIGH
|
||||
}
|
||||
TYPE_INCOMING_RINGING -> {
|
||||
builder.setContentText(context.getString(R.string.NotificationBarManager__incoming_signal_call))
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
|
|
|
@ -36,7 +36,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
CallDataListener, CameraEventListener, DataChannel.Observer {
|
||||
|
||||
enum class CallState {
|
||||
STATE_IDLE, STATE_DIALING, STATE_ANSWERING, STATE_REMOTE_RINGING, STATE_LOCAL_RINGING, STATE_CONNECTED
|
||||
STATE_IDLE, STATE_PRE_OFFER, STATE_DIALING, STATE_ANSWERING, STATE_REMOTE_RINGING, STATE_LOCAL_RINGING, STATE_CONNECTED
|
||||
}
|
||||
|
||||
sealed class StateEvent {
|
||||
|
@ -101,7 +101,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
private val _audioDeviceEvents = MutableStateFlow(AudioDeviceUpdate(AudioDevice.NONE, setOf()))
|
||||
val audioDeviceEvents = _audioDeviceEvents.asSharedFlow()
|
||||
|
||||
|
||||
val currentConnectionState
|
||||
get() = (_connectionEvents.value as CallStateUpdate).state
|
||||
|
||||
|
@ -111,6 +110,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
|
||||
var pendingOffer: String? = null
|
||||
var pendingOfferTime: Long = -1
|
||||
var preOfferCallData: PreOffer? = null
|
||||
var callId: UUID? = null
|
||||
var recipient: Recipient? = null
|
||||
set(value) {
|
||||
|
@ -163,8 +163,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
|
||||
}
|
||||
|
||||
fun isBusy(context: Context) = currentConnectionState != CallState.STATE_IDLE
|
||||
|| context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE
|
||||
fun isBusy(context: Context, callId: UUID) = callId != this.callId && (currentConnectionState != CallState.STATE_IDLE
|
||||
|| context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE)
|
||||
|
||||
fun isPreOffer() = currentConnectionState == CallState.STATE_PRE_OFFER
|
||||
|
||||
fun isIdle() = currentConnectionState == CallState.STATE_IDLE
|
||||
|
||||
|
@ -347,6 +349,16 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
localCameraState = newCameraState
|
||||
}
|
||||
|
||||
fun onPreOffer(callId: UUID, recipient: Recipient) {
|
||||
if (preOfferCallData != null) {
|
||||
Log.d(TAG, "Received new pre-offer when we are already expecting one")
|
||||
}
|
||||
this.recipient = recipient
|
||||
this.callId = callId
|
||||
preOfferCallData = PreOffer(callId, recipient)
|
||||
postConnectionEvent(CallState.STATE_PRE_OFFER)
|
||||
}
|
||||
|
||||
fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise<Unit, Exception> {
|
||||
if (callId != this.callId) return Promise.ofFail(NullPointerException("No callId"))
|
||||
if (recipient != this.recipient) return Promise.ofFail(NullPointerException("No recipient"))
|
||||
|
@ -361,7 +373,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat): PeerConne
|
|||
}
|
||||
|
||||
fun onIncomingRing(offer: String, callId: UUID, recipient: Recipient, callTime: Long) {
|
||||
if (currentConnectionState != CallState.STATE_IDLE) return
|
||||
if (currentConnectionState !in arrayOf(CallState.STATE_IDLE, CallState.STATE_PRE_OFFER)) return
|
||||
|
||||
this.callId = callId
|
||||
this.recipient = recipient
|
||||
|
|
|
@ -77,6 +77,14 @@ class CallMessageProcessor(private val context: Context, lifecycle: Lifecycle) {
|
|||
|
||||
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,
|
||||
)
|
||||
ContextCompat.startForegroundService(context, incomingIntent)
|
||||
}
|
||||
|
||||
private fun incomingCall(callMessage: CallMessage) {
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
package org.thoughtcrime.securesms.webrtc
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||
import org.webrtc.SurfaceViewRenderer
|
||||
import javax.inject.Inject
|
||||
|
@ -33,6 +30,7 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
|
|||
enum class State {
|
||||
CALL_PENDING,
|
||||
|
||||
CALL_PRE_INIT,
|
||||
CALL_INCOMING,
|
||||
CALL_OUTGOING,
|
||||
CALL_CONNECTED,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package org.thoughtcrime.securesms.webrtc
|
||||
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import java.util.*
|
||||
|
||||
data class PreOffer(val callId: UUID, val recipient: Recipient)
|
Loading…
Reference in New Issue