VoIP: start introducing switch call

This commit is contained in:
ganfra 2020-12-22 11:58:38 +01:00
parent 14288b545b
commit 629488bbe6
15 changed files with 183 additions and 100 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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
}
}
}

View File

@ -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()
})
)

View File

@ -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()
}

View File

@ -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 -> {

View File

@ -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,

View File

@ -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 {

View File

@ -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) {

View File

@ -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,

View File

@ -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,

View File

@ -349,7 +349,7 @@ class RoomDetailViewModel @AssistedInject constructor(
}
private fun handleEndCall() {
callManager.endCall()
callManager.endCallForRoom(initialState.roomId)
}
private fun handleSelectStickerAttachment() {

View File

@ -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()

View File

@ -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>

View File

@ -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>