Show error on connecting timeout + refactoring

This commit is contained in:
Valere 2020-06-17 16:37:17 +02:00
parent c85ba51274
commit fd3f591541
10 changed files with 167 additions and 171 deletions

View File

@ -16,26 +16,29 @@
package im.vector.matrix.android.api.session.call package im.vector.matrix.android.api.session.call
enum class CallState { import org.webrtc.PeerConnection
sealed class CallState {
/** Idle, setting up objects */ /** Idle, setting up objects */
IDLE, object Idle : CallState()
/** Dialing. Outgoing call is signaling the remote peer */ /** Dialing. Outgoing call is signaling the remote peer */
DIALING, object Dialing : CallState()
/** Local ringing. Incoming call offer received */ /** Local ringing. Incoming call offer received */
LOCAL_RINGING, object LocalRinging : CallState()
/** Answering. Incoming call is responding to remote peer */ /** Answering. Incoming call is responding to remote peer */
ANSWERING, object Answering : CallState()
/** Connecting. Incoming/Outgoing Offer and answer are known, Currently checking and testing pairs of ice candidates */ /**
CONNECTING, * Connected. Incoming/Outgoing call, ice layer connecting or connected
* Notice that the PeerState failed is not always final, if you switch network, new ice candidtates
/** Connected. Incoming/Outgoing call, the call is connected */ * could be exchanged, and the connection could go back to connected
CONNECTED, * */
data class Connected(val iceConnectionState: PeerConnection.PeerConnectionState) : CallState()
/** Terminated. Incoming/Outgoing call, the call is terminated */ /** Terminated. Incoming/Outgoing call, the call is terminated */
TERMINATED, object Terminated : CallState()
} }

View File

@ -101,6 +101,7 @@ internal class DefaultCallSignalingService @Inject constructor(
override fun removeCallListener(listener: CallsListener) { override fun removeCallListener(listener: CallsListener) {
callListeners.remove(listener) callListeners.remove(listener)
} }
override fun getCallWithId(callId: String): MxCall? { override fun getCallWithId(callId: String): MxCall? {
Timber.v("## VOIP getCallWithId $callId all calls ${activeCalls.map { it.callId }}") Timber.v("## VOIP getCallWithId $callId all calls ${activeCalls.map { it.callId }}")
return activeCalls.find { it.callId == callId } return activeCalls.find { it.callId == callId }
@ -189,25 +190,4 @@ internal class DefaultCallSignalingService @Inject constructor(
companion object { companion object {
const val CALL_TIMEOUT_MS = 120_000 const val CALL_TIMEOUT_MS = 120_000
} }
// internal class PeerSignalingClientFactory @Inject constructor(
// @UserId private val userId: String,
// private val localEchoEventFactory: LocalEchoEventFactory,
// private val sendEventTask: SendEventTask,
// private val taskExecutor: TaskExecutor,
// private val cryptoService: CryptoService
// ) {
//
// fun create(roomId: String, callId: String): PeerSignalingClient {
// return RoomPeerSignalingClient(
// callID = callId,
// roomId = roomId,
// userId = userId,
// localEchoEventFactory = localEchoEventFactory,
// sendEventTask = sendEventTask,
// taskExecutor = taskExecutor,
// cryptoService = cryptoService
// )
// }
// }
} }

View File

@ -46,7 +46,7 @@ internal class MxCallImpl(
private val roomEventSender: RoomEventSender private val roomEventSender: RoomEventSender
) : MxCall { ) : MxCall {
override var state: CallState = CallState.IDLE override var state: CallState = CallState.Idle
set(value) { set(value) {
field = value field = value
dispatchStateChange() dispatchStateChange()
@ -74,17 +74,17 @@ internal class MxCallImpl(
init { init {
if (isOutgoing) { if (isOutgoing) {
state = CallState.IDLE state = CallState.Idle
} else { } else {
// because it's created on reception of an offer // because it's created on reception of an offer
state = CallState.LOCAL_RINGING state = CallState.LocalRinging
} }
} }
override fun offerSdp(sdp: SessionDescription) { override fun offerSdp(sdp: SessionDescription) {
if (!isOutgoing) return if (!isOutgoing) return
Timber.v("## VOIP offerSdp $callId") Timber.v("## VOIP offerSdp $callId")
state = CallState.DIALING state = CallState.Dialing
CallInviteContent( CallInviteContent(
callId = callId, callId = callId,
lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS, lifetime = DefaultCallSignalingService.CALL_TIMEOUT_MS,
@ -120,13 +120,13 @@ internal class MxCallImpl(
) )
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) } .let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
.also { roomEventSender.sendEvent(it) } .also { roomEventSender.sendEvent(it) }
state = CallState.TERMINATED state = CallState.Terminated
} }
override fun accept(sdp: SessionDescription) { override fun accept(sdp: SessionDescription) {
Timber.v("## VOIP accept $callId") Timber.v("## VOIP accept $callId")
if (isOutgoing) return if (isOutgoing) return
state = CallState.ANSWERING state = CallState.Answering
CallAnswerContent( CallAnswerContent(
callId = callId, callId = callId,
answer = CallAnswerContent.Answer(sdp = sdp.description) answer = CallAnswerContent.Answer(sdp = sdp.description)

View File

@ -29,6 +29,7 @@ import butterknife.OnClick
import im.vector.matrix.android.api.session.call.CallState import im.vector.matrix.android.api.session.call.CallState
import im.vector.riotx.R import im.vector.riotx.R
import kotlinx.android.synthetic.main.fragment_call_controls.view.* import kotlinx.android.synthetic.main.fragment_call_controls.view.*
import org.webrtc.PeerConnection
class CallControlsView @JvmOverloads constructor( class CallControlsView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
@ -99,28 +100,34 @@ class CallControlsView @JvmOverloads constructor(
videoToggleIcon.setImageResource(if (state.isVideoEnabled) R.drawable.ic_video else R.drawable.ic_video_off) videoToggleIcon.setImageResource(if (state.isVideoEnabled) R.drawable.ic_video else R.drawable.ic_video_off)
when (callState) { when (callState) {
CallState.IDLE, is CallState.Idle,
CallState.DIALING, is CallState.Dialing,
CallState.CONNECTING, is CallState.Answering -> {
CallState.ANSWERING -> {
ringingControls.isVisible = true ringingControls.isVisible = true
ringingControlAccept.isVisible = false ringingControlAccept.isVisible = false
ringingControlDecline.isVisible = true ringingControlDecline.isVisible = true
connectedControls.isVisible = false connectedControls.isVisible = false
} }
CallState.LOCAL_RINGING -> { is CallState.LocalRinging -> {
ringingControls.isVisible = true ringingControls.isVisible = true
ringingControlAccept.isVisible = true ringingControlAccept.isVisible = true
ringingControlDecline.isVisible = true ringingControlDecline.isVisible = true
connectedControls.isVisible = false connectedControls.isVisible = false
} }
CallState.CONNECTED -> { is CallState.Connected -> {
ringingControls.isVisible = false if (callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) {
connectedControls.isVisible = true ringingControls.isVisible = false
iv_video_toggle.isVisible = state.isVideoCall connectedControls.isVisible = true
iv_video_toggle.isVisible = state.isVideoCall
} else {
ringingControls.isVisible = true
ringingControlAccept.isVisible = false
ringingControlDecline.isVisible = true
connectedControls.isVisible = false
}
} }
CallState.TERMINATED, is CallState.Terminated,
null -> { null -> {
ringingControls.isVisible = false ringingControls.isVisible = false
connectedControls.isVisible = false connectedControls.isVisible = false
} }

View File

@ -27,6 +27,7 @@ import android.os.Parcelable
import android.view.View import android.view.View
import android.view.Window import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -39,6 +40,7 @@ import com.jakewharton.rxbinding3.view.clicks
import im.vector.matrix.android.api.session.call.CallState import im.vector.matrix.android.api.session.call.CallState
import im.vector.matrix.android.api.session.call.EglUtils import im.vector.matrix.android.api.session.call.EglUtils
import im.vector.matrix.android.api.session.call.MxCallDetail import im.vector.matrix.android.api.session.call.MxCallDetail
import im.vector.matrix.android.api.session.call.TurnServerResponse
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.core.platform.VectorBaseActivity
@ -54,6 +56,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.activity_call.* import kotlinx.android.synthetic.main.activity_call.*
import org.webrtc.EglBase import org.webrtc.EglBase
import org.webrtc.PeerConnection
import org.webrtc.RendererCommon import org.webrtc.RendererCommon
import org.webrtc.SurfaceViewRenderer import org.webrtc.SurfaceViewRenderer
import timber.log.Timber import timber.log.Timber
@ -66,8 +69,7 @@ data class CallArgs(
val callId: String?, val callId: String?,
val participantUserId: String, val participantUserId: String,
val isIncomingCall: Boolean, val isIncomingCall: Boolean,
val isVideoCall: Boolean, val isVideoCall: Boolean
val autoAccept: Boolean
) : Parcelable ) : Parcelable
class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionListener { class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionListener {
@ -216,53 +218,6 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
turnScreenOffAndKeyguardOn() turnScreenOffAndKeyguardOn()
} }
// override fun onResume() {
// super.onResume()
// }
//
// override fun onStop() {
// super.onStop()
// when(callViewModel.call?.state) {
// CallState.DIALING -> {
// CallService.onIncomingCall(
// this,
// callArgs.isVideoCall,
// callArgs.participantUserId,
// callArgs.roomId,
// "",
// callArgs.callId ?: ""
// )
// }
// CallState.LOCAL_RINGING -> {
// CallService.onIncomingCall(
// this,
// callArgs.isVideoCall,
// callArgs.participantUserId,
// callArgs.roomId,
// "",
// callArgs.callId ?: ""
// )
// }
// CallState.ANSWERING,
// CallState.CONNECTING,
// CallState.CONNECTED -> {
// CallService.onPendingCall(
// this,
// callArgs.isVideoCall,
// callArgs.participantUserId,
// callArgs.roomId,
// "",
// callArgs.callId ?: ""
// )
// }
// CallState.TERMINATED ,
// CallState.IDLE ,
// null -> {
//
// }
// }
// }
private fun renderState(state: VectorCallViewState) { private fun renderState(state: VectorCallViewState) {
Timber.v("## VOIP renderState call $state") Timber.v("## VOIP renderState call $state")
if (state.callState is Fail) { if (state.callState is Fail) {
@ -273,52 +228,55 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
} }
callControlsView.updateForState(state) callControlsView.updateForState(state)
when (state.callState.invoke()) { val callState = state.callState.invoke()
CallState.IDLE, when (callState) {
CallState.DIALING -> { is CallState.Idle,
is CallState.Dialing -> {
callVideoGroup.isInvisible = true callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_ring) callStatusText.setText(R.string.call_ring)
configureCallInfo(state) configureCallInfo(state)
} }
CallState.LOCAL_RINGING -> { is CallState.LocalRinging -> {
callVideoGroup.isInvisible = true callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true callInfoGroup.isVisible = true
callStatusText.text = null callStatusText.text = null
configureCallInfo(state) configureCallInfo(state)
} }
CallState.ANSWERING -> { is CallState.Answering -> {
callVideoGroup.isInvisible = true callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true callInfoGroup.isVisible = true
callStatusText.setText(R.string.call_connecting) callStatusText.setText(R.string.call_connecting)
configureCallInfo(state) configureCallInfo(state)
} }
CallState.CONNECTING -> { is CallState.Connected -> {
callVideoGroup.isInvisible = true if (callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) {
callInfoGroup.isVisible = true if (callArgs.isVideoCall) {
configureCallInfo(state) callVideoGroup.isVisible = true
callStatusText.setText(R.string.call_connecting) callInfoGroup.isVisible = false
} pip_video_view.isVisible = !state.isVideoCaptureInError
CallState.CONNECTED -> { } else {
if (callArgs.isVideoCall) { callVideoGroup.isInvisible = true
callVideoGroup.isVisible = true callInfoGroup.isVisible = true
callInfoGroup.isVisible = false configureCallInfo(state)
pip_video_view.isVisible = !state.isVideoCaptureInError callStatusText.text = null
}
} else { } else {
// This state is not final, if you change network, new candidates will be sent
callVideoGroup.isInvisible = true callVideoGroup.isInvisible = true
callInfoGroup.isVisible = true callInfoGroup.isVisible = true
configureCallInfo(state) configureCallInfo(state)
callStatusText.text = null callStatusText.setText(R.string.call_connecting)
} }
// ensure all attached? // ensure all attached?
peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer, null) peerConnectionManager.attachViewRenderers(pipRenderer, fullscreenRenderer, null)
} }
CallState.TERMINATED -> { is CallState.Terminated -> {
finish() finish()
} }
null -> { null -> {
} }
} }
} }
@ -382,20 +340,29 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
private fun handleViewEvents(event: VectorCallViewEvents?) { private fun handleViewEvents(event: VectorCallViewEvents?) {
Timber.v("## VOIP handleViewEvents $event") Timber.v("## VOIP handleViewEvents $event")
when (event) { when (event) {
VectorCallViewEvents.DismissNoCall -> { VectorCallViewEvents.DismissNoCall -> {
CallService.onNoActiveCall(this) CallService.onNoActiveCall(this)
finish() finish()
} }
null -> { is VectorCallViewEvents.ConnectionTimout -> {
onErrorTimoutConnect(event.turn)
}
null -> {
} }
} }
// when (event) { }
// is VectorCallViewEvents.CallAnswered -> {
// } private fun onErrorTimoutConnect(turn: TurnServerResponse?) {
// is VectorCallViewEvents.CallHangup -> { Timber.d("## VOIP onErrorTimoutConnect $turn")
// finish() // TODO ask to use default stun, etc...
// } AlertDialog
// } .Builder(this)
.setTitle(R.string.call_failed_no_connection)
.setMessage(getString(R.string.call_failed_no_connection_description))
.setNegativeButton(R.string.ok) { _, _ ->
callViewModel.handle(VectorCallViewActions.EndCall)
}
.show()
} }
companion object { companion object {
@ -411,7 +378,7 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
return Intent(context, VectorCallActivity::class.java).apply { return Intent(context, VectorCallActivity::class.java).apply {
// what could be the best flags? // what could be the best flags?
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.otherUserId, !mxCall.isOutgoing, mxCall.isVideoCall, false)) putExtra(MvRx.KEY_ARG, CallArgs(mxCall.roomId, mxCall.callId, mxCall.otherUserId, !mxCall.isOutgoing, mxCall.isVideoCall))
putExtra(EXTRA_MODE, OUTGOING_CREATED) putExtra(EXTRA_MODE, OUTGOING_CREATED)
} }
} }
@ -422,12 +389,11 @@ class VectorCallActivity : VectorBaseActivity(), CallControlsView.InteractionLis
otherUserId: String, otherUserId: String,
isIncomingCall: Boolean, isIncomingCall: Boolean,
isVideoCall: Boolean, isVideoCall: Boolean,
accept: Boolean,
mode: String?): Intent { mode: String?): Intent {
return Intent(context, VectorCallActivity::class.java).apply { return Intent(context, VectorCallActivity::class.java).apply {
// what could be the best flags? // what could be the best flags?
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
putExtra(MvRx.KEY_ARG, CallArgs(roomId, callId, otherUserId, isIncomingCall, isVideoCall, accept)) putExtra(MvRx.KEY_ARG, CallArgs(roomId, callId, otherUserId, isIncomingCall, isVideoCall))
putExtra(EXTRA_MODE, mode) putExtra(EXTRA_MODE, mode)
} }
} }

View File

@ -26,15 +26,20 @@ import com.airbnb.mvrx.Uninitialized
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.call.CallState import im.vector.matrix.android.api.session.call.CallState
import im.vector.matrix.android.api.session.call.MxCall import im.vector.matrix.android.api.session.call.MxCall
import im.vector.matrix.android.api.session.call.TurnServerResponse
import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.MatrixItem
import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.extensions.exhaustive import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.platform.VectorViewEvents import im.vector.riotx.core.platform.VectorViewEvents
import im.vector.riotx.core.platform.VectorViewModel import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.core.platform.VectorViewModelAction import im.vector.riotx.core.platform.VectorViewModelAction
import org.webrtc.PeerConnection
import java.util.Timer
import java.util.TimerTask
data class VectorCallViewState( data class VectorCallViewState(
val callId: String? = null, val callId: String? = null,
@ -60,6 +65,7 @@ sealed class VectorCallViewActions : VectorViewModelAction {
sealed class VectorCallViewEvents : VectorViewEvents { sealed class VectorCallViewEvents : VectorViewEvents {
object DismissNoCall : VectorCallViewEvents() object DismissNoCall : VectorCallViewEvents()
data class ConnectionTimout(val turn: TurnServerResponse?) : VectorCallViewEvents()
// data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents() // data class CallAnswered(val content: CallAnswerContent) : VectorCallViewEvents()
// data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents() // data class CallHangup(val content: CallHangupContent) : VectorCallViewEvents()
// object CallAccepted : VectorCallViewEvents() // object CallAccepted : VectorCallViewEvents()
@ -72,15 +78,38 @@ class VectorCallViewModel @AssistedInject constructor(
val webRtcPeerConnectionManager: WebRtcPeerConnectionManager val webRtcPeerConnectionManager: WebRtcPeerConnectionManager
) : VectorViewModel<VectorCallViewState, VectorCallViewActions, VectorCallViewEvents>(initialState) { ) : VectorViewModel<VectorCallViewState, VectorCallViewActions, VectorCallViewEvents>(initialState) {
var autoReplyIfNeeded: Boolean = false
var call: MxCall? = null var call: MxCall? = null
var connectionTimoutTimer: Timer? = null
private val callStateListener = object : MxCall.StateListener { private val callStateListener = object : MxCall.StateListener {
override fun onStateUpdate(call: MxCall) { override fun onStateUpdate(call: MxCall) {
val callState = call.state
if (callState is CallState.Connected && callState.iceConnectionState == PeerConnection.PeerConnectionState.CONNECTED) {
connectionTimoutTimer?.cancel()
connectionTimoutTimer = null
} else {
// do we reset as long as it's moving?
connectionTimoutTimer?.cancel()
connectionTimoutTimer = Timer().apply {
schedule(object : TimerTask() {
override fun run() {
session.callSignalingService().getTurnServer(object : MatrixCallback<TurnServerResponse> {
override fun onFailure(failure: Throwable) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimout(null))
}
override fun onSuccess(data: TurnServerResponse) {
_viewEvents.post(VectorCallViewEvents.ConnectionTimout(data))
}
})
}
}, 30_000)
}
}
setState { setState {
copy( copy(
callState = Success(call.state) callState = Success(callState)
) )
} }
} }
@ -99,8 +128,6 @@ class VectorCallViewModel @AssistedInject constructor(
init { init {
autoReplyIfNeeded = args.autoAccept
initialState.callId?.let { initialState.callId?.let {
webRtcPeerConnectionManager.addCurrentCallListener(currentCallListener) webRtcPeerConnectionManager.addCurrentCallListener(currentCallListener)

View File

@ -269,7 +269,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
// The call is going to resume from background, we can reduce notif // The call is going to resume from background, we can reduce notif
currentCall?.mxCall currentCall?.mxCall
?.takeIf { it.state == CallState.CONNECTING || it.state == CallState.CONNECTED } ?.takeIf { it.state is CallState.Connected }
?.let { mxCall -> ?.let { mxCall ->
val name = sessionHolder.getSafeActiveSession()?.getUser(mxCall.otherUserId)?.getBestName() val name = sessionHolder.getSafeActiveSession()?.getUser(mxCall.otherUserId)?.getBestName()
?: mxCall.roomId ?: mxCall.roomId
@ -466,7 +466,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
fun acceptIncomingCall() { fun acceptIncomingCall() {
Timber.v("## VOIP acceptIncomingCall from state ${currentCall?.mxCall?.state}") Timber.v("## VOIP acceptIncomingCall from state ${currentCall?.mxCall?.state}")
val mxCall = currentCall?.mxCall val mxCall = currentCall?.mxCall
if (mxCall?.state == CallState.LOCAL_RINGING) { if (mxCall?.state == CallState.LocalRinging) {
getTurnServer { turnServer -> getTurnServer { turnServer ->
internalAcceptIncomingCall(currentCall!!, turnServer) internalAcceptIncomingCall(currentCall!!, turnServer)
} }
@ -476,7 +476,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
fun detachRenderers() { fun detachRenderers() {
// The call is going to continue in background, so ensure notification is visible // The call is going to continue in background, so ensure notification is visible
currentCall?.mxCall currentCall?.mxCall
?.takeIf { it.state == CallState.CONNECTING || it.state == CallState.CONNECTED } ?.takeIf { it.state is CallState.Connected }
?.let { mxCall -> ?.let { mxCall ->
// Start background service with notification // Start background service with notification
@ -687,7 +687,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
if (call.mxCall.callId != callHangupContent.callId) return Unit.also { if (call.mxCall.callId != callHangupContent.callId) return Unit.also {
Timber.w("onCallHangupReceived for non active call? ${callHangupContent.callId}") Timber.w("onCallHangupReceived for non active call? ${callHangupContent.callId}")
} }
call.mxCall.state = CallState.TERMINATED call.mxCall.state = CallState.Terminated
currentCall = null currentCall = null
close() close()
} }
@ -702,7 +702,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
* or is closed (state "closed"); in addition, at least one transport is either "connected" or "completed" * or is closed (state "closed"); in addition, at least one transport is either "connected" or "completed"
*/ */
PeerConnection.PeerConnectionState.CONNECTED -> { PeerConnection.PeerConnectionState.CONNECTED -> {
callContext.mxCall.state = CallState.CONNECTED callContext.mxCall.state = CallState.Connected(newState)
} }
/** /**
* One or more of the ICE transports on the connection is in the "failed" state. * One or more of the ICE transports on the connection is in the "failed" state.
@ -710,6 +710,7 @@ class WebRtcPeerConnectionManager @Inject constructor(
PeerConnection.PeerConnectionState.FAILED -> { PeerConnection.PeerConnectionState.FAILED -> {
// This can be temporary, e.g when other ice not yet received... // This can be temporary, e.g when other ice not yet received...
// callContext.mxCall.state = CallState.ERROR // callContext.mxCall.state = CallState.ERROR
callContext.mxCall.state = CallState.Connected(newState)
} }
/** /**
* At least one of the connection's ICE transports (RTCIceTransports or RTCDtlsTransports) are in the "new" state, * At least one of the connection's ICE transports (RTCIceTransports or RTCDtlsTransports) are in the "new" state,
@ -723,20 +724,20 @@ class WebRtcPeerConnectionManager @Inject constructor(
* that is, their RTCIceConnectionState is either "checking" or "connected", and no transports are in the "failed" state * that is, their RTCIceConnectionState is either "checking" or "connected", and no transports are in the "failed" state
*/ */
PeerConnection.PeerConnectionState.CONNECTING -> { PeerConnection.PeerConnectionState.CONNECTING -> {
callContext.mxCall.state = CallState.CONNECTING callContext.mxCall.state = CallState.Connected(PeerConnection.PeerConnectionState.CONNECTING)
} }
/** /**
* The RTCPeerConnection is closed. * The RTCPeerConnection is closed.
* This value was in the RTCSignalingState enum (and therefore found by reading the value of the signalingState) * This value was in the RTCSignalingState enum (and therefore found by reading the value of the signalingState)
* property until the May 13, 2016 draft of the specification. * property until the May 13, 2016 draft of the specification.
*/ */
PeerConnection.PeerConnectionState.CLOSED -> { PeerConnection.PeerConnectionState.CLOSED,
} /**
/** * At least one of the ICE transports for the connection is in the "disconnected" state and none of
* At least one of the ICE transports for the connection is in the "disconnected" state and none of * the other transports are in the state "failed", "connecting", or "checking".
* the other transports are in the state "failed", "connecting", or "checking". */
*/
PeerConnection.PeerConnectionState.DISCONNECTED -> { PeerConnection.PeerConnectionState.DISCONNECTED -> {
callContext.mxCall.state = CallState.Connected(newState)
} }
null -> { null -> {
} }

View File

@ -296,7 +296,7 @@ class RoomDetailFragment @Inject constructor(
sharedCallActionViewModel sharedCallActionViewModel
.activeCall .activeCall
.observe(viewLifecycleOwner, Observer { .observe(viewLifecycleOwner, Observer {
val hasActiveCall = it?.state == CallState.CONNECTED val hasActiveCall = it?.state is CallState.Connected
activeCallView.isVisible = hasActiveCall activeCallView.isVisible = hasActiveCall
}) })
@ -1517,14 +1517,13 @@ class RoomDetailFragment @Inject constructor(
override fun onTapToReturnToCall() { override fun onTapToReturnToCall() {
sharedCallActionViewModel.activeCall.value?.let { call -> sharedCallActionViewModel.activeCall.value?.let { call ->
VectorCallActivity.newIntent( VectorCallActivity.newIntent(
requireContext(), context = requireContext(),
call.callId, callId = call.callId,
call.roomId, roomId = call.roomId,
call.otherUserId, otherUserId = call.otherUserId,
!call.isOutgoing, isIncomingCall = !call.isOutgoing,
call.isVideoCall, isVideoCall = call.isVideoCall,
false, mode = null
null
).let { ).let {
startActivity(it) startActivity(it)
} }

View File

@ -293,30 +293,34 @@ class NotificationUtils @Inject constructor(private val context: Context,
// val pendingIntent = stackBuilder.getPendingIntent(requestId, PendingIntent.FLAG_UPDATE_CURRENT) // val pendingIntent = stackBuilder.getPendingIntent(requestId, PendingIntent.FLAG_UPDATE_CURRENT)
val contentIntent = VectorCallActivity.newIntent( val contentIntent = VectorCallActivity.newIntent(
context, callId, roomId, otherUserId, true, isVideo, context = context,
false, VectorCallActivity.INCOMING_RINGING callId = callId,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mode = VectorCallActivity.INCOMING_RINGING
).apply { ).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
data = Uri.parse("foobar://$callId") data = Uri.parse("foobar://$callId")
} }
val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0) val contentPendingIntent = PendingIntent.getActivity(context, System.currentTimeMillis().toInt(), contentIntent, 0)
// val contentPendingIntent = TaskStackBuilder.create(context)
// .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
// .addNextIntent(RoomDetailActivity.newIntent(context, RoomDetailArgs(roomId = roomId))
// .addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, otherUserId, true, isVideo, false, VectorCallActivity.INCOMING_RINGING))
// .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
val answerCallPendingIntent = TaskStackBuilder.create(context) val answerCallPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java)) .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, otherUserId, true, isVideo, true, VectorCallActivity.INCOMING_ACCEPT)) .addNextIntent(VectorCallActivity.newIntent(
context = context,
callId = callId,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mode = VectorCallActivity.INCOMING_ACCEPT)
)
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
// val answerCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
// putExtra(CallHeadsUpService.EXTRA_CALL_ACTION_KEY, CallHeadsUpService.CALL_ACTION_ANSWER)
// }
val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply { val rejectCallActionReceiver = Intent(context, CallHeadsUpActionReceiver::class.java).apply {
// putExtra(CallHeadsUpService.EXTRA_CALL_ACTION_KEY, CallHeadsUpService.CALL_ACTION_REJECT) putExtra(CallHeadsUpActionReceiver.EXTRA_CALL_ACTION_KEY, CallHeadsUpActionReceiver.CALL_ACTION_REJECT)
} }
// val answerCallPendingIntent = PendingIntent.getBroadcast(context, requestId, answerCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT) // val answerCallPendingIntent = PendingIntent.getBroadcast(context, requestId, answerCallActionReceiver, PendingIntent.FLAG_UPDATE_CURRENT)
val rejectCallPendingIntent = PendingIntent.getBroadcast( val rejectCallPendingIntent = PendingIntent.getBroadcast(
@ -367,8 +371,13 @@ class NotificationUtils @Inject constructor(private val context: Context,
val requestId = Random.nextInt(1000) val requestId = Random.nextInt(1000)
val contentIntent = VectorCallActivity.newIntent( val contentIntent = VectorCallActivity.newIntent(
context, callId, roomId, otherUserId, true, isVideo, context = context,
false, null).apply { callId = callId,
roomId = roomId,
otherUserId = otherUserId,
isIncomingCall = true,
isVideoCall = isVideo,
mode = null).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
data = Uri.parse("foobar://$callId") data = Uri.parse("foobar://$callId")
} }
@ -451,7 +460,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
val contentPendingIntent = TaskStackBuilder.create(context) val contentPendingIntent = TaskStackBuilder.create(context)
.addNextIntentWithParentStack(Intent(context, HomeActivity::class.java)) .addNextIntentWithParentStack(Intent(context, HomeActivity::class.java))
// TODO other userId // TODO other userId
.addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, false, null)) .addNextIntent(VectorCallActivity.newIntent(context, callId, roomId, "otherUserId", true, isVideo, null))
.getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT) .getPendingIntent(System.currentTimeMillis().toInt(), PendingIntent.FLAG_UPDATE_CURRENT)
builder.setContentIntent(contentPendingIntent) builder.setContentIntent(contentPendingIntent)

View File

@ -210,6 +210,10 @@
<string name="call_failed_no_ice_description">Please ask the administrator of your homeserver (%1$s) to configure a TURN server in order for calls to work reliably.\n\nAlternatively, you can try to use the public server at %2$s, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings."</string> <string name="call_failed_no_ice_description">Please ask the administrator of your homeserver (%1$s) to configure a TURN server in order for calls to work reliably.\n\nAlternatively, you can try to use the public server at %2$s, but this will not be as reliable, and it will share your IP address with that server. You can also manage this in Settings."</string>
<string name="call_failed_no_ice_use_alt">Try using %s</string> <string name="call_failed_no_ice_use_alt">Try using %s</string>
<string name="call_failed_dont_ask_again">Do not ask me again</string> <string name="call_failed_dont_ask_again">Do not ask me again</string>
<string name="call_failed_no_connection">RiotX Call Failed</string>
<string name="call_failed_no_connection_description">Failed to establish real time connection.\nPlease ask the administrator of your homeserver to configure a TURN server in order for calls to work reliably.</string>
<string name="call_select_sound_device">Select Sound Device</string> <string name="call_select_sound_device">Select Sound Device</string>
<string name="sound_device_phone">Phone</string> <string name="sound_device_phone">Phone</string>
<string name="sound_device_speaker">Speaker</string> <string name="sound_device_speaker">Speaker</string>