Merge pull request #5203 from vector-im/feature/dla/fix_consult_transfer

Call transfer with consult fails to make outgoing consultation call.
This commit is contained in:
Benoit Marty 2022-02-14 19:54:09 +01:00 committed by GitHub
commit 6c4f389342
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 101 additions and 77 deletions

1
changelog.d/5201.bugfix Normal file
View File

@ -0,0 +1 @@
Fix for call transfer with consult failing to make outgoing consultation call.

View File

@ -54,6 +54,7 @@ import im.vector.app.core.utils.registerForPermissionsResult
import im.vector.app.databinding.ActivityCallBinding import im.vector.app.databinding.ActivityCallBinding
import im.vector.app.features.call.dialpad.CallDialPadBottomSheet import im.vector.app.features.call.dialpad.CallDialPadBottomSheet
import im.vector.app.features.call.dialpad.DialPadFragment import im.vector.app.features.call.dialpad.DialPadFragment
import im.vector.app.features.call.transfer.CallTransferActivity
import im.vector.app.features.call.utils.EglUtils import im.vector.app.features.call.utils.EglUtils
import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
@ -165,6 +166,7 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
?.let { ?.let {
callViewModel.handle(VectorCallViewActions.SwitchCall(it)) callViewModel.handle(VectorCallViewActions.SwitchCall(it))
} }
this.intent = intent
} }
override fun getMenuRes() = R.menu.vector_call override fun getMenuRes() = R.menu.vector_call
@ -522,15 +524,22 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
val callId = withState(callViewModel) { it.callId } val callId = withState(callViewModel) { it.callId }
navigator.openCallTransfer(this, callTransferActivityResultLauncher, callId) navigator.openCallTransfer(this, callTransferActivityResultLauncher, callId)
} }
is VectorCallViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure))
null -> { null -> {
} }
} }
} }
private val callTransferActivityResultLauncher = registerStartForActivityResult { activityResult -> private val callTransferActivityResultLauncher = registerStartForActivityResult { activityResult ->
if (activityResult.resultCode == Activity.RESULT_CANCELED) { when (activityResult.resultCode) {
Activity.RESULT_CANCELED -> {
callViewModel.handle(VectorCallViewActions.CallTransferSelectionCancelled) callViewModel.handle(VectorCallViewActions.CallTransferSelectionCancelled)
} }
Activity.RESULT_OK -> {
CallTransferActivity.getCallTransferResult(activityResult.data)
?.let { callViewModel.handle(VectorCallViewActions.CallTransferSelectionResult(it)) }
}
}
} }
private fun onErrorTimoutConnect(turn: TurnServerResponse?) { private fun onErrorTimoutConnect(turn: TurnServerResponse?) {

View File

@ -18,6 +18,7 @@ package im.vector.app.features.call
import im.vector.app.core.platform.VectorViewModelAction import im.vector.app.core.platform.VectorViewModelAction
import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.transfer.CallTransferResult
sealed class VectorCallViewActions : VectorViewModelAction { sealed class VectorCallViewActions : VectorViewModelAction {
object EndCall : VectorCallViewActions() object EndCall : VectorCallViewActions()
@ -37,5 +38,6 @@ sealed class VectorCallViewActions : VectorViewModelAction {
object ToggleHDSD : VectorCallViewActions() object ToggleHDSD : VectorCallViewActions()
object InitiateCallTransfer : VectorCallViewActions() object InitiateCallTransfer : VectorCallViewActions()
object CallTransferSelectionCancelled : VectorCallViewActions() object CallTransferSelectionCancelled : VectorCallViewActions()
data class CallTransferSelectionResult(val callTransferResult: CallTransferResult) : VectorCallViewActions()
object TransferCall : VectorCallViewActions() object TransferCall : VectorCallViewActions()
} }

View File

@ -29,6 +29,7 @@ sealed class VectorCallViewEvents : VectorViewEvents {
) : VectorCallViewEvents() ) : VectorCallViewEvents()
object ShowDialPad : VectorCallViewEvents() object ShowDialPad : VectorCallViewEvents()
object ShowCallTransferScreen : VectorCallViewEvents() object ShowCallTransferScreen : VectorCallViewEvents()
object FailToTransfer : 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()

View File

@ -29,13 +29,17 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.dialpad.DialPadLookup
import im.vector.app.features.call.transfer.CallTransferResult
import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.call.webrtc.getOpponentAsMatrixItem import im.vector.app.features.call.webrtc.getOpponentAsMatrixItem
import im.vector.app.features.createdirect.DirectRoomHelper
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.MatrixPatterns
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
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.MxCall import org.matrix.android.sdk.api.session.call.MxCall
@ -47,7 +51,9 @@ class VectorCallViewModel @AssistedInject constructor(
@Assisted initialState: VectorCallViewState, @Assisted initialState: VectorCallViewState,
val session: Session, val session: Session,
val callManager: WebRtcCallManager, val callManager: WebRtcCallManager,
val proximityManager: CallProximityManager val proximityManager: CallProximityManager,
private val dialPadLookup: DialPadLookup,
private val directRoomHelper: DirectRoomHelper,
) : VectorViewModel<VectorCallViewState, VectorCallViewActions, VectorCallViewEvents>(initialState) { ) : VectorViewModel<VectorCallViewState, VectorCallViewActions, VectorCallViewEvents>(initialState) {
private var call: WebRtcCall? = null private var call: WebRtcCall? = null
@ -327,6 +333,9 @@ class VectorCallViewModel @AssistedInject constructor(
VectorCallViewActions.CallTransferSelectionCancelled -> { VectorCallViewActions.CallTransferSelectionCancelled -> {
call?.updateRemoteOnHold(false) call?.updateRemoteOnHold(false)
} }
is VectorCallViewActions.CallTransferSelectionResult -> {
handleCallTransferSelectionResult(action.callTransferResult)
}
VectorCallViewActions.TransferCall -> { VectorCallViewActions.TransferCall -> {
handleCallTransfer() handleCallTransfer()
} }
@ -345,6 +354,53 @@ class VectorCallViewModel @AssistedInject constructor(
} }
} }
private fun handleCallTransferSelectionResult(result: CallTransferResult) {
when (result) {
is CallTransferResult.ConnectWithUserId -> connectWithUserId(result)
is CallTransferResult.ConnectWithPhoneNumber -> connectWithPhoneNumber(result)
}.exhaustive
}
private fun connectWithUserId(result: CallTransferResult.ConnectWithUserId) {
viewModelScope.launch {
try {
if (result.consultFirst) {
val dmRoomId = directRoomHelper.ensureDMExists(result.selectedUserId)
callManager.startOutgoingCall(
nativeRoomId = dmRoomId,
otherUserId = result.selectedUserId,
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
transferee = call
)
} else {
call?.transferToUser(result.selectedUserId, null)
}
} catch (failure: Throwable) {
_viewEvents.post(VectorCallViewEvents.FailToTransfer)
}
}
}
private fun connectWithPhoneNumber(action: CallTransferResult.ConnectWithPhoneNumber) {
viewModelScope.launch {
try {
val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber)
if (action.consultFirst) {
callManager.startOutgoingCall(
nativeRoomId = result.roomId,
otherUserId = result.userId,
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
transferee = call
)
} else {
call?.transferToUser(result.userId, result.roomId)
}
} catch (failure: Throwable) {
_viewEvents.post(VectorCallViewEvents.FailToTransfer)
}
}
}
@AssistedFactory @AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<VectorCallViewModel, VectorCallViewState> { interface Factory : MavericksAssistedViewModelFactory<VectorCallViewModel, VectorCallViewState> {
override fun create(initialState: VectorCallViewState): VectorCallViewModel override fun create(initialState: VectorCallViewState): VectorCallViewModel

View File

@ -16,7 +16,6 @@
package im.vector.app.features.call.transfer package im.vector.app.features.call.transfer
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
@ -27,6 +26,7 @@ import com.google.android.material.tabs.TabLayoutMediator
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.exhaustive
import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.databinding.ActivityCallTransferBinding import im.vector.app.databinding.ActivityCallTransferBinding
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -57,9 +57,7 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>() {
callTransferViewModel.observeViewEvents { callTransferViewModel.observeViewEvents {
when (it) { when (it) {
is CallTransferViewEvents.Complete -> handleComplete() is CallTransferViewEvents.Complete -> handleComplete()
CallTransferViewEvents.Loading -> showWaitingView() }.exhaustive
is CallTransferViewEvents.FailToTransfer -> showSnackbar(getString(R.string.call_transfer_failure))
}
} }
sectionsPagerAdapter = CallTransferPagerAdapter(this) sectionsPagerAdapter = CallTransferPagerAdapter(this)
@ -82,29 +80,41 @@ class CallTransferActivity : VectorBaseActivity<ActivityCallTransferBinding>() {
when (views.callTransferTabLayout.selectedTabPosition) { when (views.callTransferTabLayout.selectedTabPosition) {
CallTransferPagerAdapter.USER_LIST_INDEX -> { CallTransferPagerAdapter.USER_LIST_INDEX -> {
val selectedUser = sectionsPagerAdapter.userListFragment?.getCurrentState()?.getSelectedMatrixId()?.firstOrNull() ?: return@debouncedClicks val selectedUser = sectionsPagerAdapter.userListFragment?.getCurrentState()?.getSelectedMatrixId()?.firstOrNull() ?: return@debouncedClicks
val action = CallTransferAction.ConnectWithUserId(views.callTransferConsultCheckBox.isChecked, selectedUser) val result = CallTransferResult.ConnectWithUserId(views.callTransferConsultCheckBox.isChecked, selectedUser)
callTransferViewModel.handle(action) handleComplete(result)
} }
CallTransferPagerAdapter.DIAL_PAD_INDEX -> { CallTransferPagerAdapter.DIAL_PAD_INDEX -> {
val phoneNumber = sectionsPagerAdapter.dialPadFragment?.getRawInput() ?: return@debouncedClicks val phoneNumber = sectionsPagerAdapter.dialPadFragment?.getRawInput() ?: return@debouncedClicks
val action = CallTransferAction.ConnectWithPhoneNumber(views.callTransferConsultCheckBox.isChecked, phoneNumber) val result = CallTransferResult.ConnectWithPhoneNumber(views.callTransferConsultCheckBox.isChecked, phoneNumber)
callTransferViewModel.handle(action) handleComplete(result)
} }
} }
} }
} }
private fun handleComplete() { private fun handleComplete(callTransferResult: CallTransferResult? = null) {
setResult(Activity.RESULT_OK) if (callTransferResult != null) {
val intent = Intent().apply {
putExtra(EXTRA_TRANSFER_RESULT, callTransferResult)
}
setResult(RESULT_OK, intent)
} else {
setResult(RESULT_OK)
}
finish() finish()
} }
companion object { companion object {
private const val EXTRA_TRANSFER_RESULT = "EXTRA_TRANSFER_RESULT"
fun newIntent(context: Context, callId: String): Intent { fun newIntent(context: Context, callId: String): Intent {
return Intent(context, CallTransferActivity::class.java).also { return Intent(context, CallTransferActivity::class.java).also {
it.putExtra(Mavericks.KEY_ARG, CallTransferArgs(callId)) it.putExtra(Mavericks.KEY_ARG, CallTransferArgs(callId))
} }
} }
fun getCallTransferResult(intent: Intent?): CallTransferResult? {
return intent?.extras?.getParcelable(EXTRA_TRANSFER_RESULT)
}
} }
} }

