VoIP: fix a bunch of issues

This commit is contained in:
ganfra 2021-01-26 18:50:21 +01:00
parent 05361c13f1
commit 88e18a8640
13 changed files with 151 additions and 102 deletions

View File

@ -126,7 +126,16 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
private fun handleCallRejectEvent(event: Event) { private fun handleCallRejectEvent(event: Event) {
val content = event.getClearContent().toModel<CallRejectContent>() ?: return val content = event.getClearContent().toModel<CallRejectContent>() ?: return
val call = content.getCall() ?: return val call = content.getCall() ?: return
if (call.ourPartyId == content.partyId) {
// Ignore remote echo
return
}
activeCallHandler.removeCall(content.callId) activeCallHandler.removeCall(content.callId)
if (event.senderId == userId) {
// discard current call, it's rejected by another of my session
callListenersDispatcher.onCallManagedByOtherSession(content.callId)
return
}
// No need to check party_id for reject because if we'd received either // No need to check party_id for reject because if we'd received either
// an answer or reject, we wouldn't be in state InviteSent // an answer or reject, we wouldn't be in state InviteSent
if (call.state != CallState.Dialing) { if (call.state != CallState.Dialing) {
@ -177,6 +186,7 @@ internal class CallSignalingHandler @Inject constructor(private val activeCallHa
} }
if (event.senderId == userId) { if (event.senderId == userId) {
// discard current call, it's answered by another of my session // discard current call, it's answered by another of my session
activeCallHandler.removeCall(call.callId)
callListenersDispatcher.onCallManagedByOtherSession(content.callId) callListenersDispatcher.onCallManagedByOtherSession(content.callId)
} else { } else {
if (call.opponentPartyId != null) { if (call.opponentPartyId != null) {

View File

@ -161,7 +161,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils # android\.text\.TextUtils
### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt ### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
enum class===87 enum class===88
### Do not import temporary legacy classes ### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3 import org.matrix.android.sdk.internal.legacy.riot===3

View File

@ -16,33 +16,76 @@
package im.vector.app.core.services package im.vector.app.core.services
import android.app.NotificationChannel
import android.content.Context import android.content.Context
import android.media.Ringtone
import android.media.RingtoneManager
import android.media.AudioAttributes import android.media.AudioAttributes
import android.media.AudioManager import android.media.AudioManager
import android.media.MediaPlayer import android.media.MediaPlayer
import android.media.Ringtone
import android.media.RingtoneManager
import android.os.Build import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import im.vector.app.R import im.vector.app.R
import im.vector.app.features.notifications.NotificationUtils
import org.matrix.android.sdk.api.extensions.orFalse
import timber.log.Timber import timber.log.Timber
class CallRingPlayerIncoming( class CallRingPlayerIncoming(
context: Context context: Context,
private val notificationUtils: NotificationUtils
) { ) {
private val applicationContext = context.applicationContext private val applicationContext = context.applicationContext
private var r: Ringtone? = null private var ringtone: Ringtone? = null
private var vibrator: Vibrator? = null
fun start() { private val VIBRATE_PATTERN = longArrayOf(0, 400, 600)
val notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
r = RingtoneManager.getRingtone(applicationContext, notification) fun start(fromBg: Boolean) {
Timber.v("## VOIP Starting ringing incomming") val audioManager = applicationContext.getSystemService<AudioManager>()
r?.play() val incomingCallChannel = notificationUtils.getChannelForIncomingCall(fromBg)
val ringerMode = audioManager?.ringerMode
if (ringerMode == AudioManager.RINGER_MODE_NORMAL) {
playRingtoneIfNeeded(incomingCallChannel)
} else if (ringerMode == AudioManager.RINGER_MODE_VIBRATE) {
vibrateIfNeeded(incomingCallChannel)
}
}
private fun playRingtoneIfNeeded(incomingCallChannel: NotificationChannel?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && incomingCallChannel?.sound != null) {
Timber.v("Ringtone already configured by notification channel")
return
}
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
ringtone = RingtoneManager.getRingtone(applicationContext, ringtoneUri)
Timber.v("Play ringtone for incoming call")
ringtone?.play()
}
private fun vibrateIfNeeded(incomingCallChannel: NotificationChannel?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && incomingCallChannel?.shouldVibrate().orFalse()) {
Timber.v("## Vibration already configured by notification channel")
return
}
vibrator = applicationContext.getSystemService()
Timber.v("Vibrate for incoming call")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationEffect = VibrationEffect.createWaveform(VIBRATE_PATTERN, 0)
vibrator?.vibrate(vibrationEffect)
} else {
@Suppress("DEPRECATION")
vibrator?.vibrate(VIBRATE_PATTERN, 0)
}
} }
fun stop() { fun stop() {
r?.stop() ringtone?.stop()
ringtone = null
vibrator?.cancel()
vibrator = null
} }
} }
@ -55,12 +98,12 @@ class CallRingPlayerOutgoing(
private var player: MediaPlayer? = null private var player: MediaPlayer? = null
fun start() { fun start() {
val audioManager = applicationContext.getSystemService<AudioManager>()!! val audioManager: AudioManager? = applicationContext.getSystemService()
player?.release() player?.release()
player = createPlayer() player = createPlayer()
// Check if sound is enabled // Check if sound is enabled
val ringerMode = audioManager.ringerMode val ringerMode = audioManager?.ringerMode
if (player != null && ringerMode == AudioManager.RINGER_MODE_NORMAL) { if (player != null && ringerMode == AudioManager.RINGER_MODE_NORMAL) {
try { try {
if (player?.isPlaying == false) { if (player?.isPlaying == false) {
@ -89,14 +132,14 @@ class CallRingPlayerOutgoing(
mediaPlayer.setOnErrorListener(MediaPlayerErrorListener()) mediaPlayer.setOnErrorListener(MediaPlayerErrorListener())
mediaPlayer.isLooping = true mediaPlayer.isLooping = true
if (Build.VERSION.SDK_INT <= 21) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
@Suppress("DEPRECATION")
mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING)
} else {
mediaPlayer.setAudioAttributes(AudioAttributes.Builder() mediaPlayer.setAudioAttributes(AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
.build()) .build())
} else {
@Suppress("DEPRECATION")
mediaPlayer.setAudioStreamType(AudioManager.STREAM_RING)
} }
return mediaPlayer return mediaPlayer
} catch (failure: Throwable) { } catch (failure: Throwable) {

View File

@ -79,7 +79,7 @@ class CallService : VectorService() {
callManager = vectorComponent().webRtcCallManager() callManager = vectorComponent().webRtcCallManager()
avatarRenderer = vectorComponent().avatarRenderer() avatarRenderer = vectorComponent().avatarRenderer()
alertManager = vectorComponent().alertManager() alertManager = vectorComponent().alertManager()
callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext) callRingPlayerIncoming = CallRingPlayerIncoming(applicationContext, notificationUtils)
callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext) callRingPlayerOutgoing = CallRingPlayerOutgoing(applicationContext)
} }
@ -98,21 +98,17 @@ class CallService : VectorService() {
setCallback(mediaSessionButtonCallback) setCallback(mediaSessionButtonCallback)
} }
} }
if (intent == null) {
// Service started again by the system.
// TODO What do we do here?
return START_STICKY
}
mediaSession?.let { mediaSession?.let {
// This ensures that the correct callbacks to MediaSessionCompat.Callback // This ensures that the correct callbacks to MediaSessionCompat.Callback
// will be triggered based on the incoming KeyEvent. // will be triggered based on the incoming KeyEvent.
MediaButtonReceiver.handleIntent(it, intent) MediaButtonReceiver.handleIntent(it, intent)
} }
when (intent.action) { when (intent?.action) {
ACTION_INCOMING_RINGING_CALL -> { ACTION_INCOMING_RINGING_CALL -> {
mediaSession?.isActive = true mediaSession?.isActive = true
callRingPlayerIncoming?.start() val fromBg = intent.getBooleanExtra(EXTRA_IS_IN_BG, false)
callRingPlayerIncoming?.start(fromBg)
displayIncomingCallNotification(intent) displayIncomingCallNotification(intent)
} }
ACTION_OUTGOING_RINGING_CALL -> { ACTION_OUTGOING_RINGING_CALL -> {
@ -136,15 +132,12 @@ class CallService : VectorService() {
handleCallTerminated(intent) handleCallTerminated(intent)
} }
else -> { else -> {
// Should not happen handleUnexpectedState(null)
callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop()
myStopSelf()
} }
} }
// We want the system to restore the service if killed // We want the system to restore the service if killed
return START_STICKY return START_REDELIVER_INTENT
} }
// ================================================================================ // ================================================================================
@ -158,10 +151,8 @@ class CallService : VectorService() {
private fun displayIncomingCallNotification(intent: Intent) { private fun displayIncomingCallNotification(intent: Intent) {
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 Unit.also {
if (knownCalls.contains(callId)) { handleUnexpectedState(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)
@ -202,13 +193,14 @@ class CallService : VectorService() {
private fun handleCallTerminated(intent: Intent) { private fun handleCallTerminated(intent: Intent) {
val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: "" val callId = intent.getStringExtra(EXTRA_CALL_ID) ?: ""
alertManager.cancelAlert(callId)
if (!knownCalls.remove(callId)) { if (!knownCalls.remove(callId)) {
Timber.v("Call terminated for unknown call $callId$") Timber.v("Call terminated for unknown call $callId$")
handleUnexpectedState(callId)
return return
} }
val notification = notificationUtils.buildCallEndedNotification() val notification = notificationUtils.buildCallEndedNotification()
notificationManager.notify(callId.hashCode(), notification) notificationManager.notify(callId.hashCode(), notification)
alertManager.cancelAlert(callId)
if (knownCalls.isEmpty()) { if (knownCalls.isEmpty()) {
mediaSession?.isActive = false mediaSession?.isActive = false
myStopSelf() myStopSelf()
@ -225,11 +217,9 @@ class CallService : VectorService() {
} }
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) ?: ""
val call = callManager.getCallById(callId) ?: return val call = callManager.getCallById(callId) ?: return Unit.also {
if (knownCalls.contains(callId)) { handleUnexpectedState(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")
@ -251,10 +241,8 @@ class CallService : VectorService() {
private fun displayCallInProgressNotification(intent: Intent) { private fun displayCallInProgressNotification(intent: Intent) {
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 Unit.also {
if (!knownCalls.contains(callId)) { handleUnexpectedState(callId)
Timber.v("Call in progress for unknown call $callId$")
return
} }
val opponentMatrixItem = getOpponentMatrixItem(call) val opponentMatrixItem = getOpponentMatrixItem(call)
alertManager.cancelAlert(callId) alertManager.cancelAlert(callId)
@ -262,8 +250,28 @@ class CallService : VectorService() {
mxCall = call.mxCall, mxCall = call.mxCall,
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
) )
if (knownCalls.isEmpty()) {
startForeground(callId.hashCode(), notification)
} else {
notificationManager.notify(callId.hashCode(), notification) notificationManager.notify(callId.hashCode(), notification)
} }
knownCalls.add(callId)
}
private fun handleUnexpectedState(callId: String?) {
Timber.v("Fallback to clear everything")
callRingPlayerIncoming?.stop()
callRingPlayerOutgoing?.stop()
if (callId != null) {
notificationManager.cancel(callId.hashCode())
}
val notification = notificationUtils.buildCallEndedNotification()
startForeground(DEFAULT_NOTIFICATION_ID, notification)
if (knownCalls.isEmpty()) {
mediaSession?.isActive = false
myStopSelf()
}
}
fun addConnection(callConnection: CallConnection) { fun addConnection(callConnection: CallConnection) {
connections[callConnection.callId] = callConnection connections[callConnection.callId] = callConnection
@ -274,7 +282,7 @@ class CallService : VectorService() {
} }
companion object { companion object {
private const val NOTIFICATION_ID = 6480 private const val DEFAULT_NOTIFICATION_ID = 6480
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"

View File

@ -20,7 +20,6 @@ import android.content.Context
import android.content.res.ColorStateList import android.content.res.ColorStateList
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.withStyledAttributes import androidx.core.content.withStyledAttributes

View File

@ -21,11 +21,7 @@ import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothManager import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile import android.bluetooth.BluetoothProfile
import android.content.Context import android.content.Context
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager import android.media.AudioManager
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import im.vector.app.core.services.BluetoothHeadsetReceiver import im.vector.app.core.services.BluetoothHeadsetReceiver
import im.vector.app.core.services.WiredHeadsetStateReceiver import im.vector.app.core.services.WiredHeadsetStateReceiver
@ -52,16 +48,15 @@ internal class API21AudioDeviceDetector(private val context: Context,
private fun getAvailableSoundDevices(): Set<CallAudioManager.Device> { private fun getAvailableSoundDevices(): Set<CallAudioManager.Device> {
return HashSet<CallAudioManager.Device>().apply { return HashSet<CallAudioManager.Device>().apply {
if (isBluetoothHeadsetOn()) add(CallAudioManager.Device.WIRELESS_HEADSET) if (isBluetoothHeadsetOn()) add(CallAudioManager.Device.WIRELESS_HEADSET)
if(isWiredHeadsetOn()){ if (isWiredHeadsetOn()) {
add(CallAudioManager.Device.HEADSET) add(CallAudioManager.Device.HEADSET)
}else { } else {
add(CallAudioManager.Device.PHONE) add(CallAudioManager.Device.PHONE)
} }
add(CallAudioManager.Device.SPEAKER) add(CallAudioManager.Device.SPEAKER)
} }
} }
private fun isWiredHeadsetOn(): Boolean { private fun isWiredHeadsetOn(): Boolean {
return audioManager.isWiredHeadsetOn return audioManager.isWiredHeadsetOn
} }
@ -82,7 +77,6 @@ internal class API21AudioDeviceDetector(private val context: Context,
} }
} }
/** /**
* Helper method to trigger an audio route update when devices change. It * Helper method to trigger an audio route update when devices change. It
* makes sure the operation is performed on the audio thread. * makes sure the operation is performed on the audio thread.
@ -119,7 +113,6 @@ internal class API21AudioDeviceDetector(private val context: Context,
onAudioDeviceChange() onAudioDeviceChange()
} }
override fun stop() { override fun stop() {
Timber.i("Stop using $this as the audio device handler") Timber.i("Stop using $this as the audio device handler")
wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(context, it) } wiredHeadsetStateReceiver?.let { WiredHeadsetStateReceiver.unRegister(context, it) }

View File

@ -30,7 +30,7 @@ internal class API23AudioDeviceDetector(private val audioManager: AudioManager,
private val onAudioDeviceChangeRunner = Runnable { private val onAudioDeviceChangeRunner = Runnable {
val devices: MutableSet<CallAudioManager.Device> = HashSet() val devices: MutableSet<CallAudioManager.Device> = HashSet()
val deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_ALL) val deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
for (info in deviceInfos) { for (info in deviceInfos) {
when (info.type) { when (info.type) {
AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> devices.add(CallAudioManager.Device.WIRELESS_HEADSET) AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> devices.add(CallAudioManager.Device.WIRELESS_HEADSET)

View File

@ -18,6 +18,8 @@ package im.vector.app.features.call.audio
import android.content.Context import android.content.Context
import android.media.AudioManager import android.media.AudioManager
import android.os.Build
import androidx.core.content.getSystemService
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import timber.log.Timber import timber.log.Timber
import java.util.HashSet import java.util.HashSet
@ -25,7 +27,7 @@ import java.util.concurrent.Executors
class CallAudioManager(private val context: Context, val configChange: (() -> Unit)?) { class CallAudioManager(private val context: Context, val configChange: (() -> Unit)?) {
private val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager private val audioManager: AudioManager? = context.getSystemService()
private var audioDeviceDetector: AudioDeviceDetector? = null private var audioDeviceDetector: AudioDeviceDetector? = null
private var audioDeviceRouter: AudioDeviceRouter? = null private var audioDeviceRouter: AudioDeviceRouter? = null
@ -56,8 +58,11 @@ class CallAudioManager(private val context: Context, val configChange: (() -> Un
} }
private fun setup() { private fun setup() {
if (audioManager == null) {
return
}
audioDeviceDetector?.stop() audioDeviceDetector?.stop()
audioDeviceDetector = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { audioDeviceDetector = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
API23AudioDeviceDetector(audioManager, this) API23AudioDeviceDetector(audioManager, this)
} else { } else {
API21AudioDeviceDetector(context, audioManager, this) API21AudioDeviceDetector(context, audioManager, this)

View File

@ -16,7 +16,6 @@
package im.vector.app.features.call.webrtc package im.vector.app.features.call.webrtc
import im.vector.app.features.call.audio.CallAudioManager
import org.matrix.android.sdk.api.session.call.CallState import org.matrix.android.sdk.api.session.call.CallState
import org.matrix.android.sdk.api.session.call.MxPeerConnectionState import org.matrix.android.sdk.api.session.call.MxPeerConnectionState
import org.webrtc.DataChannel import org.webrtc.DataChannel

View File

@ -21,7 +21,6 @@ import android.hardware.camera2.CameraManager
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import im.vector.app.core.services.CallService import im.vector.app.core.services.CallService
import im.vector.app.core.utils.CountUpTimer import im.vector.app.core.utils.CountUpTimer
import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.CameraEventsHandlerAdapter import im.vector.app.features.call.CameraEventsHandlerAdapter
import im.vector.app.features.call.CameraProxy import im.vector.app.features.call.CameraProxy
import im.vector.app.features.call.CameraType import im.vector.app.features.call.CameraType
@ -92,7 +91,7 @@ class WebRtcCall(val mxCall: MxCall,
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 onCallBecomeActive: (WebRtcCall) -> Unit,
private val onCallEnded: (WebRtcCall) -> Unit) : MxCall.StateListener { private val onCallEnded: (String) -> Unit) : MxCall.StateListener {
interface Listener : MxCall.StateListener { interface Listener : MxCall.StateListener {
fun onCaptureStateChanged() {} fun onCaptureStateChanged() {}
@ -725,7 +724,7 @@ class WebRtcCall(val mxCall: MxCall,
GlobalScope.launch(dispatcher) { GlobalScope.launch(dispatcher) {
release() release()
} }
onCallEnded(this) onCallEnded(callId)
if (originatedByMe) { if (originatedByMe) {
if (wasRinging) { if (wasRinging) {
mxCall.reject() mxCall.reject()

View File

@ -21,9 +21,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.OnLifecycleEvent
import im.vector.app.ActiveSessionDataSource import im.vector.app.ActiveSessionDataSource
import im.vector.app.core.services.BluetoothHeadsetReceiver
import im.vector.app.core.services.CallService import im.vector.app.core.services.CallService
import im.vector.app.core.services.WiredHeadsetStateReceiver
import im.vector.app.features.call.VectorCallActivity import im.vector.app.features.call.VectorCallActivity
import im.vector.app.features.call.audio.CallAudioManager import im.vector.app.features.call.audio.CallAudioManager
import im.vector.app.features.call.utils.EglUtils import im.vector.app.features.call.utils.EglUtils
@ -186,22 +184,34 @@ class WebRtcCallManager @Inject constructor(
this.currentCall.setAndNotify(call) this.currentCall.setAndNotify(call)
} }
private fun onCallEnded(call: WebRtcCall) { private fun onCallEnded(callId: String) {
Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: ${call.mxCall.callId}") Timber.v("## VOIP WebRtcPeerConnectionManager onCall ended: $callId")
CallService.onCallTerminated(context, call.callId) val webRtcCall = callsByCallId.remove(callId) ?: return Unit.also {
callsByCallId.remove(call.mxCall.callId) Timber.v("On call ended for unknown call $callId")
callsByRoomId[call.mxCall.roomId]?.remove(call) }
if (getCurrentCall() == call) { CallService.onCallTerminated(context, callId)
callsByRoomId[webRtcCall.roomId]?.remove(webRtcCall)
if (getCurrentCall()?.callId == callId) {
val otherCall = getCalls().lastOrNull() val otherCall = getCalls().lastOrNull()
currentCall.setAndNotify(otherCall) currentCall.setAndNotify(otherCall)
} }
// This must be done in this thread // This must be done in this thread
executor.execute { executor.execute {
// There is no active calls
if (getCurrentCall() == null) { if (getCurrentCall() == null) {
Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one") Timber.v("## VOIP Dispose peerConnectionFactory as there is no need to keep one")
peerConnectionFactory?.dispose() peerConnectionFactory?.dispose()
peerConnectionFactory = null peerConnectionFactory = null
audioManager.setMode(CallAudioManager.Mode.DEFAULT) audioManager.setMode(CallAudioManager.Mode.DEFAULT)
// did we start background sync? so we should stop it
if (isInBackground) {
if (FcmHelper.isPushSupported()) {
currentSession?.stopAnyBackgroundSync()
} else {
// for fdroid we should not stop, it should continue syncing
// maybe we should restore default timeout/delay though?
}
}
} }
Timber.v("## VOIP WebRtcPeerConnectionManager close() executor done") Timber.v("## VOIP WebRtcPeerConnectionManager close() executor done")
} }
@ -225,7 +235,6 @@ class WebRtcCallManager @Inject constructor(
val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return val mxCall = currentSession?.callSignalingService()?.createOutgoingCall(signalingRoomId, otherUserId, isVideoCall) ?: return
val webRtcCall = createWebRtcCall(mxCall) val webRtcCall = createWebRtcCall(mxCall)
currentCall.setAndNotify(webRtcCall) currentCall.setAndNotify(webRtcCall)
//callAudioManager.startForCall(mxCall)
CallService.onOutgoingCallRinging( CallService.onOutgoingCallRinging(
context = context.applicationContext, context = context.applicationContext,
@ -261,6 +270,9 @@ class WebRtcCallManager @Inject constructor(
callsByCallId[mxCall.callId] = webRtcCall callsByCallId[mxCall.callId] = webRtcCall
callsByRoomId.getOrPut(mxCall.roomId) { ArrayList(1) } callsByRoomId.getOrPut(mxCall.roomId) { ArrayList(1) }
.add(webRtcCall) .add(webRtcCall)
if (getCurrentCall() == null) {
currentCall.setAndNotify(webRtcCall)
}
return webRtcCall return webRtcCall
} }
@ -268,18 +280,6 @@ class WebRtcCallManager @Inject constructor(
callsByRoomId[roomId]?.forEach { it.endCall(originatedByMe) } callsByRoomId[roomId]?.forEach { it.endCall(originatedByMe) }
} }
fun onWiredDeviceEvent(event: WiredHeadsetStateReceiver.HeadsetPlugEvent) {
Timber.v("## VOIP onWiredDeviceEvent $event")
getCurrentCall() ?: return
// sometimes we received un-wanted unplugged...
//callAudioManager.wiredStateChange(event)
}
fun onWirelessDeviceEvent(event: BluetoothHeadsetReceiver.BTHeadsetPlugEvent) {
Timber.v("## VOIP onWirelessDeviceEvent $event")
//callAudioManager.bluetoothStateChange(event.plugged)
}
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 (getCallsByRoomId(mxCall.roomId).isNotEmpty()) { if (getCallsByRoomId(mxCall.roomId).isNotEmpty()) {
@ -294,7 +294,6 @@ class WebRtcCallManager @Inject constructor(
createWebRtcCall(mxCall).apply { createWebRtcCall(mxCall).apply {
offerSdp = callInviteContent.offer offerSdp = callInviteContent.offer
} }
//callAudioManager.startForCall(mxCall)
// Start background service with notification // Start background service with notification
CallService.onIncomingCallRinging( CallService.onIncomingCallRinging(
context = context, context = context,
@ -367,21 +366,6 @@ class WebRtcCallManager @Inject constructor(
override fun onCallManagedByOtherSession(callId: String) { override fun onCallManagedByOtherSession(callId: String) {
Timber.v("## VOIP onCallManagedByOtherSession: $callId") Timber.v("## VOIP onCallManagedByOtherSession: $callId")
val webRtcCall = callsByCallId.remove(callId) onCallEnded(callId)
if (webRtcCall != null) {
callsByRoomId[webRtcCall.mxCall.roomId]?.remove(webRtcCall)
}
// TODO: handle this properly
CallService.onCallTerminated(context, callId)
// did we start background sync? so we should stop it
if (isInBackground) {
if (FcmHelper.isPushSupported()) {
currentSession?.stopAnyBackgroundSync()
} else {
// for fdroid we should not stop, it should continue syncing
// maybe we should restore default timeout/delay though?
}
}
} }
} }

View File

@ -208,6 +208,10 @@ class NotificationUtils @Inject constructor(private val context: Context,
}) })
} }
fun getChannel(channelId: String): NotificationChannel? {
return notificationManager.getNotificationChannel(channelId)
}
/** /**
* Build a polling thread listener notification * Build a polling thread listener notification
* *
@ -266,6 +270,11 @@ class NotificationUtils @Inject constructor(private val context: Context,
return notification return notification
} }
fun getChannelForIncomingCall(fromBg: Boolean): NotificationChannel? {
val notificationChannel = if (fromBg) CALL_NOTIFICATION_CHANNEL_ID else SILENT_NOTIFICATION_CHANNEL_ID
return getChannel(notificationChannel)
}
/** /**
* Build an incoming call notification. * Build an incoming call notification.
* This notification starts the VectorHomeActivity which is in charge of centralizing the incoming call flow. * This notification starts the VectorHomeActivity which is in charge of centralizing the incoming call flow.

View File

@ -44,7 +44,7 @@
android:textSize="16sp" android:textSize="16sp"
app:layout_constrainedWidth="true" app:layout_constrainedWidth="true"
app:layout_constraintBottom_toTopOf="@+id/bottomSheetActionSubTitle" app:layout_constraintBottom_toTopOf="@+id/bottomSheetActionSubTitle"
app:layout_constraintEnd_toStartOf="@+id/itemVerificationActionIcon" app:layout_constraintEnd_toStartOf="@+id/bottomSheetActionIcon"
app:layout_constraintStart_toEndOf="@+id/bottomSheetActionLeftIcon" app:layout_constraintStart_toEndOf="@+id/bottomSheetActionLeftIcon"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"