VoIP: fix a bunch of issues
This commit is contained in:
parent
05361c13f1
commit
88e18a8640
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,7 +250,27 @@ class CallService : VectorService() {
|
||||||
mxCall = call.mxCall,
|
mxCall = call.mxCall,
|
||||||
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
|
title = opponentMatrixItem?.getBestName() ?: call.mxCall.opponentUserId
|
||||||
)
|
)
|
||||||
notificationManager.notify(callId.hashCode(), notification)
|
if (knownCalls.isEmpty()) {
|
||||||
|
startForeground(callId.hashCode(), notification)
|
||||||
|
} else {
|
||||||
|
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) {
|
||||||
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue