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