View File

@ -16,9 +16,10 @@
package im.vector.app.features.call.transfer package im.vector.app.features.call.transfer
import im.vector.app.core.platform.VectorViewModelAction import android.os.Parcelable
import kotlinx.parcelize.Parcelize
sealed class CallTransferAction : VectorViewModelAction { sealed class CallTransferResult : Parcelable {
data class ConnectWithUserId(val consultFirst: Boolean, val selectedUserId: String) : CallTransferAction() @Parcelize data class ConnectWithUserId(val consultFirst: Boolean, val selectedUserId: String) : CallTransferResult()
data class ConnectWithPhoneNumber(val consultFirst: Boolean, val phoneNumber: String) : CallTransferAction() @Parcelize data class ConnectWithPhoneNumber(val consultFirst: Boolean, val phoneNumber: String) : CallTransferResult()
} }

View File

@ -20,6 +20,4 @@ import im.vector.app.core.platform.VectorViewEvents
sealed class CallTransferViewEvents : VectorViewEvents { sealed class CallTransferViewEvents : VectorViewEvents {
object Complete : CallTransferViewEvents() object Complete : CallTransferViewEvents()
object Loading : CallTransferViewEvents()
object FailToTransfer : CallTransferViewEvents()
} }

View File

@ -22,22 +22,16 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import im.vector.app.core.di.MavericksAssistedViewModelFactory import im.vector.app.core.di.MavericksAssistedViewModelFactory
import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.di.hiltMavericksViewModelFactory
import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.EmptyAction
import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.platform.VectorViewModel
import im.vector.app.features.call.dialpad.DialPadLookup
import im.vector.app.features.call.webrtc.WebRtcCall import im.vector.app.features.call.webrtc.WebRtcCall
import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.createdirect.DirectRoomHelper
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.extensions.orFalse
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.MxCall import org.matrix.android.sdk.api.session.call.MxCall
class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState, class CallTransferViewModel @AssistedInject constructor(@Assisted initialState: CallTransferViewState,
private val dialPadLookup: DialPadLookup,
private val directRoomHelper: DirectRoomHelper,
private val callManager: WebRtcCallManager) : private val callManager: WebRtcCallManager) :
VectorViewModel<CallTransferViewState, CallTransferAction, CallTransferViewEvents>(initialState) { VectorViewModel<CallTransferViewState, EmptyAction, CallTransferViewEvents>(initialState) {
@AssistedFactory @AssistedFactory
interface Factory : MavericksAssistedViewModelFactory<CallTransferViewModel, CallTransferViewState> { interface Factory : MavericksAssistedViewModelFactory<CallTransferViewModel, CallTransferViewState> {
@ -68,53 +62,5 @@ class CallTransferViewModel @AssistedInject constructor(@Assisted initialState:
call?.removeListener(callListener) call?.removeListener(callListener)
} }
override fun handle(action: CallTransferAction) { override fun handle(action: EmptyAction) { }
when (action) {
is CallTransferAction.ConnectWithUserId -> connectWithUserId(action)
is CallTransferAction.ConnectWithPhoneNumber -> connectWithPhoneNumber(action)
}.exhaustive
}
private fun connectWithUserId(action: CallTransferAction.ConnectWithUserId) {
viewModelScope.launch {
try {
if (action.consultFirst) {
val dmRoomId = directRoomHelper.ensureDMExists(action.selectedUserId)
callManager.startOutgoingCall(
nativeRoomId = dmRoomId,
otherUserId = action.selectedUserId,
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
transferee = call
)
} else {
call?.transferToUser(action.selectedUserId, null)
}
_viewEvents.post(CallTransferViewEvents.Complete)
} catch (failure: Throwable) {
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
}
}
}
private fun connectWithPhoneNumber(action: CallTransferAction.ConnectWithPhoneNumber) {
viewModelScope.launch {
try {
_viewEvents.post(CallTransferViewEvents.Loading)
val result = dialPadLookup.lookupPhoneNumber(action.phoneNumber)
if (action.consultFirst) {
callManager.startOutgoingCall(
nativeRoomId = result.roomId,
otherUserId = result.userId,
isVideoCall = call?.mxCall?.isVideoCall.orFalse(),
transferee = call
)
} else {
call?.transferToUser(result.userId, result.roomId)
}
_viewEvents.post(CallTransferViewEvents.Complete)
} catch (failure: Throwable) {
_viewEvents.post(CallTransferViewEvents.FailToTransfer)
}
}
}
} }