Fixed several audio focus/routing/volume control issues

This commit is contained in:
Juha Heinanen
2023-04-06 13:29:41 +03:00
parent de8d0f01ef
commit b77b7b8342
3 changed files with 92 additions and 81 deletions
@@ -50,7 +50,7 @@ import kotlin.math.roundToInt
class BaresipService: Service() { class BaresipService: Service() {
internal lateinit var intent: Intent internal lateinit var intent: Intent
internal lateinit var am: AudioManager private lateinit var am: AudioManager
private lateinit var nt: Ringtone private lateinit var nt: Ringtone
private lateinit var nm: NotificationManager private lateinit var nm: NotificationManager
private lateinit var snb: NotificationCompat.Builder private lateinit var snb: NotificationCompat.Builder
@@ -94,7 +94,7 @@ class BaresipService: Service() {
filesPath = filesDir.absolutePath filesPath = filesDir.absolutePath
pName = packageName pName = packageName
am = getSystemService(Context.AUDIO_SERVICE) as AudioManager am = applicationContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val ntUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext, val ntUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext,
RingtoneManager.TYPE_NOTIFICATION) RingtoneManager.TYPE_NOTIFICATION)
@@ -253,12 +253,12 @@ class BaresipService: Service() {
BluetoothHeadset.STATE_CONNECTED -> { BluetoothHeadset.STATE_CONNECTED -> {
Log.d(TAG, "Bluetooth headset is connected") Log.d(TAG, "Bluetooth headset is connected")
if (audioFocusRequest != null) if (audioFocusRequest != null)
startBluetoothSco(am, 1000L, 3) startBluetoothSco(applicationContext, 1000L, 3)
} }
BluetoothHeadset.STATE_DISCONNECTED -> { BluetoothHeadset.STATE_DISCONNECTED -> {
Log.d(TAG, "Bluetooth headset is disconnected") Log.d(TAG, "Bluetooth headset is disconnected")
if (audioFocusRequest != null) if (audioFocusRequest != null)
stopBluetoothSco(am) stopBluetoothSco(applicationContext)
} }
} }
@@ -287,6 +287,7 @@ class BaresipService: Service() {
} }
AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> { AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> {
Log.d(TAG, "Bluetooth headset SCO is disconnected") Log.d(TAG, "Bluetooth headset SCO is disconnected")
resetCallVolume()
} }
AudioManager.SCO_AUDIO_STATE_ERROR -> { AudioManager.SCO_AUDIO_STATE_ERROR -> {
Log.d(TAG, "Bluetooth headset SCO state ERROR") Log.d(TAG, "Bluetooth headset SCO state ERROR")
@@ -465,7 +466,6 @@ class BaresipService: Service() {
val vidMode = intent.getIntExtra("video", Api.VIDMODE_OFF) val vidMode = intent.getIntExtra("video", Api.VIDMODE_OFF)
stopRinging() stopRinging()
stopMediaPlayer() stopMediaPlayer()
//requestAudioFocus(applicationContext, am, AudioAttributes.CONTENT_TYPE_SPEECH)
setCallVolume() setCallVolume()
proximitySensing(true) proximitySensing(true)
Api.ua_answer(uap, callp, vidMode) Api.ua_answer(uap, callp, vidMode)
@@ -677,8 +677,15 @@ class BaresipService: Service() {
} }
"call incoming" -> { "call incoming" -> {
val peerUri = ev[1] val peerUri = ev[1]
var missed = false
if (!Utils.checkPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO))) { if (!Utils.checkPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO))) {
toast(getString(R.string.no_calls)) toast(getString(R.string.no_calls))
missed = true
} else if (!requestAudioFocus(applicationContext)) {
toast(getString(R.string.audio_focus_denied))
missed = true
}
if (missed) {
if (ua.account.callHistory) { if (ua.account.callHistory) {
CallHistory.add(CallHistory(aor, peerUri, "in")) CallHistory.add(CallHistory(aor, peerUri, "in"))
CallHistory.save() CallHistory.save()
@@ -889,8 +896,9 @@ class BaresipService: Service() {
call.remove() call.remove()
if (!Call.inCall()) { if (!Call.inCall()) {
resetCallVolume() resetCallVolume()
abandonAudioFocus(am) if (!abandonAudioFocus(applicationContext))
Utils.clearCommunicationDevice(am) Log.e(TAG, "Failed to abandon audio focus")
am.mode = MODE_NORMAL
proximitySensing(false) proximitySensing(false)
} }
val missed = call.startTime == null && call.dir == "in" && !call.rejected val missed = call.startTime == null && call.dir == "in" && !call.rejected
@@ -1190,7 +1198,6 @@ class BaresipService: Service() {
} }
private fun startRinging() { private fun startRinging() {
requestAudioFocus(applicationContext, am, AudioAttributes.CONTENT_TYPE_MUSIC)
if (rt == null) { if (rt == null) {
val rtUri = RingtoneManager.getActualDefaultRingtoneUri( val rtUri = RingtoneManager.getActualDefaultRingtoneUri(
applicationContext, applicationContext,
@@ -1305,7 +1312,7 @@ class BaresipService: Service() {
} }
private fun setCallVolume() { private fun setCallVolume() {
if (callVolume != 0 && origVolume.isEmpty()) { if (callVolume != 0)
for (streamType in listOf(AudioManager.STREAM_MUSIC, AudioManager.STREAM_VOICE_CALL)) { for (streamType in listOf(AudioManager.STREAM_MUSIC, AudioManager.STREAM_VOICE_CALL)) {
origVolume[streamType] = am.getStreamVolume(streamType) origVolume[streamType] = am.getStreamVolume(streamType)
val maxVolume = am.getStreamMaxVolume(streamType) val maxVolume = am.getStreamMaxVolume(streamType)
@@ -1313,15 +1320,14 @@ class BaresipService: Service() {
Log.d(TAG, "Orig/new/max $streamType volume is " + Log.d(TAG, "Orig/new/max $streamType volume is " +
"${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume") "${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume")
} }
}
} }
private fun resetCallVolume() { private fun resetCallVolume() {
for ((streamType, streamVolume) in origVolume) { if (callVolume != 0)
am.setStreamVolume(streamType, streamVolume, 0) for ((streamType, streamVolume) in origVolume) {
Log.d(TAG, "Reset $streamType volume to ${am.getStreamVolume(streamType)}") am.setStreamVolume(streamType, streamVolume, 0)
} Log.d(TAG, "Reset $streamType volume to ${am.getStreamVolume(streamType)}")
origVolume.clear() }
} }
@SuppressLint("WakelockTimeout") @SuppressLint("WakelockTimeout")
@@ -1499,7 +1505,7 @@ class BaresipService: Service() {
this.unregisterReceiver(bluetoothReceiver) this.unregisterReceiver(bluetoothReceiver)
this.unregisterReceiver(hotSpotReceiver) this.unregisterReceiver(hotSpotReceiver)
stopRinging() stopRinging()
abandonAudioFocus(am) abandonAudioFocus(applicationContext)
stopMediaPlayer() stopMediaPlayer()
uas.clear() uas.clear()
callHistory.clear() callHistory.clear()
@@ -1562,53 +1568,44 @@ class BaresipService: Service() {
private var audioFocusRequest: AudioFocusRequestCompat? = null private var audioFocusRequest: AudioFocusRequestCompat? = null
private var btAdapter: BluetoothAdapter? = null private var btAdapter: BluetoothAdapter? = null
fun requestAudioFocus(ctx: Context, am: AudioManager, type: Int): Boolean { fun requestAudioFocus(ctx: Context): Boolean {
Log.d(TAG, "Requesting audio focus of type $type") Log.d(TAG, "Requesting audio focus")
if (audioFocusRequest != null) { if (audioFocusRequest != null) {
if (audioFocusRequest!!.audioAttributesCompat.contentType == type) { Log.d(TAG, "Already focused")
Log.d(TAG, "Already focused") return true
am.mode = MODE_IN_COMMUNICATION
return true
} else {
abandonAudioFocus(am)
}
} }
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val attributes = AudioAttributesCompat.Builder() val attributes = AudioAttributesCompat.Builder()
.setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION) .setUsage(AudioAttributesCompat.USAGE_MEDIA)
.setContentType(type) .setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH)
.build() .build()
audioFocusRequest = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT) audioFocusRequest = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setAudioAttributes(attributes) .setAudioAttributes(attributes)
.setOnAudioFocusChangeListener { } .setOnAudioFocusChangeListener { }
.build() .build()
if (AudioManagerCompat.requestAudioFocus(am, audioFocusRequest!!) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (AudioManagerCompat.requestAudioFocus(am, audioFocusRequest!!) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "Audio focus granted") Log.d(TAG, "requestAudioFocus granted")
isAudioFocused = true if (isBluetoothHeadsetConnected(ctx))
if (isBluetoothHeadsetConnected(ctx) /* && !am.isBluetoothScoOn */) { startBluetoothSco(ctx, 250L, 3)
Log.d(TAG, "Starting Bluetooth SCO")
startBluetoothSco(am, 10L, 1)
}
am.mode = MODE_IN_COMMUNICATION
//if (type == AudioAttributes.CONTENT_TYPE_SPEECH || isBluetoothHeadsetConnected(ctx))
//am.mode = AudioManager.MODE_IN_COMMUNICATION
} else { } else {
Log.i(TAG, "Audio focus denied") Log.w(TAG, "requestAudioFocus denied")
audioFocusRequest = null audioFocusRequest = null
isAudioFocused = false
} }
return isAudioFocused return audioFocusRequest != null
} }
private fun abandonAudioFocus(am: AudioManager) { fun abandonAudioFocus(ctx: Context): Boolean {
if (audioFocusRequest != null) { if (audioFocusRequest != null) {
Log.d(TAG, "Abandoning audio focus") Log.d(TAG, "Abandoning audio focus")
AudioManagerCompat.abandonAudioFocusRequest(am, audioFocusRequest!!) val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
audioFocusRequest = null if (AudioManagerCompat.abandonAudioFocusRequest(am, audioFocusRequest!!) ==
isAudioFocused = false AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
stopBluetoothSco(am) audioFocusRequest = null
Utils.clearCommunicationDevice(am) if (isBluetoothHeadsetConnected(ctx))
am.mode = MODE_NORMAL stopBluetoothSco(ctx)
}
} }
return audioFocusRequest == null
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
@@ -1631,22 +1628,33 @@ class BaresipService: Service() {
false false
} }
private fun startBluetoothSco(am: AudioManager, delay: Long, count: Int) { private fun startBluetoothSco(ctx: Context, delay: Long, count: Int) {
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (isBluetoothScoOn(am)) {
Log.d(TAG, "Bluetooth SCO is already on")
return
}
Log.d(TAG, "Starting Bluetooth SCO at count $count") Log.d(TAG, "Starting Bluetooth SCO at count $count")
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
am.isBluetoothScoOn = true
if (VERSION.SDK_INT < 31) { if (VERSION.SDK_INT < 31) {
am.startBluetoothSco() am.startBluetoothSco()
} else { } else {
Utils.setCommunicationDevice(am, AudioDeviceInfo.TYPE_BLUETOOTH_SCO) Utils.setCommunicationDevice(am, AudioDeviceInfo.TYPE_BLUETOOTH_SCO)
} }
if (!isBluetoothScoOn(am) && count > 1) if (!isBluetoothScoOn(am) && count > 1)
startBluetoothSco(am, delay, count - 1) startBluetoothSco(ctx, delay, count - 1)
else
am.isBluetoothScoOn = true
}, delay) }, delay)
} }
private fun stopBluetoothSco(am: AudioManager) { private fun stopBluetoothSco(ctx: Context) {
Log.d(TAG, "Stopping Bluetooth SCO") Log.d(TAG, "Stopping Bluetooth SCO")
val am = ctx.getSystemService(Context.AUDIO_SERVICE) as AudioManager
if (!isBluetoothScoOn(am)) {
Log.d(TAG, "Bluetooth SCO is already off")
return
}
Handler(Looper.getMainLooper()).postDelayed({ Handler(Looper.getMainLooper()).postDelayed({
if (VERSION.SDK_INT < 31) if (VERSION.SDK_INT < 31)
am.stopBluetoothSco() am.stopBluetoothSco()
@@ -10,7 +10,6 @@ import android.content.*
import android.content.Intent.ACTION_CALL import android.content.Intent.ACTION_CALL
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration.ORIENTATION_PORTRAIT import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.media.AudioAttributes
import android.media.AudioManager import android.media.AudioManager
import android.net.Uri import android.net.Uri
import android.os.* import android.os.*
@@ -169,7 +168,7 @@ class MainActivity : AppCompatActivity() {
am = getSystemService(AUDIO_SERVICE) as AudioManager am = getSystemService(AUDIO_SERVICE) as AudioManager
kgm = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager kgm = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
serviceEventObserver = Observer<Event<Long>> { serviceEventObserver = Observer {
val event = it.getContentIfNotHandled() val event = it.getContentIfNotHandled()
Log.d(TAG, "Observed event $event") Log.d(TAG, "Observed event $event")
if (event != null && BaresipService.serviceEvents.isNotEmpty()) { if (event != null && BaresipService.serviceEvents.isNotEmpty()) {
@@ -352,7 +351,7 @@ class MainActivity : AppCompatActivity() {
callButton.setOnClickListener { callButton.setOnClickListener {
if (aorSpinner.selectedItemPosition >= 0) { if (aorSpinner.selectedItemPosition >= 0) {
if (Utils.checkPermissions(this, arrayOf(RECORD_AUDIO))) if (Utils.checkPermissions(this, arrayOf(RECORD_AUDIO)))
makeCall() makeCall()
else else
Toast.makeText(applicationContext, R.string.no_calls, Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, R.string.no_calls, Toast.LENGTH_SHORT).show()
} }
@@ -364,6 +363,7 @@ class MainActivity : AppCompatActivity() {
if (callRunnable != null) { if (callRunnable != null) {
callHandler.removeCallbacks(callRunnable!!) callHandler.removeCallbacks(callRunnable!!)
callRunnable = null callRunnable = null
BaresipService.abandonAudioFocus(applicationContext)
am.mode = AudioManager.MODE_NORMAL am.mode = AudioManager.MODE_NORMAL
showCall(ua) showCall(ua)
return@setOnClickListener return@setOnClickListener
@@ -372,6 +372,7 @@ class MainActivity : AppCompatActivity() {
if (audioModeChangedListener != null) { if (audioModeChangedListener != null) {
am.removeOnModeChangedListener(audioModeChangedListener!!) am.removeOnModeChangedListener(audioModeChangedListener!!)
audioModeChangedListener = null audioModeChangedListener = null
BaresipService.abandonAudioFocus(applicationContext)
am.mode = AudioManager.MODE_NORMAL am.mode = AudioManager.MODE_NORMAL
showCall(ua) showCall(ua)
return@setOnClickListener return@setOnClickListener
@@ -972,12 +973,12 @@ class MainActivity : AppCompatActivity() {
AudioManager.STREAM_MUSIC AudioManager.STREAM_MUSIC
when (keyCode) { when (keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP -> { KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP -> {
Log.d(TAG, "Adjusting volume $keyCode of stream $stream")
am.adjustStreamVolume(stream, am.adjustStreamVolume(stream,
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
AudioManager.ADJUST_LOWER else AudioManager.ADJUST_LOWER else
AudioManager.ADJUST_RAISE, AudioManager.ADJUST_RAISE,
AudioManager.FLAG_SHOW_UI) AudioManager.FLAG_SHOW_UI)
Log.d(TAG, "Adjusted volume $keyCode of stream $stream to ${am.getStreamVolume(stream)}")
return true return true
} }
} }
@@ -1803,6 +1804,8 @@ class MainActivity : AppCompatActivity() {
onHoldCall.newCall = null onHoldCall.newCall = null
call.remove() call.remove()
call.destroy() call.destroy()
if (!BaresipService.abandonAudioFocus(applicationContext))
Log.e(TAG, "Failed to abandon audio focus")
showCall(ua) showCall(ua)
false false
} }
@@ -1863,7 +1866,7 @@ class MainActivity : AppCompatActivity() {
val uri = if (Utils.isTelUri(uriText)) { val uri = if (Utils.isTelUri(uriText)) {
if (ua.account.telProvider == "") { if (ua.account.telProvider == "") {
Utils.alertView(this, getString(R.string.notice), Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.no_telephony_provider), aor)) String.format(getString(R.string.no_telephony_provider), aor))
return return
} }
Utils.telToSip(uriText, ua.account) Utils.telToSip(uriText, ua.account)
@@ -1872,7 +1875,10 @@ class MainActivity : AppCompatActivity() {
} }
if (!Utils.checkUri(uri)) { if (!Utils.checkUri(uri)) {
Utils.alertView(this, getString(R.string.notice), Utils.alertView(this, getString(R.string.notice),
String.format(getString(R.string.invalid_sip_or_tel_uri), uri)) String.format(getString(R.string.invalid_sip_or_tel_uri), uri))
} else if (!BaresipService.requestAudioFocus(applicationContext)) {
Toast.makeText(applicationContext, R.string.audio_focus_denied,
Toast.LENGTH_SHORT).show()
} else { } else {
callUri.isFocusable = false callUri.isFocusable = false
uaAdapter.notifyDataSetChanged() uaAdapter.notifyDataSetChanged()
@@ -1881,24 +1887,16 @@ class MainActivity : AppCompatActivity() {
hangupButton.visibility = View.VISIBLE hangupButton.visibility = View.VISIBLE
hangupButton.isEnabled = true hangupButton.isEnabled = true
if (Build.VERSION.SDK_INT < 31) { if (Build.VERSION.SDK_INT < 31) {
if (!BaresipService.requestAudioFocus(this, am, AudioAttributes.CONTENT_TYPE_SPEECH)) { callRunnable = Runnable {
Toast.makeText( callRunnable = null
applicationContext, if (!call(ua, uri)) {
R.string.audio_focus_denied, callButton.visibility = View.VISIBLE
Toast.LENGTH_SHORT callButton.isEnabled = true
).show() hangupButton.visibility = View.INVISIBLE
} else { hangupButton.isEnabled = false
callRunnable = Runnable {
callRunnable = null
if (!call(ua, uri)) {
callButton.visibility = View.VISIBLE
callButton.isEnabled = true
hangupButton.visibility = View.INVISIBLE
hangupButton.isEnabled = false
}
} }
callHandler.postDelayed(callRunnable!!, 1000)
} }
callHandler.postDelayed(callRunnable!!, 1000)
} else { } else {
audioModeChangedListener = AudioManager.OnModeChangedListener { mode -> audioModeChangedListener = AudioManager.OnModeChangedListener { mode ->
if (mode == AudioManager.MODE_IN_COMMUNICATION) { if (mode == AudioManager.MODE_IN_COMMUNICATION) {
@@ -1919,13 +1917,18 @@ class MainActivity : AppCompatActivity() {
"device ${am.communicationDevice!!.type}") "device ${am.communicationDevice!!.type}")
} }
} }
am.addOnModeChangedListener(mainExecutor, audioModeChangedListener!!) if (am.mode == AudioManager.MODE_IN_COMMUNICATION) {
if (!BaresipService.requestAudioFocus(this, am, AudioAttributes.CONTENT_TYPE_SPEECH)) if (!call(ua, uri)) {
Toast.makeText( callButton.visibility = View.VISIBLE
applicationContext, callButton.isEnabled = true
R.string.audio_focus_denied, hangupButton.visibility = View.INVISIBLE
Toast.LENGTH_SHORT hangupButton.isEnabled = false
).show() }
} else {
am.addOnModeChangedListener(mainExecutor, audioModeChangedListener!!)
Log.d(TAG, "Setting audio mode to MODE_IN_COMMUNICATION")
am.mode = AudioManager.MODE_IN_COMMUNICATION
}
} }
} }
} else { } else {
@@ -986,7 +986,7 @@ object Utils {
Log.d(TAG, "New com dev/mode ${am.communicationDevice!!.type}/${am.mode}") Log.d(TAG, "New com dev/mode ${am.communicationDevice!!.type}/${am.mode}")
} }
fun clearCommunicationDevice(am: AudioManager) { private fun clearCommunicationDevice(am: AudioManager) {
if (Build.VERSION.SDK_INT > 33) { if (Build.VERSION.SDK_INT > 33) {
am.clearCommunicationDevice() am.clearCommunicationDevice()
} else { } else {