mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-04 04:57:39 +01:00
VoIP: start introducing switch call
This commit is contained in:
parent
14288b545b
commit
629488bbe6
@ -22,7 +22,7 @@ import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import im.vector.app.core.platform.ConfigurationViewModel
|
||||
import im.vector.app.features.call.SharedActiveCallViewModel
|
||||
import im.vector.app.features.call.SharedCurrentCallViewModel
|
||||
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreFromKeyViewModel
|
||||
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreFromPassphraseViewModel
|
||||
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreSharedViewModel
|
||||
@ -85,8 +85,8 @@ interface ViewModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(SharedActiveCallViewModel::class)
|
||||
fun bindSharedActiveCallViewModel(viewModel: SharedActiveCallViewModel): ViewModel
|
||||
@ViewModelKey(SharedCurrentCallViewModel::class)
|
||||
fun bindSharedActiveCallViewModel(viewModel: SharedCurrentCallViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
|
@ -22,6 +22,7 @@ import android.content.Intent
|
||||
import android.os.Binder
|
||||
import android.support.v4.media.session.MediaSessionCompat
|
||||
import android.view.KeyEvent
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.media.session.MediaButtonReceiver
|
||||
import com.airbnb.mvrx.MvRx
|
||||
@ -46,7 +47,9 @@ import timber.log.Timber
|
||||
class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListener, BluetoothHeadsetReceiver.EventListener {
|
||||
|
||||
private val connections = mutableMapOf<String, CallConnection>()
|
||||
private val knownCalls = mutableSetOf<String>()
|
||||
|
||||
private lateinit var notificationManager: NotificationManagerCompat
|
||||
private lateinit var notificationUtils: NotificationUtils
|
||||
private lateinit var callManager: WebRtcCallManager
|
||||
private lateinit var avatarRenderer: AvatarRenderer
|
||||
@ -74,6 +77,7 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
notificationManager = NotificationManagerCompat.from(this)
|
||||
notificationUtils = vectorComponent().notificationUtils()
|
||||
callManager = vectorComponent().webRtcCallManager()
|
||||
avatarRenderer = vectorComponent().avatarRenderer()
|
||||
@ -130,7 +134,6 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
callRingPlayerOutgoing?.stop()
|
||||
displayCallInProgressNotification(intent)
|
||||
}
|
||||
ACTION_NO_ACTIVE_CALL -> hideCallNotifications()
|
||||
ACTION_CALL_CONNECTING -> {
|
||||
// lower notification priority
|
||||
displayCallInProgressNotification(intent)
|
||||
@ -138,6 +141,9 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
callRingPlayerIncoming?.stop()
|
||||
callRingPlayerOutgoing?.stop()
|
||||
}
|
||||
ACTION_CALL_TERMINATED -> {
|
||||
handleCallTerminated(intent)
|
||||
}
|
||||
ACTION_ONGOING_CALL_BG -> {
|
||||
// there is an ongoing call but call activity is in background
|
||||
displayCallOnGoingInBackground(intent)
|
||||
@ -166,11 +172,15 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
Timber.v("## VOIP displayIncomingCallNotification $intent")
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
|
||||
val call = callManager.getCallById(callId) ?: return
|
||||
if (knownCalls.contains(callId)) {
|
||||
Timber.v("Call already notified $callId$")
|
||||
return
|
||||
}
|
||||
val isVideoCall = call.mxCall.isVideoCall
|
||||
val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false)
|
||||
val opponentMatrixItem = getOpponentMatrixItem(call)
|
||||
Timber.v("displayIncomingCallNotification : display the dedicated notification")
|
||||
val incomingCallAlert = IncomingCallAlert(INCOMING_CALL_ALERT_UID,
|
||||
val incomingCallAlert = IncomingCallAlert(callId,
|
||||
shouldBeDisplayedIn = { activity ->
|
||||
if (activity is RoomDetailActivity) {
|
||||
call.roomId != activity.currentRoomId
|
||||
@ -195,7 +205,27 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
|
||||
fromBg = fromBg
|
||||
)
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
if (knownCalls.isEmpty()) {
|
||||
startForeground(callId.hashCode(), notification)
|
||||
} else {
|
||||
notificationManager.notify(callId.hashCode(), notification)
|
||||
}
|
||||
knownCalls.add(callId)
|
||||
}
|
||||
|
||||
private fun handleCallTerminated(intent: Intent) {
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
|
||||
if (!knownCalls.remove(callId)) {
|
||||
Timber.v("Call terminated for unknown call $callId$")
|
||||
return
|
||||
}
|
||||
val notification = notificationUtils.buildCallEndedNotification()
|
||||
notificationManager.notify(callId.hashCode(), notification)
|
||||
alertManager.cancelAlert(callId)
|
||||
if (knownCalls.isEmpty()) {
|
||||
mediaSession?.isActive = false
|
||||
myStopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showCallScreen(call: WebRtcCall, mode: String) {
|
||||
@ -210,13 +240,22 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
private fun displayOutgoingRingingCallNotification(intent: Intent) {
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
|
||||
val call = callManager.getCallById(callId) ?: return
|
||||
if (knownCalls.contains(callId)) {
|
||||
Timber.v("Call already notified $callId$")
|
||||
return
|
||||
}
|
||||
val opponentMatrixItem = getOpponentMatrixItem(call)
|
||||
Timber.v("displayOutgoingCallNotification : display the dedicated notification")
|
||||
val notification = notificationUtils.buildOutgoingRingingCallNotification(
|
||||
mxCall = call.mxCall,
|
||||
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
|
||||
)
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
if (knownCalls.isEmpty()) {
|
||||
startForeground(callId.hashCode(), notification)
|
||||
} else {
|
||||
notificationManager.notify(callId.hashCode(), notification)
|
||||
}
|
||||
knownCalls.add(callId)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -226,14 +265,17 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
Timber.v("## VOIP displayCallInProgressNotification")
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
|
||||
val call = callManager.getCallById(callId) ?: return
|
||||
if (!knownCalls.contains(callId)) {
|
||||
Timber.v("Call in progress for unknown call $callId$")
|
||||
return
|
||||
}
|
||||
val opponentMatrixItem = getOpponentMatrixItem(call)
|
||||
alertManager.cancelAlert(INCOMING_CALL_ALERT_UID)
|
||||
alertManager.cancelAlert(callId)
|
||||
val notification = notificationUtils.buildPendingCallNotification(
|
||||
mxCall = call.mxCall,
|
||||
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
|
||||
)
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
// mCallIdInProgress = callId
|
||||
notificationManager.notify(callId.hashCode(), notification)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,27 +285,17 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
Timber.v("## VOIP displayCallInProgressNotification")
|
||||
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: return
|
||||
val call = callManager.getCallById(callId) ?: return
|
||||
if (!knownCalls.contains(callId)) {
|
||||
Timber.v("Call in in background for unknown call $callId$")
|
||||
return
|
||||
}
|
||||
val opponentMatrixItem = getOpponentMatrixItem(call)
|
||||
|
||||
val notification = notificationUtils.buildPendingCallNotification(
|
||||
mxCall = call.mxCall,
|
||||
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId,
|
||||
fromBg = true)
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
// mCallIdInProgress = callId
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the permanent call notifications
|
||||
*/
|
||||
private fun hideCallNotifications() {
|
||||
val notification = notificationUtils.buildCallEndedNotification()
|
||||
alertManager.cancelAlert(INCOMING_CALL_ALERT_UID)
|
||||
mediaSession?.isActive = false
|
||||
// It's mandatory to startForeground to avoid crash
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
|
||||
myStopSelf()
|
||||
notificationManager.notify(callId.hashCode(), notification)
|
||||
}
|
||||
|
||||
fun addConnection(callConnection: CallConnection) {
|
||||
@ -277,12 +309,12 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
companion object {
|
||||
private const val NOTIFICATION_ID = 6480
|
||||
|
||||
private const val INCOMING_CALL_ALERT_UID = "INCOMING_CALL_ALERT_UID"
|
||||
private const val ACTION_INCOMING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_INCOMING_RINGING_CALL"
|
||||
private const val ACTION_OUTGOING_RINGING_CALL = "im.vector.app.core.services.CallService.ACTION_OUTGOING_RINGING_CALL"
|
||||
private const val ACTION_CALL_CONNECTING = "im.vector.app.core.services.CallService.ACTION_CALL_CONNECTING"
|
||||
private const val ACTION_ONGOING_CALL = "im.vector.app.core.services.CallService.ACTION_ONGOING_CALL"
|
||||
private const val ACTION_ONGOING_CALL_BG = "im.vector.app.core.services.CallService.ACTION_ONGOING_CALL_BG"
|
||||
private const val ACTION_CALL_TERMINATED = "im.vector.app.core.services.CallService.ACTION_CALL_TERMINATED"
|
||||
private const val ACTION_NO_ACTIVE_CALL = "im.vector.app.core.services.CallService.NO_ACTIVE_CALL"
|
||||
// private const val ACTION_ACTIVITY_VISIBLE = "im.vector.app.core.services.CallService.ACTION_ACTIVITY_VISIBLE"
|
||||
// private const val ACTION_STOP_RINGING = "im.vector.app.core.services.CallService.ACTION_STOP_RINGING"
|
||||
@ -335,10 +367,11 @@ class CallService : VectorService(), WiredHeadsetStateReceiver.HeadsetEventListe
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
|
||||
fun onNoActiveCall(context: Context) {
|
||||
fun onCallTerminated(context: Context, callId: String) {
|
||||
val intent = Intent(context, CallService::class.java)
|
||||
.apply {
|
||||
action = ACTION_NO_ACTIVE_CALL
|
||||
action = ACTION_CALL_TERMINATED
|
||||
putExtra(EXTRA_CALL_ID, callId)
|
||||
}
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
}
|
||||
|
@ -20,7 +20,10 @@ import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.RelativeLayout
|
||||
import im.vector.app.R
|
||||
import im.vector.app.features.call.webrtc.WebRtcCall
|
||||
import im.vector.app.features.themes.ThemeUtils
|
||||
import kotlinx.android.synthetic.main.view_active_call_view.view.*
|
||||
import org.matrix.android.sdk.api.session.call.CallState
|
||||
|
||||
class ActiveCallView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
@ -43,4 +46,21 @@ class ActiveCallView @JvmOverloads constructor(
|
||||
setBackgroundColor(ThemeUtils.getColor(context, R.attr.colorPrimary))
|
||||
setOnClickListener { callback?.onTapToReturnToCall() }
|
||||
}
|
||||
|
||||
fun render(calls: List<WebRtcCall>) {
|
||||
if (calls.size == 1) {
|
||||
activeCallInfo.setText(R.string.call_active_call)
|
||||
} else if (calls.size == 2) {
|
||||
val activeCall = calls.firstOrNull {
|
||||
it.mxCall.state is CallState.Connected && !it.isLocalOnHold()
|
||||
}
|
||||
if (activeCall == null) {
|
||||
activeCallInfo.setText(R.string.call_two_paused_calls)
|
||||
} else {
|
||||
activeCallInfo.setText(R.string.call_one_active_one_paused_call)
|
||||
}
|
||||
} else {
|
||||
visibility = GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@
|
||||
|
||||
package im.vector.app.core.ui.views
|
||||
|
||||
import android.view.View
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.core.view.isVisible
|
||||
import im.vector.app.core.utils.DebouncedClickListener
|
||||
@ -35,13 +34,14 @@ class ActiveCallViewHolder {
|
||||
|
||||
private var activeCallPipInitialized = false
|
||||
|
||||
fun updateCall(activeCall: WebRtcCall?) {
|
||||
fun updateCall(activeCall: WebRtcCall?, calls: List<WebRtcCall>) {
|
||||
this.activeCall = activeCall
|
||||
val hasActiveCall = activeCall?.mxCall?.state is CallState.Connected
|
||||
if (hasActiveCall) {
|
||||
val isVideoCall = activeCall?.mxCall?.isVideoCall == true
|
||||
if (isVideoCall) initIfNeeded()
|
||||
activeCallView?.isVisible = !isVideoCall
|
||||
activeCallView?.render(calls)
|
||||
pipWrapper?.isVisible = isVideoCall
|
||||
activeCallPiP?.isVisible = isVideoCall
|
||||
activeCallPiP?.let {
|
||||
@ -74,10 +74,9 @@ class ActiveCallViewHolder {
|
||||
this.activeCallPiP = activeCallPiP
|
||||
this.activeCallView = activeCallView
|
||||
this.pipWrapper = pipWrapper
|
||||
|
||||
this.activeCallView?.callback = interactionListener
|
||||
pipWrapper.setOnClickListener(
|
||||
DebouncedClickListener(View.OnClickListener { _ ->
|
||||
DebouncedClickListener({ _ ->
|
||||
interactionListener.onTapToReturnToCall()
|
||||
})
|
||||
)
|
||||
|
@ -23,36 +23,42 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import org.matrix.android.sdk.api.session.call.MxCall
|
||||
import javax.inject.Inject
|
||||
|
||||
class SharedActiveCallViewModel @Inject constructor(
|
||||
class SharedCurrentCallViewModel @Inject constructor(
|
||||
private val callManager: WebRtcCallManager
|
||||
) : ViewModel() {
|
||||
|
||||
val activeCall: MutableLiveData<WebRtcCall?> = MutableLiveData()
|
||||
val currentCall: MutableLiveData<WebRtcCall?> = MutableLiveData()
|
||||
|
||||
val callStateListener = object : WebRtcCall.Listener {
|
||||
|
||||
override fun onStateUpdate(call: MxCall) {
|
||||
if (activeCall.value?.callId == call.callId) {
|
||||
activeCall.postValue(callManager.getCallById(call.callId))
|
||||
}
|
||||
//post it-self
|
||||
currentCall.postValue(currentCall.value)
|
||||
}
|
||||
|
||||
override fun onHoldUnhold() {
|
||||
super.onHoldUnhold()
|
||||
//post it-self
|
||||
currentCall.postValue(currentCall.value)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val listener = object : WebRtcCallManager.CurrentCallListener {
|
||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||
activeCall.value?.mxCall?.removeListener(callStateListener)
|
||||
activeCall.postValue(call)
|
||||
currentCall.value?.mxCall?.removeListener(callStateListener)
|
||||
currentCall.postValue(call)
|
||||
call?.addListener(callStateListener)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
activeCall.postValue(callManager.currentCall)
|
||||
currentCall.postValue(callManager.currentCall)
|
||||
callManager.addCurrentCallListener(listener)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
activeCall.value?.removeListener(callStateListener)
|
||||
currentCall.value?.removeListener(callStateListener)
|
||||
callManager.removeCurrentCallListener(listener)
|
||||
super.onCleared()
|
||||
}
|
@ -106,7 +106,6 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
callArgs = intent.getParcelableExtra(MvRx.KEY_ARG)!!
|
||||
} else {
|
||||
Timber.e("## VOIP missing callArgs for VectorCall Activity")
|
||||
CallService.onNoActiveCall(this)
|
||||
finish()
|
||||
}
|
||||
|
||||
@ -153,8 +152,6 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
private fun renderState(state: VectorCallViewState) {
|
||||
Timber.v("## VOIP renderState call $state")
|
||||
if (state.callState is Fail) {
|
||||
// be sure to clear notification
|
||||
CallService.onNoActiveCall(this)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
@ -295,7 +292,6 @@ class VectorCallActivity : VectorBaseActivity<ActivityCallBinding>(), CallContro
|
||||
Timber.v("## VOIP handleViewEvents $event")
|
||||
when (event) {
|
||||
VectorCallViewEvents.DismissNoCall -> {
|
||||
CallService.onNoActiveCall(this)
|
||||
finish()
|
||||
}
|
||||
is VectorCallViewEvents.ConnectionTimeout -> {
|
||||
|
@ -117,14 +117,6 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
|
||||
private val currentCallListener = object : WebRtcCallManager.CurrentCallListener {
|
||||
|
||||
override fun onCurrentCallChange(call: WebRtcCall?) {
|
||||
// we need to check the state
|
||||
if (call == null) {
|
||||
// we should dismiss, e.g handled by other session?
|
||||
_viewEvents.post(VectorCallViewEvents.DismissNoCall)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAudioDevicesChange() {
|
||||
val currentSoundDevice = callManager.callAudioManager.getCurrentSoundDevice()
|
||||
if (currentSoundDevice == CallAudioManager.SoundDevice.PHONE) {
|
||||
@ -163,6 +155,8 @@ class VectorCallViewModel @AssistedInject constructor(
|
||||
callState = Success(webRtcCall.mxCall.state),
|
||||
otherUserMatrixItem = item?.let { Success(it) } ?: Uninitialized,
|
||||
soundDevice = currentSoundDevice,
|
||||
isLocalOnHold = webRtcCall.isLocalOnHold(),
|
||||
isRemoteOnHold = webRtcCall.remoteOnHold,
|
||||
availableSoundDevices = callManager.callAudioManager.getAvailableSoundDevices(),
|
||||
isFrontCamera = call?.currentCameraType() == CameraType.FRONT,
|
||||
canSwitchCamera = call?.canSwitchCamera() ?: false,
|
||||
|
@ -88,6 +88,7 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
private val dispatcher: CoroutineContext,
|
||||
private val sessionProvider: Provider<Session?>,
|
||||
private val peerConnectionFactoryProvider: Provider<PeerConnectionFactory?>,
|
||||
private val onCallBecomeActive: (WebRtcCall) -> Unit,
|
||||
private val onCallEnded: (WebRtcCall) -> Unit) : MxCall.StateListener {
|
||||
|
||||
interface Listener : MxCall.StateListener {
|
||||
@ -130,8 +131,11 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
|
||||
// Mute status
|
||||
var micMuted = false
|
||||
private set
|
||||
var videoMuted = false
|
||||
private set
|
||||
var remoteOnHold = false
|
||||
private set
|
||||
|
||||
var offerSdp: CallInviteContent.Offer? = null
|
||||
|
||||
@ -328,6 +332,9 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
}
|
||||
|
||||
private suspend fun internalAcceptIncomingCall() = withContext(dispatcher) {
|
||||
tryOrNull {
|
||||
onCallBecomeActive(this@WebRtcCall)
|
||||
}
|
||||
val turnServerResponse = getTurnServer()
|
||||
// Update service state
|
||||
withContext(Dispatchers.Main) {
|
||||
@ -542,6 +549,9 @@ class WebRtcCall(val mxCall: MxCall,
|
||||
fun updateRemoteOnHold(onHold: Boolean) {
|
||||
if (remoteOnHold == onHold) return
|
||||
remoteOnHold = onHold
|
||||
if (!onHold) {
|
||||
onCallBecomeActive(this)
|
||||
}
|
||||
val direction = if (onHold) {
|
||||
RtpTransceiver.RtpTransceiverDirection.INACTIVE
|
||||
} else {
|
||||
|
@ -45,6 +45,7 @@ import org.webrtc.DefaultVideoDecoderFactory
|
||||
import org.webrtc.DefaultVideoEncoderFactory
|
||||
import org.webrtc.PeerConnectionFactory
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.Executors
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
@ -63,7 +64,7 @@ class WebRtcCallManager @Inject constructor(
|
||||
get() = activeSessionDataSource.currentValue?.orNull()
|
||||
|
||||
interface CurrentCallListener {
|
||||
fun onCurrentCallChange(call: WebRtcCall?)
|
||||
fun onCurrentCallChange(call: WebRtcCall?) {}
|
||||
fun onAudioDevicesChange() {}
|
||||
}
|
||||
|
||||
@ -101,15 +102,15 @@ class WebRtcCallManager @Inject constructor(
|
||||
}
|
||||
|
||||
var currentCall: WebRtcCall? = null
|
||||
set(value) {
|
||||
private set(value) {
|
||||
field = value
|
||||
currentCallsListeners.forEach {
|
||||
tryOrNull { it.onCurrentCallChange(value) }
|
||||
}
|
||||
}
|
||||
|
||||
private val callsByCallId = HashMap<String, WebRtcCall>()
|
||||
private val callsByRoomId = HashMap<String, ArrayList<WebRtcCall>>()
|
||||
private val callsByCallId = ConcurrentHashMap<String, WebRtcCall>()
|
||||
private val callsByRoomId = ConcurrentHashMap<String, MutableList<WebRtcCall>>()
|
||||
|
||||
fun getCallById(callId: String): WebRtcCall? {
|
||||
return callsByCallId[callId]
|
||||
@ -119,6 +120,10 @@ class WebRtcCallManager @Inject constructor(
|
||||
return callsByRoomId[roomId] ?: emptyList()
|
||||
}
|
||||
|
||||
fun getCalls(): List<WebRtcCall> {
|
||||
return callsByCallId.values.toList()
|
||||
}
|
||||
|
||||
fun headSetButtonTapped() {
|
||||
Timber.v("## VOIP headSetButtonTapped")
|
||||
val call = currentCall ?: return
|
||||
@ -161,13 +166,23 @@ class WebRtcCallManager @Inject constructor(
|
||||
.createPeerConnectionFactory()
|
||||
}
|
||||
|
||||
private fun onCallActive(call: WebRtcCall) {
|
||||
Timber.v("## VOIP WebRtcPeerConnectionManager onCall active: ${call.mxCall.callId}")
|
||||
if (currentCall != call) {
|
||||
currentCall?.updateRemoteOnHold(onHold = true)
|
||||
currentCall = call
|
||||
}
|
||||
}
|
||||
|
||||
private fun onCallEnded(call: WebRtcCall) {
|
||||
Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: ${call.mxCall.callId}")
|
||||
CallService.onNoActiveCall(context)
|
||||
CallService.onCallTerminated(context, call.callId)
|
||||
callAudioManager.stop()
|
||||
currentCall = null
|
||||
callsByCallId.remove(call.mxCall.callId)
|
||||
callsByRoomId[call.mxCall.roomId]?.remove(call)
|
||||
if (currentCall == call) {
|
||||
currentCall = getCalls().lastOrNull()
|
||||
}
|
||||
// This must be done in this thread
|
||||
executor.execute {
|
||||
if (currentCall == null) {
|
||||
@ -181,12 +196,17 @@ class WebRtcCallManager @Inject constructor(
|
||||
|
||||
fun startOutgoingCall(signalingRoomId: String, otherUserId: String, isVideoCall: Boolean) {
|
||||
Timber.v("## VOIP startOutgoingCall in room $signalingRoomId to $otherUserId isVideo $isVideoCall")
|
||||
if (currentCall != null && currentCall?.mxCall?.state !is CallState.Connected || getCalls().size >= 2) {
|
||||
Timber.w("## VOIP cannot start outgoing call")
|
||||
// Just ignore, maybe we could answer from other session?
|
||||
return
|
||||
}
|
||||
executor.execute {
|
||||
createPeerConnectionFactoryIfNeeded()
|
||||
}
|
||||
|
||||
currentCall?.updateRemoteOnHold(onHold = true)
|
||||
val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
|
||||
createWebRtcCall(mxCall)
|
||||
currentCall = createWebRtcCall(mxCall)
|
||||
callAudioManager.startForCall(mxCall)
|
||||
|
||||
CallService.onOutgoingCallRinging(
|
||||
@ -199,10 +219,11 @@ class WebRtcCallManager @Inject constructor(
|
||||
|
||||
override fun onCallIceCandidateReceived(mxCall: MxCall, iceCandidatesContent: CallCandidatesContent) {
|
||||
Timber.v("## VOIP onCallIceCandidateReceived for call ${mxCall.callId}")
|
||||
if (currentCall?.mxCall?.callId != mxCall.callId) return Unit.also {
|
||||
Timber.w("## VOIP ignore ice candidates from other call")
|
||||
}
|
||||
currentCall?.onCallIceCandidateReceived(iceCandidatesContent)
|
||||
val call = callsByCallId[iceCandidatesContent.callId]
|
||||
?: return Unit.also {
|
||||
Timber.w("onCallIceCandidateReceived for non active call? ${iceCandidatesContent.callId}")
|
||||
}
|
||||
call.onCallIceCandidateReceived(iceCandidatesContent)
|
||||
}
|
||||
|
||||
private fun createWebRtcCall(mxCall: MxCall): WebRtcCall {
|
||||
@ -217,21 +238,17 @@ class WebRtcCallManager @Inject constructor(
|
||||
peerConnectionFactory
|
||||
},
|
||||
sessionProvider = { currentSession },
|
||||
onCallBecomeActive = this::onCallActive,
|
||||
onCallEnded = this::onCallEnded
|
||||
)
|
||||
currentCall = webRtcCall
|
||||
callsByCallId[mxCall.callId] = webRtcCall
|
||||
callsByRoomId.getOrPut(mxCall.roomId) { ArrayList() }
|
||||
callsByRoomId.getOrPut(mxCall.roomId) { ArrayList(1) }
|
||||
.add(webRtcCall)
|
||||
return webRtcCall
|
||||
}
|
||||
|
||||
fun acceptIncomingCall() {
|
||||
currentCall?.acceptIncomingCall()
|
||||
}
|
||||
|
||||
fun endCall(originatedByMe: Boolean = true) {
|
||||
currentCall?.endCall(originatedByMe)
|
||||
fun endCallForRoom(roomId: String, originatedByMe: Boolean = true) {
|
||||
callsByRoomId[roomId]?.forEach { it.endCall(originatedByMe) }
|
||||
}
|
||||
|
||||
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
|
||||
@ -248,8 +265,8 @@ class WebRtcCallManager @Inject constructor(
|
||||
|
||||
override fun onCallInviteReceived(mxCall: MxCall, callInviteContent: CallInviteContent) {
|
||||
Timber.v("## VOIP onCallInviteReceived callId ${mxCall.callId}")
|
||||
if (currentCall != null) {
|
||||
Timber.w("## VOIP receiving incoming call while already in call?")
|
||||
if (currentCall != null && currentCall?.mxCall?.state !is CallState.Connected || getCalls().size >= 2) {
|
||||
Timber.w("## VOIP receiving incoming call but cannot handle it")
|
||||
// Just ignore, maybe we could answer from other session?
|
||||
return
|
||||
}
|
||||
@ -329,12 +346,12 @@ class WebRtcCallManager @Inject constructor(
|
||||
|
||||
override fun onCallManagedByOtherSession(callId: String) {
|
||||
Timber.v("## VOIP onCallManagedByOtherSession: $callId")
|
||||
currentCall = null
|
||||
val webRtcCall = callsByCallId.remove(callId)
|
||||
if (webRtcCall != null) {
|
||||
callsByRoomId[webRtcCall.mxCall.roomId]?.remove(webRtcCall)
|
||||
}
|
||||
CallService.onNoActiveCall(context)
|
||||
// TODO: handle this properly
|
||||
CallService.onCallTerminated(context, callId)
|
||||
|
||||
// did we start background sync? so we should stop it
|
||||
if (isInBackground) {
|
||||
|
@ -21,7 +21,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import com.airbnb.mvrx.activityViewModel
|
||||
import com.airbnb.mvrx.fragmentViewModel
|
||||
import com.airbnb.mvrx.withState
|
||||
@ -37,7 +36,7 @@ import im.vector.app.core.ui.views.ActiveCallView
|
||||
import im.vector.app.core.ui.views.ActiveCallViewHolder
|
||||
import im.vector.app.core.ui.views.KeysBackupBanner
|
||||
import im.vector.app.databinding.FragmentHomeDetailBinding
|
||||
import im.vector.app.features.call.SharedActiveCallViewModel
|
||||
import im.vector.app.features.call.SharedCurrentCallViewModel
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
import im.vector.app.features.home.room.list.RoomListFragment
|
||||
@ -78,7 +77,7 @@ class HomeDetailFragment @Inject constructor(
|
||||
private val serverBackupStatusViewModel: ServerBackupStatusViewModel by activityViewModel()
|
||||
|
||||
private lateinit var sharedActionViewModel: HomeSharedActionViewModel
|
||||
private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel
|
||||
private lateinit var sharedCallActionViewModel: SharedCurrentCallViewModel
|
||||
|
||||
override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentHomeDetailBinding {
|
||||
return FragmentHomeDetailBinding.inflate(inflater, container, false)
|
||||
@ -89,7 +88,7 @@ class HomeDetailFragment @Inject constructor(
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java)
|
||||
sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java)
|
||||
sharedCallActionViewModel = activityViewModelProvider.get(SharedCurrentCallViewModel::class.java)
|
||||
|
||||
setupBottomNavigationView()
|
||||
setupToolbar()
|
||||
@ -127,9 +126,9 @@ class HomeDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
sharedCallActionViewModel
|
||||
.activeCall
|
||||
.observe(viewLifecycleOwner, Observer {
|
||||
activeCallViewHolder.updateCall(it)
|
||||
.currentCall
|
||||
.observe(viewLifecycleOwner, {
|
||||
activeCallViewHolder.updateCall(it, callManager.getCalls())
|
||||
invalidateOptionsMenu()
|
||||
})
|
||||
}
|
||||
@ -336,7 +335,7 @@ class HomeDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onTapToReturnToCall() {
|
||||
sharedCallActionViewModel.activeCall.value?.let { call ->
|
||||
sharedCallActionViewModel.currentCall.value?.let { call ->
|
||||
VectorCallActivity.newIntent(
|
||||
context = requireContext(),
|
||||
callId = call.callId,
|
||||
|
@ -120,7 +120,7 @@ import im.vector.app.features.attachments.ContactAttachment
|
||||
import im.vector.app.features.attachments.preview.AttachmentsPreviewActivity
|
||||
import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs
|
||||
import im.vector.app.features.attachments.toGroupedContentAttachmentData
|
||||
import im.vector.app.features.call.SharedActiveCallViewModel
|
||||
import im.vector.app.features.call.SharedCurrentCallViewModel
|
||||
import im.vector.app.features.call.VectorCallActivity
|
||||
import im.vector.app.features.call.conference.JitsiCallViewModel
|
||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||
@ -227,7 +227,8 @@ class RoomDetailFragment @Inject constructor(
|
||||
private val matrixItemColorProvider: MatrixItemColorProvider,
|
||||
private val imageContentRenderer: ImageContentRenderer,
|
||||
private val roomDetailPendingActionStore: RoomDetailPendingActionStore,
|
||||
private val pillsPostProcessorFactory: PillsPostProcessor.Factory
|
||||
private val pillsPostProcessorFactory: PillsPostProcessor.Factory,
|
||||
private val callManager: WebRtcCallManager
|
||||
) :
|
||||
VectorBaseFragment<FragmentRoomDetailBinding>(),
|
||||
TimelineEventController.Callback,
|
||||
@ -282,7 +283,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
override fun getMenuRes() = R.menu.menu_timeline
|
||||
|
||||
private lateinit var sharedActionViewModel: MessageSharedActionViewModel
|
||||
private lateinit var sharedCallActionViewModel: SharedActiveCallViewModel
|
||||
private lateinit var sharedCurrentCallViewModel: SharedCurrentCallViewModel
|
||||
|
||||
private lateinit var layoutManager: LinearLayoutManager
|
||||
private lateinit var jumpToBottomViewVisibilityManager: JumpToBottomViewVisibilityManager
|
||||
@ -299,7 +300,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
sharedActionViewModel = activityViewModelProvider.get(MessageSharedActionViewModel::class.java)
|
||||
sharedCallActionViewModel = activityViewModelProvider.get(SharedActiveCallViewModel::class.java)
|
||||
sharedCurrentCallViewModel = activityViewModelProvider.get(SharedCurrentCallViewModel::class.java)
|
||||
attachmentsHelper = AttachmentsHelper(requireContext(), this).register()
|
||||
keyboardStateUtils = KeyboardStateUtils(requireActivity())
|
||||
setupToolbar(views.roomToolbar)
|
||||
@ -324,10 +325,10 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
.disposeOnDestroyView()
|
||||
|
||||
sharedCallActionViewModel
|
||||
.activeCall
|
||||
sharedCurrentCallViewModel
|
||||
.currentCall
|
||||
.observe(viewLifecycleOwner, {
|
||||
activeCallViewHolder.updateCall(it)
|
||||
activeCallViewHolder.updateCall(it, callManager.getCalls())
|
||||
invalidateOptionsMenu()
|
||||
})
|
||||
|
||||
@ -799,8 +800,8 @@ class RoomDetailFragment @Inject constructor(
|
||||
showDialogWithMessage(getString(R.string.cannot_call_yourself))
|
||||
}
|
||||
}
|
||||
2 -> {
|
||||
val activeCall = sharedCallActionViewModel.activeCall.value
|
||||
2 -> {
|
||||
val activeCall = sharedCurrentCallViewModel.currentCall.value
|
||||
if (activeCall != null) {
|
||||
// resume existing if same room, if not prompt to kill and then restart new call?
|
||||
if (activeCall.roomId == roomDetailArgs.roomId) {
|
||||
@ -2015,7 +2016,7 @@ class RoomDetailFragment @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onTapToReturnToCall() {
|
||||
sharedCallActionViewModel.activeCall.value?.let { call ->
|
||||
sharedCurrentCallViewModel.currentCall.value?.let { call ->
|
||||
VectorCallActivity.newIntent(
|
||||
context = requireContext(),
|
||||
callId = call.callId,
|
||||
|
@ -349,7 +349,7 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private fun handleEndCall() {
|
||||
callManager.endCall()
|
||||
callManager.endCallForRoom(initialState.roomId)
|
||||
}
|
||||
|
||||
private fun handleSelectStickerAttachment() {
|
||||
|
@ -450,6 +450,7 @@ class NotificationUtils @Inject constructor(private val context: Context,
|
||||
fun buildCallEndedNotification(): Notification {
|
||||
return NotificationCompat.Builder(context, SILENT_NOTIFICATION_CHANNEL_ID)
|
||||
.setContentTitle(stringProvider.getString(R.string.call_ended))
|
||||
.setTimeoutAfter(2000)
|
||||
.setSmallIcon(R.drawable.ic_material_call_end_grey)
|
||||
.setCategory(NotificationCompat.CATEGORY_CALL)
|
||||
.build()
|
||||
|
@ -20,10 +20,11 @@
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="12dp"
|
||||
android:text="@string/active_call"
|
||||
android:textSize="14sp"
|
||||
android:text="@string/call_one_active_one_paused_call"
|
||||
android:textColor="@color/white"
|
||||
app:drawableTint="@color/white"
|
||||
app:drawableStartCompat="@drawable/ic_call" />
|
||||
app:drawableStartCompat="@drawable/ic_call_answer" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/returnToCallButton"
|
||||
@ -38,9 +39,9 @@
|
||||
android:gravity="center"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/return_to_call"
|
||||
android:text="@string/action_return"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</merge>
|
||||
|
@ -141,6 +141,7 @@
|
||||
<string name="action_unpublish">Unpublish</string>
|
||||
<string name="copied_to_clipboard">Copied to clipboard</string>
|
||||
<string name="disable">Disable</string>
|
||||
<string name="action_return">Return</string>
|
||||
|
||||
<!-- dialog titles -->
|
||||
<string name="dialog_title_confirmation">Confirmation</string>
|
||||
@ -2773,4 +2774,9 @@
|
||||
<string name="call_tile_other_declined">%1$s declined this call</string>
|
||||
<string name="call_tile_ended">This call has ended</string>
|
||||
<string name="call_tile_call_back">Call back</string>
|
||||
|
||||
<string name="call_active_call">Active call (%1$s)</string>
|
||||
<string name="call_one_active_one_paused_call">1 active call (%1$s) · 1 paused call</string>
|
||||
<string name="call_two_paused_calls">2 paused calls</string>
|
||||
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user