Call ended: handle busy reason and invite timeout.

This commit is contained in:
ganfra 2021-08-17 10:46:04 +02:00
parent 37e722e85d
commit 99a0d17dfc
6 changed files with 62 additions and 13 deletions

1
changelog.d/3853.feature Normal file
View File

@ -0,0 +1 @@
Call: show dialog for some ended reasons.

View File

@ -26,6 +26,7 @@ import android.os.Bundle
import android.os.Parcelable
import android.view.View
import android.view.WindowManager
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.view.isInvisible
@ -58,6 +59,7 @@ import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.matrix.android.sdk.api.session.call.TurnServerResponse
import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.webrtc.EglBase
import org.webrtc.RendererCommon
import timber.log.Timber
@ -133,6 +135,12 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
renderState(it)
}
callViewModel.asyncSubscribe(this, VectorCallViewState::callState) {
if (it is CallState.Ended) {
handleCallEnded(it)
}
}
callViewModel.viewEvents
.observe()
.observeOn(AndroidSchedulers.mainThread())
@ -199,7 +207,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
views.callConnectingProgress.isVisible = true
configureCallInfo(state)
}
is CallState.Connected -> {
is CallState.Connected -> {
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
if (state.isLocalOnHold || state.isRemoteOnHold) {
views.smallIsHeldIcon.isVisible = true
@ -249,14 +257,44 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
views.callConnectingProgress.isVisible = true
}
}
is CallState.Ended -> {
finish()
is CallState.Ended -> {
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isVisible = true
views.callStatusText.setText(R.string.call_ended)
configureCallInfo(state)
}
null -> {
else -> {
views.callVideoGroup.isInvisible = true
views.callInfoGroup.isInvisible = true
}
}
}
private fun handleCallEnded(callState: CallState.Ended) {
when (callState.reason) {
EndCallReason.USER_BUSY -> {
showEndCallDialog(R.string.call_ended_user_busy_title, R.string.call_ended_user_busy_description)
}
EndCallReason.INVITE_TIMEOUT -> {
showEndCallDialog(R.string.call_ended_invite_timeout_title, R.string.call_error_user_not_responding)
}
else -> {
finish()
}
}
}
private fun showEndCallDialog(@StringRes title: Int, @StringRes description: Int) {
MaterialAlertDialogBuilder(this)
.setTitle(title)
.setMessage(description)
.setNegativeButton(R.string.ok) { _, _ -> }
.setOnDismissListener {
finish()
}
.show()
}
private fun configureCallInfo(state: VectorCallViewState, blurAvatar: Boolean = false) {
state.callInfo?.opponentUserItem?.let {
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen_blur)
@ -340,9 +378,6 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
private fun handleViewEvents(event: VectorCallViewEvents?) {
Timber.tag(loggerTag.value).v("handleViewEvents $event")
when (event) {
VectorCallViewEvents.DismissNoCall -> {
finish()
}
is VectorCallViewEvents.ConnectionTimeout -> {
onErrorTimoutConnect(event.turn)
}
@ -364,7 +399,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
// TODO ask to use default stun, etc...
MaterialAlertDialogBuilder(this)
.setTitle(R.string.call_failed_no_connection)
.setMessage(getString(R.string.call_failed_no_connection_description))
.setMessage(R.string.call_failed_no_connection_description)
.setNegativeButton(R.string.ok) { _, _ ->
callViewModel.handle(VectorCallViewActions.EndCall)
}

View File

@ -22,7 +22,6 @@ import org.matrix.android.sdk.api.session.call.TurnServerResponse
sealed class VectorCallViewEvents : VectorViewEvents {
object DismissNoCall : VectorCallViewEvents()
data class ConnectionTimeout(val turn: TurnServerResponse?) : VectorCallViewEvents()
data class ShowSoundDeviceChooser(
val available: Set<CallAudioManager.Device>,

View File

@ -137,9 +137,7 @@ class VectorCallViewModel @AssistedInject constructor(
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
override fun onCurrentCallChange(call: WebRtcCall?) {
if (call == null) {
_viewEvents.post(VectorCallViewEvents.DismissNoCall)
} else {
if (call != null) {
updateOtherKnownCall(call)
}
}

View File

@ -39,7 +39,9 @@ import io.reactivex.disposables.Disposable
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.ReplaySubject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -91,6 +93,7 @@ private const val STREAM_ID = "userMedia"
private const val AUDIO_TRACK_ID = "${STREAM_ID}a0"
private const val VIDEO_TRACK_ID = "${STREAM_ID}v0"
private val DEFAULT_AUDIO_CONSTRAINTS = MediaConstraints()
private const val INVITE_TIMEOUT_IN_MS = 60_000L
private val loggerTag = LoggerTag("WebRtcCall", LoggerTag.VOIP)
@ -165,6 +168,8 @@ class WebRtcCall(
}
}
private var inviteTimeout: Deferred<Unit>? = null
// Mute status
var micMuted = false
private set
@ -239,6 +244,10 @@ class WebRtcCall(
if (mxCall.state == CallState.CreateOffer) {
// send offer to peer
mxCall.offerSdp(sessionDescription.description)
inviteTimeout = async {
delay(INVITE_TIMEOUT_IN_MS)
endCall(EndCallReason.INVITE_TIMEOUT)
}
} else {
mxCall.negotiate(sessionDescription.description, SdpType.OFFER)
}
@ -807,7 +816,7 @@ class WebRtcCall(
return@launch
}
val reject = mxCall.state is CallState.LocalRinging
terminate(EndCallReason.USER_HANGUP, reject)
terminate(reason, reject)
if (reject) {
mxCall.reject()
} else {
@ -824,6 +833,8 @@ class WebRtcCall(
val cameraManager = context.getSystemService<CameraManager>()!!
cameraManager.unregisterAvailabilityCallback(cameraAvailabilityCallback)
}
inviteTimeout?.cancel()
inviteTimeout = null
mxCall.state = CallState.Ended(reason ?: EndCallReason.USER_HANGUP)
release()
onCallEnded(callId, reason ?: EndCallReason.USER_HANGUP, rejected)
@ -845,6 +856,8 @@ class WebRtcCall(
}
fun onCallAnswerReceived(callAnswerContent: CallAnswerContent) {
inviteTimeout?.cancel()
inviteTimeout = null
sessionScope?.launch(dispatcher) {
Timber.tag(loggerTag.value).v("onCallAnswerReceived ${callAnswerContent.callId}")
val sdp = SessionDescription(SessionDescription.Type.ANSWER, callAnswerContent.answer.sdp)

View File

@ -749,6 +749,9 @@
<string name="call_held_by_user">%s held the call</string>
<string name="call_held_by_you">You held the call</string>
<string name="call_ended_user_busy_title">User busy</string>
<string name="call_ended_user_busy_description">The user you called is busy."</string>
<string name="call_ended_invite_timeout_title">No answer</string>
<string name="call_error_user_not_responding">The remote side failed to pick up.</string>
<string name="call_error_ice_failed">Media Connection Failed</string>
<string name="call_error_camera_init_failed">Cannot initialize the camera</string>