Merge pull request #3854 from vector-im/feature/fga/call_end_reasons_dialog

Call ended: handle busy reason and invite timeout.
This commit is contained in:
Benoit Marty 2021-08-26 11:40:27 +02:00 committed by GitHub
commit 97bdd14880
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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.os.Parcelable
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.core.view.isInvisible 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.CallState
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState 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.call.TurnServerResponse
import org.matrix.android.sdk.api.session.room.model.call.EndCallReason
import org.webrtc.EglBase import org.webrtc.EglBase
import org.webrtc.RendererCommon import org.webrtc.RendererCommon
import timber.log.Timber import timber.log.Timber
@ -133,6 +135,12 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
renderState(it) renderState(it)
} }
callViewModel.asyncSubscribe(this, VectorCallViewState::callState) {
if (it is CallState.Ended) {
handleCallEnded(it)
}
}
callViewModel.viewEvents callViewModel.viewEvents
.observe() .observe()
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
@ -199,7 +207,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
views.callConnectingProgress.isVisible = true views.callConnectingProgress.isVisible = true
configureCallInfo(state) configureCallInfo(state)
} }
is CallState.Connected -> { is CallState.Connected -> {
if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) { if (callState.iceConnectionState == MxPeerConnectionState.CONNECTED) {
if (state.isLocalOnHold || state.isRemoteOnHold) { if (state.isLocalOnHold || state.isRemoteOnHold) {
views.smallIsHeldIcon.isVisible = true views.smallIsHeldIcon.isVisible = true
@ -249,14 +257,44 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
views.callConnectingProgress.isVisible = true views.callConnectingProgress.isVisible = true
} }
} }
is CallState.Ended -> { is CallState.Ended -> {
finish() 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, null)
.setOnDismissListener {
finish()
}
.show()
}
private fun configureCallInfo(state: VectorCallViewState, blurAvatar: Boolean = false) { private fun configureCallInfo(state: VectorCallViewState, blurAvatar: Boolean = false) {
state.callInfo?.opponentUserItem?.let { state.callInfo?.opponentUserItem?.let {
val colorFilter = ContextCompat.getColor(this, R.color.bg_call_screen_blur) 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?) { private fun handleViewEvents(event: VectorCallViewEvents?) {
Timber.tag(loggerTag.value).v("handleViewEvents $event") Timber.tag(loggerTag.value).v("handleViewEvents $event")
when (event) { when (event) {
VectorCallViewEvents.DismissNoCall -> {
finish()
}
is VectorCallViewEvents.ConnectionTimeout -> { is VectorCallViewEvents.ConnectionTimeout -> {
onErrorTimoutConnect(event.turn) onErrorTimoutConnect(event.turn)
} }
@ -364,7 +399,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
// TODO ask to use default stun, etc... // TODO ask to use default stun, etc...
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setTitle(R.string.call_failed_no_connection) .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) { _, _ -> .setNegativeButton(R.string.ok) { _, _ ->
callViewModel.handle(VectorCallViewActions.EndCall) callViewModel.handle(VectorCallViewActions.EndCall)
} }

View File

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

View File

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

View File

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