mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-25 20:07:32 +00:00
feat: new call state processing
This commit is contained in:
@@ -25,12 +25,30 @@ import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_IN
|
|||||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_PRE_OFFER
|
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_INCOMING_RINGING
|
||||||
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING
|
import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING
|
||||||
import org.thoughtcrime.securesms.webrtc.*
|
import org.thoughtcrime.securesms.webrtc.AudioManagerCommand
|
||||||
|
import org.thoughtcrime.securesms.webrtc.CallManager
|
||||||
|
import org.thoughtcrime.securesms.webrtc.CallViewModel
|
||||||
|
import org.thoughtcrime.securesms.webrtc.HangUpRtcOnPstnCallAnsweredListener
|
||||||
|
import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver
|
||||||
|
import org.thoughtcrime.securesms.webrtc.NetworkChangeReceiver
|
||||||
|
import org.thoughtcrime.securesms.webrtc.PeerConnectionException
|
||||||
|
import org.thoughtcrime.securesms.webrtc.PowerButtonReceiver
|
||||||
|
import org.thoughtcrime.securesms.webrtc.ProximityLockRelease
|
||||||
|
import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager
|
||||||
|
import org.thoughtcrime.securesms.webrtc.WiredHeadsetStateReceiver
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
||||||
|
import org.thoughtcrime.securesms.webrtc.data.Event
|
||||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
||||||
import org.webrtc.*
|
import org.webrtc.DataChannel
|
||||||
import org.webrtc.PeerConnection.IceConnectionState.*
|
import org.webrtc.IceCandidate
|
||||||
import java.util.*
|
import org.webrtc.MediaStream
|
||||||
|
import org.webrtc.PeerConnection
|
||||||
|
import org.webrtc.PeerConnection.IceConnectionState.CONNECTED
|
||||||
|
import org.webrtc.PeerConnection.IceConnectionState.DISCONNECTED
|
||||||
|
import org.webrtc.PeerConnection.IceConnectionState.FAILED
|
||||||
|
import org.webrtc.RtpReceiver
|
||||||
|
import org.webrtc.SessionDescription
|
||||||
|
import java.util.UUID
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@@ -56,6 +74,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
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"
|
||||||
const val ACTION_CHECK_TIMEOUT = "CHECK_TIMEOUT"
|
const val ACTION_CHECK_TIMEOUT = "CHECK_TIMEOUT"
|
||||||
|
const val ACTION_CHECK_RECONNECT = "CHECK_RECONNECT"
|
||||||
|
const val ACTION_CHECK_RECONNECT_TIMEOUT = "CHECK_RECONNECT_TIMEOUT"
|
||||||
const val ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL"
|
const val ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL"
|
||||||
const val ACTION_WANTS_TO_ANSWER = "WANTS_TO_ANSWER"
|
const val ACTION_WANTS_TO_ANSWER = "WANTS_TO_ANSWER"
|
||||||
|
|
||||||
@@ -80,7 +100,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
const val EXTRA_WANTS_TO_ANSWER = "wants_to_answer"
|
const val EXTRA_WANTS_TO_ANSWER = "wants_to_answer"
|
||||||
|
|
||||||
const val INVALID_NOTIFICATION_ID = -1
|
const val INVALID_NOTIFICATION_ID = -1
|
||||||
private const val TIMEOUT_SECONDS = 30L
|
private const val TIMEOUT_SECONDS = 90L
|
||||||
|
private const val MAX_TIMEOUTS = 3
|
||||||
|
|
||||||
fun cameraEnabled(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java)
|
fun cameraEnabled(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_SET_MUTE_VIDEO)
|
.setAction(ACTION_SET_MUTE_VIDEO)
|
||||||
@@ -165,6 +186,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
@Inject lateinit var callManager: CallManager
|
@Inject lateinit var callManager: CallManager
|
||||||
|
|
||||||
private var wantsToAnswer = false
|
private var wantsToAnswer = false
|
||||||
|
private var currentTimeouts = 0
|
||||||
|
private var isNetworkAvailable = true
|
||||||
|
|
||||||
private val lockManager by lazy { LockManager(this) }
|
private val lockManager by lazy { LockManager(this) }
|
||||||
private val serviceExecutor = Executors.newSingleThreadExecutor()
|
private val serviceExecutor = Executors.newSingleThreadExecutor()
|
||||||
@@ -185,6 +208,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(WebRtcCallActivity.ACTION_END))
|
LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(WebRtcCallActivity.ACTION_END))
|
||||||
lockManager.updatePhoneState(LockManager.PhoneState.IDLE)
|
lockManager.updatePhoneState(LockManager.PhoneState.IDLE)
|
||||||
callManager.stop()
|
callManager.stop()
|
||||||
|
wantsToAnswer = false
|
||||||
|
currentTimeouts = 0
|
||||||
|
isNetworkAvailable = true
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,6 +265,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
|
action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent)
|
||||||
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
|
action == ACTION_ICE_CONNECTED -> handleIceConnected(intent)
|
||||||
action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent)
|
action == ACTION_CHECK_TIMEOUT -> handleCheckTimeout(intent)
|
||||||
|
action == ACTION_CHECK_RECONNECT -> handleCheckReconnect(intent)
|
||||||
|
action == ACTION_CHECK_RECONNECT_TIMEOUT -> handleCheckReconnectTimeout(intent)
|
||||||
action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent)
|
action == ACTION_IS_IN_CALL_QUERY -> handleIsInCallQuery(intent)
|
||||||
action == ACTION_UPDATE_AUDIO -> handleUpdateAudio(intent)
|
action == ACTION_UPDATE_AUDIO -> handleUpdateAudio(intent)
|
||||||
}
|
}
|
||||||
@@ -250,9 +278,10 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
callManager.registerListener(this)
|
callManager.registerListener(this)
|
||||||
wantsToAnswer = false
|
wantsToAnswer = false
|
||||||
|
isNetworkAvailable = false
|
||||||
registerIncomingPstnCallReceiver()
|
registerIncomingPstnCallReceiver()
|
||||||
registerWiredHeadsetStateReceiver()
|
registerWiredHeadsetStateReceiver()
|
||||||
registerWantsToAnswerReceiver() // TODO unregister
|
registerWantsToAnswerReceiver()
|
||||||
getSystemService(TelephonyManager::class.java)
|
getSystemService(TelephonyManager::class.java)
|
||||||
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
|
.listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE)
|
||||||
registerUncaughtExceptionHandler()
|
registerUncaughtExceptionHandler()
|
||||||
@@ -322,7 +351,6 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
val callId = getCallId(intent)
|
val callId = getCallId(intent)
|
||||||
val recipient = getRemoteRecipient(intent)
|
val recipient = getRemoteRecipient(intent)
|
||||||
val sentTimestamp = intent.getLongExtra(EXTRA_TIMESTAMP, -1)
|
|
||||||
|
|
||||||
if (isIncomingMessageExpired(intent)) {
|
if (isIncomingMessageExpired(intent)) {
|
||||||
insertMissedCall(recipient, true)
|
insertMissedCall(recipient, true)
|
||||||
@@ -330,17 +358,16 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callManager.onPreOffer(callId, recipient) {
|
||||||
setCallInProgressNotification(TYPE_INCOMING_PRE_OFFER, recipient)
|
setCallInProgressNotification(TYPE_INCOMING_PRE_OFFER, recipient)
|
||||||
callManager.onPreOffer(callId, recipient, sentTimestamp)
|
|
||||||
callManager.postViewModelState(CallViewModel.State.CALL_PRE_INIT)
|
callManager.postViewModelState(CallViewModel.State.CALL_PRE_INIT)
|
||||||
callManager.initializeAudioForCall()
|
callManager.initializeAudioForCall()
|
||||||
callManager.startIncomingRinger()
|
callManager.startIncomingRinger()
|
||||||
callManager.setAudioEnabled(true)
|
callManager.setAudioEnabled(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleIncomingRing(intent: Intent) {
|
private fun handleIncomingRing(intent: Intent) {
|
||||||
if (!callManager.isPreOffer() && !callManager.isIdle()) throw IllegalStateException("Incoming ring on non-idle")
|
|
||||||
|
|
||||||
val callId = getCallId(intent)
|
val callId = getCallId(intent)
|
||||||
val recipient = getRemoteRecipient(intent)
|
val recipient = getRemoteRecipient(intent)
|
||||||
val preOffer = callManager.preOfferCallData
|
val preOffer = callManager.preOfferCallData
|
||||||
@@ -352,22 +379,21 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
|
|
||||||
val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return
|
val offer = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) ?: return
|
||||||
val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, -1)
|
val timestamp = intent.getLongExtra(EXTRA_TIMESTAMP, -1)
|
||||||
|
|
||||||
|
callManager.onIncomingRing(offer, callId, recipient, timestamp) {
|
||||||
if (wantsToAnswer) {
|
if (wantsToAnswer) {
|
||||||
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
|
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
|
||||||
} else {
|
} else {
|
||||||
setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient)
|
setCallInProgressNotification(TYPE_INCOMING_RINGING, recipient)
|
||||||
}
|
}
|
||||||
callManager.clearPendingIceUpdates()
|
callManager.clearPendingIceUpdates()
|
||||||
callManager.onIncomingRing(offer, callId, recipient, timestamp)
|
|
||||||
callManager.postConnectionEvent(STATE_LOCAL_RINGING)
|
|
||||||
callManager.postViewModelState(CallViewModel.State.CALL_RINGING)
|
callManager.postViewModelState(CallViewModel.State.CALL_RINGING)
|
||||||
registerPowerButtonReceiver()
|
registerPowerButtonReceiver()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleOutgoingCall(intent: Intent) {
|
private fun handleOutgoingCall(intent: Intent) {
|
||||||
if (callManager.currentConnectionState != STATE_IDLE) throw IllegalStateException("Dialing from non-idle")
|
callManager.postConnectionEvent(Event.SendPreOffer) {
|
||||||
|
|
||||||
callManager.postConnectionEvent(STATE_DIALING)
|
|
||||||
val recipient = getRemoteRecipient(intent)
|
val recipient = getRemoteRecipient(intent)
|
||||||
callManager.recipient = recipient
|
callManager.recipient = recipient
|
||||||
val callId = UUID.randomUUID()
|
val callId = UUID.randomUUID()
|
||||||
@@ -393,46 +419,49 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
if (isConsistentState(expectedState, expectedCallId, callManager.currentConnectionState, callManager.callId)) {
|
if (isConsistentState(expectedState, expectedCallId, callManager.currentConnectionState, callManager.callId)) {
|
||||||
Log.e(TAG,e)
|
Log.e(TAG,e)
|
||||||
callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE)
|
callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE)
|
||||||
|
callManager.postConnectionError()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG,e)
|
Log.e(TAG,e)
|
||||||
|
callManager.postConnectionError()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleAnswerCall(intent: Intent) {
|
private fun handleAnswerCall(intent: Intent) {
|
||||||
val recipient = callManager.recipient ?: return
|
val recipient = callManager.recipient ?: return
|
||||||
|
|
||||||
if (callManager.currentConnectionState != STATE_LOCAL_RINGING) {
|
|
||||||
if (callManager.currentConnectionState == STATE_PRE_OFFER) {
|
|
||||||
// show answer state from pre-offer
|
|
||||||
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
|
|
||||||
}
|
|
||||||
Log.e(TAG, "Can only answer from ringing!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val pending = callManager.pendingOffer ?: return
|
val pending = callManager.pendingOffer ?: return
|
||||||
val callId = callManager.callId ?: return
|
val callId = callManager.callId ?: return
|
||||||
val timestamp = callManager.pendingOfferTime
|
val timestamp = callManager.pendingOfferTime
|
||||||
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
|
|
||||||
|
if (callManager.currentConnectionState != CallState.RemoteRing) {
|
||||||
|
Log.e(TAG, "Can only answer from ringing!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
intent.putExtra(EXTRA_CALL_ID, callId)
|
intent.putExtra(EXTRA_CALL_ID, callId)
|
||||||
intent.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address)
|
intent.putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address)
|
||||||
intent.putExtra(EXTRA_REMOTE_DESCRIPTION, pending)
|
intent.putExtra(EXTRA_REMOTE_DESCRIPTION, pending)
|
||||||
intent.putExtra(EXTRA_TIMESTAMP, timestamp)
|
intent.putExtra(EXTRA_TIMESTAMP, timestamp)
|
||||||
|
|
||||||
callManager.silenceIncomingRinger()
|
|
||||||
callManager.postConnectionEvent(STATE_ANSWERING)
|
|
||||||
callManager.postViewModelState(CallViewModel.State.CALL_INCOMING)
|
|
||||||
|
|
||||||
if (isIncomingMessageExpired(intent)) {
|
if (isIncomingMessageExpired(intent)) {
|
||||||
|
val didHangup = callManager.postConnectionEvent(Event.TimeOut) {
|
||||||
insertMissedCall(recipient, true)
|
insertMissedCall(recipient, true)
|
||||||
terminate()
|
terminate()
|
||||||
|
}
|
||||||
|
if (didHangup) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callManager.postConnectionEvent(Event.SendAnswer) {
|
||||||
|
setCallInProgressNotification(TYPE_INCOMING_CONNECTING, recipient)
|
||||||
|
|
||||||
|
callManager.silenceIncomingRinger()
|
||||||
|
callManager.postViewModelState(CallViewModel.State.CALL_INCOMING)
|
||||||
|
|
||||||
timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
|
||||||
@@ -448,6 +477,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) {
|
if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) {
|
||||||
Log.e(TAG, e)
|
Log.e(TAG, e)
|
||||||
insertMissedCall(recipient, true)
|
insertMissedCall(recipient, true)
|
||||||
|
callManager.postConnectionError()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -455,16 +485,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
callManager.setAudioEnabled(true)
|
callManager.setAudioEnabled(true)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG,e)
|
Log.e(TAG,e)
|
||||||
|
callManager.postConnectionError()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleDenyCall(intent: Intent) {
|
private fun handleDenyCall(intent: Intent) {
|
||||||
if (callManager.currentConnectionState !in CallState.CAN_DECLINE_STATES) {
|
|
||||||
Log.e(TAG,"Can only deny from ringing!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
callManager.handleDenyCall()
|
callManager.handleDenyCall()
|
||||||
terminate()
|
terminate()
|
||||||
}
|
}
|
||||||
@@ -509,7 +536,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
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) {
|
if (callManager.isCurrentUser(recipient) && callManager.currentConnectionState in CallState.OUTGOING_STATES) {
|
||||||
handleLocalHangup(intent)
|
handleLocalHangup(intent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -542,22 +569,20 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
|
|
||||||
private fun handleIceConnected(intent: Intent) {
|
private fun handleIceConnected(intent: Intent) {
|
||||||
val recipient = callManager.recipient ?: return
|
val recipient = callManager.recipient ?: return
|
||||||
if (callManager.currentConnectionState in arrayOf(STATE_ANSWERING)) {
|
val connected = callManager.postConnectionEvent(Event.Connect) {
|
||||||
callManager.postConnectionEvent(STATE_CONNECTED)
|
|
||||||
callManager.postViewModelState(CallViewModel.State.CALL_CONNECTED)
|
callManager.postViewModelState(CallViewModel.State.CALL_CONNECTED)
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Got ice connected out of state")
|
|
||||||
}
|
|
||||||
|
|
||||||
setCallInProgressNotification(TYPE_ESTABLISHED, recipient)
|
setCallInProgressNotification(TYPE_ESTABLISHED, recipient)
|
||||||
|
|
||||||
callManager.startCommunication(lockManager)
|
callManager.startCommunication(lockManager)
|
||||||
}
|
}
|
||||||
|
if (!connected) {
|
||||||
|
terminate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleIsInCallQuery(intent: Intent) {
|
private fun handleIsInCallQuery(intent: Intent) {
|
||||||
val listener = intent.getParcelableExtra<ResultReceiver>(EXTRA_RESULT_RECEIVER) ?: return
|
val listener = intent.getParcelableExtra<ResultReceiver>(EXTRA_RESULT_RECEIVER) ?: return
|
||||||
val currentState = callManager.currentConnectionState
|
val currentState = callManager.currentConnectionState
|
||||||
val isInCall = if (currentState in CONNECTED_STATES || currentState in PENDING_CONNECTION_STATES) 1 else 0
|
val isInCall = if (currentState in arrayOf(*CallState.PENDING_CONNECTION_STATES, CallState.Connected)) 1 else 0
|
||||||
listener.send(isInCall, bundleOf())
|
listener.send(isInCall, bundleOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -569,11 +594,32 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleCheckReconnect(intent: Intent) {
|
||||||
|
val callId = callManager.callId ?: return
|
||||||
|
val numTimeouts = ++currentTimeouts
|
||||||
|
|
||||||
|
if (callId == getCallId(intent) && isNetworkAvailable && numTimeouts <= 5) {
|
||||||
|
callManager.networkReestablished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleCheckReconnectTimeout(intent: Intent) {
|
||||||
|
val callId = callManager.callId ?: return
|
||||||
|
val callState = callManager.currentConnectionState
|
||||||
|
|
||||||
|
if (callId == getCallId(intent) && (callState !in arrayOf(CallState.Connected, CallState.Connecting))) {
|
||||||
|
Log.w(TAG, "Timing out reconnect: $callId")
|
||||||
|
handleLocalHangup(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private fun handleCheckTimeout(intent: Intent) {
|
private fun handleCheckTimeout(intent: Intent) {
|
||||||
val callId = callManager.callId ?: return
|
val callId = callManager.callId ?: return
|
||||||
val callState = callManager.currentConnectionState
|
val callState = callManager.currentConnectionState
|
||||||
|
|
||||||
if (callId == getCallId(intent) && (callState !in arrayOf(STATE_CONNECTED) || callManager.iceState == CHECKING)) {
|
if (callId == getCallId(intent) && (callState !in arrayOf(CallState.Connected, CallState.Connecting))) {
|
||||||
Log.w(TAG, "Timing out call: $callId")
|
Log.w(TAG, "Timing out call: $callId")
|
||||||
handleLocalHangup(intent)
|
handleLocalHangup(intent)
|
||||||
}
|
}
|
||||||
@@ -622,9 +668,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
callReceiver?.let { receiver ->
|
callReceiver?.let { receiver ->
|
||||||
unregisterReceiver(receiver)
|
unregisterReceiver(receiver)
|
||||||
}
|
}
|
||||||
networkChangedReceiver?.let { receiver ->
|
networkChangedReceiver?.unregister(this)
|
||||||
receiver.unregister(this)
|
|
||||||
}
|
|
||||||
wantsToAnswerReceiver?.let { receiver ->
|
wantsToAnswerReceiver?.let { receiver ->
|
||||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver)
|
||||||
}
|
}
|
||||||
@@ -632,12 +676,33 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
callReceiver = null
|
callReceiver = null
|
||||||
uncaughtExceptionHandlerManager?.unregister()
|
uncaughtExceptionHandlerManager?.unregister()
|
||||||
wantsToAnswer = false
|
wantsToAnswer = false
|
||||||
|
currentTimeouts = 0
|
||||||
|
isNetworkAvailable = true
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun networkChange(networkAvailable: Boolean) {
|
fun networkChange(networkAvailable: Boolean) {
|
||||||
if (networkAvailable && !callManager.isReestablishing && callManager.currentConnectionState in arrayOf(STATE_CONNECTED)) {
|
isNetworkAvailable = networkAvailable
|
||||||
callManager.networkReestablished()
|
if (networkAvailable && !callManager.isReestablishing && callManager.currentConnectionState == CallState.Connected) {
|
||||||
|
Log.d("Loki", "Should reconnected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CheckReconnectedRunnable(private val callId: UUID, private val context: Context): Runnable {
|
||||||
|
override fun run() {
|
||||||
|
val intent = Intent(context, WebRtcCallService::class.java)
|
||||||
|
.setAction(ACTION_CHECK_RECONNECT)
|
||||||
|
.putExtra(EXTRA_CALL_ID, callId)
|
||||||
|
context.startService(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ReconnectTimeoutRunnable(private val callId: UUID, private val context: Context): Runnable {
|
||||||
|
override fun run() {
|
||||||
|
val intent = Intent(context, WebRtcCallService::class.java)
|
||||||
|
.setAction(ACTION_CHECK_RECONNECT_TIMEOUT)
|
||||||
|
.putExtra(EXTRA_CALL_ID, callId)
|
||||||
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,16 +716,16 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private abstract class FailureListener<V>(
|
private abstract class FailureListener<V>(
|
||||||
expectedState: CallManager.CallState,
|
expectedState: CallState,
|
||||||
expectedCallId: UUID?,
|
expectedCallId: UUID?,
|
||||||
getState: () -> Pair<CallManager.CallState, UUID?>): StateAwareListener<V>(expectedState, expectedCallId, getState) {
|
getState: () -> Pair<CallState, UUID?>): StateAwareListener<V>(expectedState, expectedCallId, getState) {
|
||||||
override fun onSuccessContinue(result: V) {}
|
override fun onSuccessContinue(result: V) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private abstract class SuccessOnlyListener<V>(
|
private abstract class SuccessOnlyListener<V>(
|
||||||
expectedState: CallManager.CallState,
|
expectedState: CallState,
|
||||||
expectedCallId: UUID?,
|
expectedCallId: UUID?,
|
||||||
getState: () -> Pair<CallManager.CallState, UUID>): StateAwareListener<V>(expectedState, expectedCallId, getState) {
|
getState: () -> Pair<CallState, UUID>): StateAwareListener<V>(expectedState, expectedCallId, getState) {
|
||||||
override fun onFailureContinue(throwable: Throwable?) {
|
override fun onFailureContinue(throwable: Throwable?) {
|
||||||
Log.e(TAG, throwable)
|
Log.e(TAG, throwable)
|
||||||
throw AssertionError(throwable)
|
throw AssertionError(throwable)
|
||||||
@@ -668,9 +733,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private abstract class StateAwareListener<V>(
|
private abstract class StateAwareListener<V>(
|
||||||
private val expectedState: CallManager.CallState,
|
private val expectedState: CallState,
|
||||||
private val expectedCallId: UUID?,
|
private val expectedCallId: UUID?,
|
||||||
private val getState: ()->Pair<CallManager.CallState, UUID?>): FutureTaskListener<V> {
|
private val getState: ()->Pair<CallState, UUID?>): FutureTaskListener<V> {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = Log.tag(StateAwareListener::class.java)
|
private val TAG = Log.tag(StateAwareListener::class.java)
|
||||||
@@ -706,9 +771,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isConsistentState(
|
private fun isConsistentState(
|
||||||
expectedState: CallManager.CallState,
|
expectedState: CallState,
|
||||||
expectedCallId: UUID?,
|
expectedCallId: UUID?,
|
||||||
currentState: CallManager.CallState,
|
currentState: CallState,
|
||||||
currentCallId: UUID?
|
currentCallId: UUID?
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return expectedState == currentState && expectedCallId == currentCallId
|
return expectedState == currentState && expectedCallId == currentCallId
|
||||||
@@ -717,16 +782,22 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener {
|
|||||||
override fun onSignalingChange(p0: PeerConnection.SignalingState?) {}
|
override fun onSignalingChange(p0: PeerConnection.SignalingState?) {}
|
||||||
|
|
||||||
override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) {
|
override fun onIceConnectionChange(newState: PeerConnection.IceConnectionState?) {
|
||||||
if (newState in arrayOf(CONNECTED, COMPLETED)) {
|
if (newState == CONNECTED) {
|
||||||
val intent = Intent(this, WebRtcCallService::class.java)
|
val intent = Intent(this, WebRtcCallService::class.java)
|
||||||
.setAction(ACTION_ICE_CONNECTED)
|
.setAction(ACTION_ICE_CONNECTED)
|
||||||
startService(intent)
|
startService(intent)
|
||||||
} else if (newState == FAILED) {
|
} else if (newState == FAILED) {
|
||||||
val intent = Intent(this, WebRtcCallService::class.java)
|
val intent = hangupIntent(this)
|
||||||
.setAction(ACTION_LOCAL_HANGUP)
|
|
||||||
.putExtra(EXTRA_CALL_ID, callManager.callId)
|
|
||||||
|
|
||||||
startService(intent)
|
startService(intent)
|
||||||
|
} else if (newState == DISCONNECTED) {
|
||||||
|
callManager.callId?.let { callId ->
|
||||||
|
callManager.postViewModelState(CallViewModel.State.CALL_RECONNECTING)
|
||||||
|
timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), 5, TimeUnit.SECONDS)
|
||||||
|
timeoutExecutor.schedule(ReconnectTimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||||
|
} ?: run {
|
||||||
|
val intent = hangupIntent(this)
|
||||||
|
startService(intent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Log.d(TAG, "onIceConnectionChange: $newState")
|
Log.d(TAG, "onIceConnectionChange: $newState")
|
||||||
}
|
}
|
||||||
|
@@ -24,13 +24,13 @@ import org.session.libsignal.protos.SignalServiceProtos.CallMessage.Type.ICE_CAN
|
|||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioDeviceUpdate
|
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioDeviceUpdate
|
||||||
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioEnabled
|
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.AudioEnabled
|
||||||
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.CallStateUpdate
|
|
||||||
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.RecipientUpdate
|
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.RecipientUpdate
|
||||||
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.VideoEnabled
|
import org.thoughtcrime.securesms.webrtc.CallManager.StateEvent.VideoEnabled
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
|
import org.thoughtcrime.securesms.webrtc.audio.AudioManagerCompat
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager
|
||||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice
|
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager.AudioDevice
|
||||||
|
import org.thoughtcrime.securesms.webrtc.data.Event
|
||||||
import org.thoughtcrime.securesms.webrtc.data.StateProcessor
|
import org.thoughtcrime.securesms.webrtc.data.StateProcessor
|
||||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
import org.thoughtcrime.securesms.webrtc.locks.LockManager
|
||||||
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
|
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
|
||||||
@@ -163,8 +163,12 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
signalAudioManager.handleCommand(AudioManagerCommand.SilenceIncomingRinger)
|
signalAudioManager.handleCommand(AudioManagerCommand.SilenceIncomingRinger)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postConnectionEvent(newState: CallState) {
|
fun postConnectionEvent(transition: Event, onSuccess: ()->Unit): Boolean {
|
||||||
_connectionEvents.value = CallStateUpdate(newState)
|
return stateProcessor.processEvent(transition, onSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postConnectionError(): Boolean {
|
||||||
|
return stateProcessor.processEvent(Event.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun postViewModelState(newState: CallViewModel.State) {
|
fun postViewModelState(newState: CallViewModel.State) {
|
||||||
@@ -221,7 +225,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setAudioEnabled(isEnabled: Boolean) {
|
fun setAudioEnabled(isEnabled: Boolean) {
|
||||||
currentConnectionState.withState(*PENDING_CONNECTION_STATES)) {
|
currentConnectionState.withState(*CallState.CAN_HANGUP_STATES) {
|
||||||
peerConnection?.setAudioEnabled(isEnabled)
|
peerConnection?.setAudioEnabled(isEnabled)
|
||||||
_audioEvents.value = AudioEnabled(true)
|
_audioEvents.value = AudioEnabled(true)
|
||||||
}
|
}
|
||||||
@@ -345,13 +349,10 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
_audioDeviceEvents.value = AudioDeviceUpdate(activeDevice, devices)
|
_audioDeviceEvents.value = AudioDeviceUpdate(activeDevice, devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CallState.withState(vararg expected: CallState, transition: () -> Unit) {
|
|
||||||
if (this in expected) transition()
|
|
||||||
else Log.w(TAG,"Tried to transition state $this but expected $expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun stop() {
|
fun stop() {
|
||||||
signalAudioManager.handleCommand(AudioManagerCommand.Stop(currentConnectionState in OUTGOING_STATES))
|
val isOutgoing = currentConnectionState in CallState.OUTGOING_STATES
|
||||||
|
stateProcessor.processEvent(Event.Cleanup) {
|
||||||
|
signalAudioManager.handleCommand(AudioManagerCommand.Stop(isOutgoing))
|
||||||
peerConnection?.dispose()
|
peerConnection?.dispose()
|
||||||
peerConnection = null
|
peerConnection = null
|
||||||
|
|
||||||
@@ -364,7 +365,6 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
remoteRenderer = null
|
remoteRenderer = null
|
||||||
eglBase = null
|
eglBase = null
|
||||||
|
|
||||||
_connectionEvents.value = CallStateUpdate(CallState.STATE_IDLE)
|
|
||||||
localCameraState = CameraState.UNKNOWN
|
localCameraState = CameraState.UNKNOWN
|
||||||
recipient = null
|
recipient = null
|
||||||
callId = null
|
callId = null
|
||||||
@@ -377,19 +377,22 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
pendingOutgoingIceUpdates.clear()
|
pendingOutgoingIceUpdates.clear()
|
||||||
pendingIncomingIceUpdates.clear()
|
pendingIncomingIceUpdates.clear()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCameraSwitchCompleted(newCameraState: CameraState) {
|
override fun onCameraSwitchCompleted(newCameraState: CameraState) {
|
||||||
localCameraState = newCameraState
|
localCameraState = newCameraState
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onPreOffer(callId: UUID, recipient: Recipient, sentTimestamp: Long) {
|
fun onPreOffer(callId: UUID, recipient: Recipient, onSuccess: () -> Unit) {
|
||||||
|
stateProcessor.processEvent(Event.ReceivePreOffer) {
|
||||||
if (preOfferCallData != null) {
|
if (preOfferCallData != null) {
|
||||||
Log.d(TAG, "Received new pre-offer when we are already expecting one")
|
Log.d(TAG, "Received new pre-offer when we are already expecting one")
|
||||||
}
|
}
|
||||||
this.recipient = recipient
|
this.recipient = recipient
|
||||||
this.callId = callId
|
this.callId = callId
|
||||||
preOfferCallData = PreOffer(callId, recipient)
|
preOfferCallData = PreOffer(callId, recipient)
|
||||||
postConnectionEvent(CallState.STATE_PRE_OFFER)
|
onSuccess()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise<Unit, Exception> {
|
fun onNewOffer(offer: String, callId: UUID, recipient: Recipient): Promise<Unit, Exception> {
|
||||||
@@ -407,15 +410,16 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
return MessageSender.sendNonDurably(answerMessage, recipient.address)
|
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, onSuccess: () -> Unit) {
|
||||||
if (currentConnectionState !in arrayOf(CallState.STATE_IDLE, CallState.STATE_PRE_OFFER)) return
|
postConnectionEvent(Event.ReceiveOffer) {
|
||||||
|
|
||||||
this.callId = callId
|
this.callId = callId
|
||||||
this.recipient = recipient
|
this.recipient = recipient
|
||||||
this.pendingOffer = offer
|
this.pendingOffer = offer
|
||||||
this.pendingOfferTime = callTime
|
this.pendingOfferTime = callTime
|
||||||
initializeAudioForCall()
|
initializeAudioForCall()
|
||||||
startIncomingRinger()
|
startIncomingRinger()
|
||||||
|
onSuccess()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onIncomingCall(context: Context, isAlwaysTurn: Boolean = false): Promise<Unit, Exception> {
|
fun onIncomingCall(context: Context, isAlwaysTurn: Boolean = false): Promise<Unit, Exception> {
|
||||||
@@ -472,6 +476,11 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
?: return Promise.ofFail(NullPointerException("localRenderer is null"))
|
?: return Promise.ofFail(NullPointerException("localRenderer is null"))
|
||||||
val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null"))
|
val base = eglBase ?: return Promise.ofFail(NullPointerException("eglBase is null"))
|
||||||
|
|
||||||
|
val sentOffer = stateProcessor.processEvent(Event.SendOffer)
|
||||||
|
|
||||||
|
if (!sentOffer) {
|
||||||
|
return Promise.ofFail(Exception("Couldn't transition to sent offer state"))
|
||||||
|
} else {
|
||||||
val connection = PeerConnectionWrapper(
|
val connection = PeerConnectionWrapper(
|
||||||
context,
|
context,
|
||||||
factory,
|
factory,
|
||||||
@@ -499,6 +508,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
), recipient.address)
|
), recipient.address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun callNotSetup(): Boolean =
|
fun callNotSetup(): Boolean =
|
||||||
peerConnection == null || dataChannel == null || recipient == null || callId == null
|
peerConnection == null || dataChannel == null || recipient == null || callId == null
|
||||||
@@ -507,10 +517,12 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
val callId = callId ?: return
|
val callId = callId ?: return
|
||||||
val recipient = recipient ?: return
|
val recipient = recipient ?: return
|
||||||
val userAddress = storage.getUserPublicKey() ?: return
|
val userAddress = storage.getUserPublicKey() ?: return
|
||||||
|
stateProcessor.processEvent(Event.DeclineCall) {
|
||||||
MessageSender.sendNonDurably(CallMessage.endCall(callId), Address.fromSerialized(userAddress))
|
MessageSender.sendNonDurably(CallMessage.endCall(callId), Address.fromSerialized(userAddress))
|
||||||
MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address)
|
MessageSender.sendNonDurably(CallMessage.endCall(callId), recipient.address)
|
||||||
insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_MISSED)
|
insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_MISSED)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleLocalHangup(intentRecipient: Recipient?) {
|
fun handleLocalHangup(intentRecipient: Recipient?) {
|
||||||
val recipient = recipient ?: return
|
val recipient = recipient ?: return
|
||||||
@@ -520,6 +532,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
val sendHangup = intentRecipient == null || (intentRecipient == recipient && recipient.address.serialize() != currentUserPublicKey)
|
val sendHangup = intentRecipient == null || (intentRecipient == recipient && recipient.address.serialize() != currentUserPublicKey)
|
||||||
|
|
||||||
postViewModelState(CallViewModel.State.CALL_DISCONNECTED)
|
postViewModelState(CallViewModel.State.CALL_DISCONNECTED)
|
||||||
|
stateProcessor.processEvent(Event.Hangup)
|
||||||
if (sendHangup) {
|
if (sendHangup) {
|
||||||
dataChannel?.let { channel ->
|
dataChannel?.let { channel ->
|
||||||
val buffer = DataChannel.Buffer(ByteBuffer.wrap(HANGUP_JSON.toString().encodeToByteArray()), false)
|
val buffer = DataChannel.Buffer(ByteBuffer.wrap(HANGUP_JSON.toString().encodeToByteArray()), false)
|
||||||
@@ -535,8 +548,8 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
|
|
||||||
fun handleRemoteHangup() {
|
fun handleRemoteHangup() {
|
||||||
when (currentConnectionState) {
|
when (currentConnectionState) {
|
||||||
CallState.STATE_DIALING,
|
CallState.LocalRing,
|
||||||
CallState.STATE_REMOTE_RINGING -> postViewModelState(CallViewModel.State.RECIPIENT_UNAVAILABLE)
|
CallState.RemoteRing -> postViewModelState(CallViewModel.State.RECIPIENT_UNAVAILABLE)
|
||||||
else -> postViewModelState(CallViewModel.State.CALL_DISCONNECTED)
|
else -> postViewModelState(CallViewModel.State.CALL_DISCONNECTED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -556,7 +569,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
channel.send(buffer)
|
channel.send(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentConnectionState == CallState.STATE_CONNECTED) {
|
if (currentConnectionState == CallState.Connected) {
|
||||||
if (connection.isVideoEnabled()) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO)
|
if (connection.isVideoEnabled()) lockManager.updatePhoneState(LockManager.PhoneState.IN_VIDEO)
|
||||||
else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL)
|
else lockManager.updatePhoneState(LockManager.PhoneState.IN_CALL)
|
||||||
}
|
}
|
||||||
@@ -585,9 +598,9 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun handleWiredHeadsetChanged(present: Boolean) {
|
fun handleWiredHeadsetChanged(present: Boolean) {
|
||||||
if (currentConnectionState in arrayOf(CallState.STATE_CONNECTED,
|
if (currentConnectionState in arrayOf(CallState.Connected,
|
||||||
CallState.STATE_DIALING,
|
CallState.LocalRing,
|
||||||
CallState.STATE_REMOTE_RINGING)) {
|
CallState.RemoteRing)) {
|
||||||
if (present && signalAudioManager.isSpeakerphoneOn()) {
|
if (present && signalAudioManager.isSpeakerphoneOn()) {
|
||||||
signalAudioManager.handleCommand(AudioManagerCommand.SetUserDevice(AudioDevice.WIRED_HEADSET))
|
signalAudioManager.handleCommand(AudioManagerCommand.SetUserDevice(AudioDevice.WIRED_HEADSET))
|
||||||
} else if (!present && !signalAudioManager.isSpeakerphoneOn() && !signalAudioManager.isBluetoothScoOn() && localCameraState.enabled) {
|
} else if (!present && !signalAudioManager.isSpeakerphoneOn() && !signalAudioManager.isBluetoothScoOn() && localCameraState.enabled) {
|
||||||
@@ -597,17 +610,18 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun handleScreenOffChange() {
|
fun handleScreenOffChange() {
|
||||||
if (currentConnectionState in arrayOf(CallState.STATE_ANSWERING, CallState.STATE_LOCAL_RINGING)) {
|
if (currentConnectionState in arrayOf(CallState.Connecting, CallState.LocalRing)) {
|
||||||
signalAudioManager.handleCommand(AudioManagerCommand.SilenceIncomingRinger)
|
signalAudioManager.handleCommand(AudioManagerCommand.SilenceIncomingRinger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleResponseMessage(recipient: Recipient, callId: UUID, answer: SessionDescription) {
|
fun handleResponseMessage(recipient: Recipient, callId: UUID, answer: SessionDescription) {
|
||||||
if (currentConnectionState !in arrayOf(CallState.STATE_DIALING, CallState.STATE_CONNECTED) || recipient != this.recipient || callId != this.callId) {
|
if (recipient != this.recipient || callId != this.callId) {
|
||||||
Log.w(TAG,"Got answer for recipient and call ID we're not currently dialing")
|
Log.w(TAG,"Got answer for recipient and call ID we're not currently dialing")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stateProcessor.processEvent(Event.ReceiveAnswer) {
|
||||||
val connection = peerConnection ?: throw AssertionError("assert")
|
val connection = peerConnection ?: throw AssertionError("assert")
|
||||||
|
|
||||||
connection.setRemoteDescription(answer)
|
connection.setRemoteDescription(answer)
|
||||||
@@ -616,6 +630,7 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va
|
|||||||
}
|
}
|
||||||
queueOutgoingIce(callId, recipient)
|
queueOutgoingIce(callId, recipient)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun handleRemoteIceCandidate(iceCandidates: List<IceCandidate>, callId: UUID) {
|
fun handleRemoteIceCandidate(iceCandidates: List<IceCandidate>, callId: UUID) {
|
||||||
if (callId != this.callId) {
|
if (callId != this.callId) {
|
||||||
|
@@ -21,6 +21,7 @@ class CallViewModel @Inject constructor(private val callManager: CallManager): V
|
|||||||
CALL_RINGING,
|
CALL_RINGING,
|
||||||
CALL_BUSY,
|
CALL_BUSY,
|
||||||
CALL_DISCONNECTED,
|
CALL_DISCONNECTED,
|
||||||
|
CALL_RECONNECTING,
|
||||||
|
|
||||||
NETWORK_FAILURE,
|
NETWORK_FAILURE,
|
||||||
RECIPIENT_UNAVAILABLE,
|
RECIPIENT_UNAVAILABLE,
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.webrtc.data
|
package org.thoughtcrime.securesms.webrtc.data
|
||||||
|
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.thoughtcrime.securesms.webrtc.data.State.Companion.CAN_DECLINE_STATES
|
||||||
import org.thoughtcrime.securesms.webrtc.data.State.Companion.CAN_HANGUP_STATES
|
import org.thoughtcrime.securesms.webrtc.data.State.Companion.CAN_HANGUP_STATES
|
||||||
|
|
||||||
sealed class State {
|
sealed class State {
|
||||||
@@ -13,6 +15,10 @@ sealed class State {
|
|||||||
object Reconnecting : State()
|
object Reconnecting : State()
|
||||||
object Disconnected : State()
|
object Disconnected : State()
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
|
val ALL_STATES = arrayOf(Idle, RemotePreOffer, RemoteRing, LocalPreOffer, LocalRing,
|
||||||
|
Connecting, Connected, Reconnecting, Disconnected)
|
||||||
|
|
||||||
val CAN_DECLINE_STATES = arrayOf(RemotePreOffer, RemoteRing)
|
val CAN_DECLINE_STATES = arrayOf(RemotePreOffer, RemoteRing)
|
||||||
val PENDING_CONNECTION_STATES = arrayOf(
|
val PENDING_CONNECTION_STATES = arrayOf(
|
||||||
LocalPreOffer,
|
LocalPreOffer,
|
||||||
@@ -26,10 +32,17 @@ sealed class State {
|
|||||||
LocalRing,
|
LocalRing,
|
||||||
)
|
)
|
||||||
val CAN_HANGUP_STATES =
|
val CAN_HANGUP_STATES =
|
||||||
arrayOf(LocalPreOffer, LocalRing, Connecting, Connected, Reconnecting)
|
arrayOf(RemotePreOffer, RemoteRing, LocalPreOffer, LocalRing, Connecting, Connected, Reconnecting)
|
||||||
val CAN_RECEIVE_ICE_STATES =
|
val CAN_RECEIVE_ICE_STATES =
|
||||||
arrayOf(RemoteRing, LocalRing, Connecting, Connected, Reconnecting)
|
arrayOf(RemoteRing, LocalRing, Connecting, Connected, Reconnecting)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun withState(vararg expectedState: State, body: ()->Unit) {
|
||||||
|
if (this in expectedState) {
|
||||||
|
body()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Event(vararg val expectedStates: State, val outputState: State) {
|
sealed class Event(vararg val expectedStates: State, val outputState: State) {
|
||||||
@@ -47,7 +60,8 @@ sealed class Event(vararg val expectedStates: State, val outputState: State) {
|
|||||||
object NetworkReconnect : Event(State.Reconnecting, outputState = State.Connecting)
|
object NetworkReconnect : Event(State.Reconnecting, outputState = State.Connecting)
|
||||||
object TimeOut :
|
object TimeOut :
|
||||||
Event(State.Connecting, State.LocalRing, State.RemoteRing, outputState = State.Disconnected)
|
Event(State.Connecting, State.LocalRing, State.RemoteRing, outputState = State.Disconnected)
|
||||||
|
object Error : Event(*State.ALL_STATES, outputState = State.Disconnected)
|
||||||
|
object DeclineCall : Event(*CAN_DECLINE_STATES, outputState = State.Disconnected)
|
||||||
object Hangup : Event(*CAN_HANGUP_STATES, outputState = State.Disconnected)
|
object Hangup : Event(*CAN_HANGUP_STATES, outputState = State.Disconnected)
|
||||||
object Cleanup : Event(State.Disconnected, outputState = State.Idle)
|
object Cleanup : Event(State.Disconnected, outputState = State.Idle)
|
||||||
}
|
}
|
||||||
@@ -62,6 +76,7 @@ open class StateProcessor(initialState: State) {
|
|||||||
sideEffect()
|
sideEffect()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
Log.e("Loki-Call", "error transitioning from $currentState with ${event::class.simpleName}")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,8 +1,13 @@
|
|||||||
package org.thoughtcrime.securesms.calls
|
package org.thoughtcrime.securesms.calls
|
||||||
|
|
||||||
|
import org.junit.After
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.mockito.MockedStatic
|
||||||
|
import org.mockito.Mockito.any
|
||||||
|
import org.mockito.Mockito.mockStatic
|
||||||
|
import org.session.libsignal.utilities.Log
|
||||||
import org.thoughtcrime.securesms.webrtc.data.Event
|
import org.thoughtcrime.securesms.webrtc.data.Event
|
||||||
import org.thoughtcrime.securesms.webrtc.data.State
|
import org.thoughtcrime.securesms.webrtc.data.State
|
||||||
|
|
||||||
@@ -10,9 +15,19 @@ class CallStateMachineTests {
|
|||||||
|
|
||||||
private lateinit var stateProcessor: TestStateProcessor
|
private lateinit var stateProcessor: TestStateProcessor
|
||||||
|
|
||||||
|
lateinit var mock: MockedStatic<Log>
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
stateProcessor = TestStateProcessor(State.Idle)
|
stateProcessor = TestStateProcessor(State.Idle)
|
||||||
|
mock = mockStatic(Log::class.java).apply {
|
||||||
|
`when`<Unit> { Log.e(any(), any(), any()) }.then { /* do nothing */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun teardown() {
|
||||||
|
mock.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -119,6 +134,10 @@ class CallStateMachineTests {
|
|||||||
Event.ReceiveOffer,
|
Event.ReceiveOffer,
|
||||||
Event.SendAnswer,
|
Event.SendAnswer,
|
||||||
Event.IceFailed,
|
Event.IceFailed,
|
||||||
|
Event.Cleanup,
|
||||||
|
Event.ReceivePreOffer,
|
||||||
|
Event.ReceiveOffer,
|
||||||
|
Event.DeclineCall,
|
||||||
Event.Cleanup
|
Event.Cleanup
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -1,20 +1,22 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
import android.content.Context;
|
import static org.junit.Assert.assertEquals;
|
||||||
import android.database.Cursor;
|
import static org.junit.Assert.assertNotEquals;
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
public class CursorRecyclerViewAdapterTest {
|
public class CursorRecyclerViewAdapterTest {
|
||||||
private CursorRecyclerViewAdapter adapter;
|
private CursorRecyclerViewAdapter adapter;
|
||||||
private Context context;
|
private Context context;
|
||||||
|
Reference in New Issue
Block a user