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