Started work on Bluetooth improvements for API leve 31+

This commit is contained in:
Juha Heinanen
2023-02-20 20:29:15 +02:00
parent 0c8cca2076
commit 7b1f11e1e2
4 changed files with 158 additions and 55 deletions

View File

@ -10,8 +10,10 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.database.ContentObserver
import android.media.*
import android.media.AudioManager.MODE_NORMAL
import android.media.AudioManager.RINGER_MODE_SILENT
import android.net.*
import android.net.wifi.WifiManager
@ -28,9 +30,9 @@ import android.widget.RemoteViews
import android.widget.Toast
import androidx.annotation.ColorRes
import androidx.annotation.Keep
import androidx.annotation.RequiresApi
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
@ -41,10 +43,10 @@ import java.io.File
import java.net.InetAddress
import java.nio.charset.StandardCharsets
import java.util.*
import kotlin.collections.ArrayList
import kotlin.concurrent.schedule
import kotlin.math.roundToInt
class BaresipService: Service() {
internal lateinit var intent: Intent
@ -244,19 +246,22 @@ class BaresipService: Service() {
when (state) {
BluetoothHeadset.STATE_CONNECTED -> {
Log.d(TAG, "Bluetooth headset is connected")
if (audioFocusRequest != null)
// Without delay, SCO_AUDIO_STATE_CONNECTING ->
// SCO_AUDIO_STATE_DISCONNECTED
Timer("Sco", false).schedule(1000) {
Log.d(TAG, "Starting Bluetooth SCO")
am.startBluetoothSco()
}
if (audioFocusRequest != null) {
//Handler(Looper.getMainLooper()).postDelayed({
//am.startBluetoothSco()
startBluetoothSco(am, 1000L)
//}, 1000)
}
//Timer("Sco", false).schedule(1000) {
// startBluetoothSco(am)
// }
}
BluetoothHeadset.STATE_DISCONNECTED -> {
Log.d(TAG, "Bluetooth headset is disconnected")
if (am.isBluetoothScoOn) {
if (isBluetoothScoOn(am)) {
Log.d(TAG, "Stopping Bluetooth SCO")
am.stopBluetoothSco()
stopBluetoothSco(am)
}
}
@ -271,12 +276,10 @@ class BaresipService: Service() {
}
BluetoothHeadset.STATE_AUDIO_DISCONNECTED -> {
Log.d(TAG, "Bluetooth headset audio is disconnected")
if (am.isBluetoothScoOn) {
Log.d(TAG, "Stopping Bluetooth SCO")
am.stopBluetoothSco()
if (isBluetoothScoOn(am)) {
stopBluetoothSco(am)
}
}
}
}
AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> {
@ -468,7 +471,7 @@ class BaresipService: Service() {
val vidMode = intent.getIntExtra("video", Api.VIDMODE_OFF)
stopRinging()
stopMediaPlayer()
requestAudioFocus(am, AudioAttributes.CONTENT_TYPE_SPEECH)
//requestAudioFocus(applicationContext, am, AudioAttributes.CONTENT_TYPE_SPEECH)
setCallVolume()
proximitySensing(true)
Api.ua_answer(uap, callp, vidMode)
@ -886,9 +889,8 @@ class BaresipService: Service() {
call.remove()
if (Call.calls().size == 0) {
resetCallVolume()
am.mode = AudioManager.MODE_NORMAL
am.stopBluetoothSco()
abandonAudioFocus(am)
Utils.clearCommunicationDevice(am)
proximitySensing(false)
}
val missed = call.startTime == null && call.dir == "in" && !call.rejected
@ -1188,11 +1190,12 @@ class BaresipService: Service() {
}
private fun startRinging() {
requestAudioFocus(am, AudioAttributes.CONTENT_TYPE_MUSIC)
am.mode = AudioManager.MODE_RINGTONE
requestAudioFocus(applicationContext, am, AudioAttributes.CONTENT_TYPE_MUSIC)
if (rt == null) {
val rtUri = RingtoneManager.getActualDefaultRingtoneUri(applicationContext,
RingtoneManager.TYPE_RINGTONE)
val rtUri = RingtoneManager.getActualDefaultRingtoneUri(
applicationContext,
RingtoneManager.TYPE_RINGTONE
)
rt = RingtoneManager.getRingtone(applicationContext, rtUri)
}
if (VERSION.SDK_INT >= 28) {
@ -1251,8 +1254,6 @@ class BaresipService: Service() {
}
private fun stopRinging() {
if (am.mode == AudioManager.MODE_RINGTONE)
abandonAudioFocus(am)
if (VERSION.SDK_INT < 28 && rtTimer != null) {
rtTimer!!.cancel()
rtTimer = null
@ -1307,13 +1308,10 @@ class BaresipService: Service() {
if (callVolume != 0 && origVolume.isEmpty()) {
for (streamType in listOf(AudioManager.STREAM_MUSIC, AudioManager.STREAM_VOICE_CALL)) {
origVolume[streamType] = am.getStreamVolume(streamType)
am.setStreamVolume(
streamType,
(callVolume * 0.1 * am.getStreamMaxVolume(streamType)).roundToInt(),
0
)
Log.d(TAG, "Orig/new $streamType volume is " +
"${origVolume[streamType]}/${am.getStreamVolume(streamType)}")
val maxVolume = am.getStreamMaxVolume(streamType)
am.setStreamVolume(streamType, (callVolume * 0.1 * maxVolume).roundToInt(), 0)
Log.d(TAG, "Orig/new/max $streamType volume is " +
"${origVolume[streamType]}/${am.getStreamVolume(streamType)}/$maxVolume")
}
}
}
@ -1490,11 +1488,8 @@ class BaresipService: Service() {
if (!isServiceClean) {
this.unregisterReceiver(bluetoothReceiver)
this.unregisterReceiver(hotSpotReceiver)
if (am.isBluetoothScoOn)
am.stopBluetoothSco()
am.mode = AudioManager.MODE_NORMAL
abandonAudioFocus(am)
stopRinging()
abandonAudioFocus(am)
stopMediaPlayer()
uas.clear()
callHistory.clear()
@ -1552,13 +1547,16 @@ class BaresipService: Service() {
private var audioFocusRequest: AudioFocusRequestCompat? = null
private var btAdapter: BluetoothAdapter? = null
fun requestAudioFocus(am: AudioManager, type: Int): Boolean {
fun requestAudioFocus(ctx: Context, am: AudioManager, type: Int): Boolean {
Log.d(TAG, "Requesting audio focus of type $type")
if (audioFocusRequest != null) {
if (audioFocusRequest!!.audioAttributesCompat.contentType == type)
if (audioFocusRequest!!.audioAttributesCompat.contentType == type) {
Log.d(TAG, "Already focused")
am.mode = AudioManager.MODE_IN_COMMUNICATION
return true
else
} else {
abandonAudioFocus(am)
}
}
val attributes = AudioAttributesCompat.Builder()
.setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION)
@ -1571,12 +1569,13 @@ class BaresipService: Service() {
if (AudioManagerCompat.requestAudioFocus(am, audioFocusRequest!!) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(TAG, "Audio focus granted")
isAudioFocused = true
if (isBluetoothHeadsetConnected() && !am.isBluetoothScoOn) {
if (isBluetoothHeadsetConnected(ctx) /* && !am.isBluetoothScoOn */) {
Log.d(TAG, "Starting Bluetooth SCO")
am.startBluetoothSco()
startBluetoothSco(am, 0L)
}
if (type == AudioAttributes.CONTENT_TYPE_SPEECH)
am.mode = AudioManager.MODE_IN_COMMUNICATION
am.mode = AudioManager.MODE_IN_COMMUNICATION
//if (type == AudioAttributes.CONTENT_TYPE_SPEECH || isBluetoothHeadsetConnected(ctx))
//am.mode = AudioManager.MODE_IN_COMMUNICATION
} else {
Log.i(TAG, "Audio focus denied")
audioFocusRequest = null
@ -1587,23 +1586,62 @@ class BaresipService: Service() {
private fun abandonAudioFocus(am: AudioManager) {
if (audioFocusRequest != null) {
Log.d(TAG, "Abandoning audio focus")
AudioManagerCompat.abandonAudioFocusRequest(am, audioFocusRequest!!)
audioFocusRequest = null
isAudioFocused = false
Log.d(TAG, "Audio focus abandoned")
stopBluetoothSco(am)
Utils.clearCommunicationDevice(am)
am.mode = MODE_NORMAL
}
}
@SuppressLint("MissingPermission")
private fun isBluetoothHeadsetConnected(): Boolean {
return if (VERSION.SDK_INT < 31)
private fun isBluetoothHeadsetConnected(ctx: Context): Boolean {
return if (ActivityCompat.checkSelfPermission(ctx, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED)
btAdapter != null && btAdapter!!.isEnabled &&
btAdapter!!.getProfileConnectionState(BluetoothHeadset.HEADSET) == BluetoothAdapter.STATE_CONNECTED
else
// getProfileConnectionState requires asking for BLUETOOTH_CONNECT permission
true
}
private fun isBluetoothScoOn(am: AudioManager): Boolean {
return if (VERSION.SDK_INT < 31)
am.isBluetoothScoOn
else
if (am.communicationDevice != null)
am.communicationDevice!!.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
else
false
}
private fun startBluetoothSco(am: AudioManager, delay: Long) {
Log.d(TAG, "Starting Bluetooth SCO")
Handler(Looper.getMainLooper()).postDelayed({
if (VERSION.SDK_INT < 35)
am.startBluetoothSco()
else
Utils.setCommunicationDevice(am, AudioDeviceInfo.TYPE_BLUETOOTH_SCO)
if (!isBluetoothScoOn(am)) {
Log.d(TAG, "Trying to start Bluetooth SCO again")
Handler(Looper.getMainLooper()).postDelayed({
am.startBluetoothSco()
}, delay)
}
}, delay)
}
private fun stopBluetoothSco(am: AudioManager) {
Log.d(TAG, "Stopping Bluetooth SCO")
Handler(Looper.getMainLooper()).postDelayed({
if (VERSION.SDK_INT < 35)
am.stopBluetoothSco()
else
am.clearCommunicationDevice()
}, 100)
}
}
init {

View File

@ -1,6 +1,7 @@
package com.tutpro.baresip
import android.Manifest
import android.Manifest.permission.*
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
@ -80,6 +81,7 @@ class MainActivity : AppCompatActivity() {
private var speakerIcon: MenuItem? = null
private lateinit var swipeRefresh: SwipeRefreshLayout
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
private lateinit var requestPermissionsLauncher: ActivityResultLauncher<Array<String>>
private lateinit var accountsRequest: ActivityResultLauncher<Intent>
private lateinit var chatRequests: ActivityResultLauncher<Intent>
private lateinit var configRequest: ActivityResultLauncher<Intent>
@ -676,6 +678,22 @@ class MainActivity : AppCompatActivity() {
}
}
requestPermissionsLauncher =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { isGranted: Map<String, Boolean> ->
Log.e(TAG, "********** isGranted $isGranted")
if (firstRun && isGranted.isEmpty()) {
with(MaterialAlertDialogBuilder(this, R.style.AlertDialogTheme)) {
setTitle("Permissions")
setMessage(getString(R.string.mic_and_bluetooth))
setPositiveButton(getString(R.string.ok)) { dialog, _ ->
requestPermissionsLauncher.launch(arrayOf(RECORD_AUDIO, BLUETOOTH_CONNECT))
dialog.dismiss()
}
show()
}
}
}
if (Preferences(applicationContext).displayTheme != AppCompatDelegate.getDefaultNightMode()) {
AppCompatDelegate.setDefaultNightMode(Preferences(applicationContext).displayTheme)
delegate.applyDayNight()
@ -685,11 +703,35 @@ class MainActivity : AppCompatActivity() {
override fun onStart() {
super.onStart()
Log.d(TAG, "Main onStart")
Log.e(TAG, "Main onStart")
if (!Utils.checkPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO)))
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.RECORD_AUDIO),
MIC_PERMISSION_REQUEST_CODE)
if (!Utils.checkPermissions(this, arrayOf(RECORD_AUDIO, BLUETOOTH_CONNECT)))
if (ActivityCompat.shouldShowRequestPermissionRationale(this, RECORD_AUDIO) ||
ActivityCompat.shouldShowRequestPermissionRationale(this, BLUETOOTH_CONNECT)) {
layout.showSnackBar(
binding.root,
if (!Utils.checkPermissions(this, arrayOf(RECORD_AUDIO)) &&
!Utils.checkPermissions(this, arrayOf(BLUETOOTH_CONNECT)))
getString(R.string.mic_and_bluetooth)
else if (!Utils.checkPermissions(this, arrayOf(RECORD_AUDIO)))
getString(R.string.no_calls)
else
getString(R.string.no_bluetooth),
Snackbar.LENGTH_INDEFINITE,
getString(R.string.ok)
) {
requestPermissionsLauncher.launch(arrayOf(RECORD_AUDIO, BLUETOOTH_CONNECT))
}
} else {
requestPermissionsLauncher.launch(
arrayOf(
RECORD_AUDIO,
BLUETOOTH_CONNECT
)
)
}
else
Log.e(TAG, "********** all permissions granted")
val action = intent.getStringExtra("action")
if (action != null) {
@ -1817,7 +1859,7 @@ class MainActivity : AppCompatActivity() {
hangupButton.visibility = View.VISIBLE
hangupButton.isEnabled = true
if (Build.VERSION.SDK_INT < 31) {
if (!BaresipService.requestAudioFocus(am, AudioAttributes.CONTENT_TYPE_SPEECH)) {
if (!BaresipService.requestAudioFocus(this, am, AudioAttributes.CONTENT_TYPE_SPEECH)) {
Toast.makeText(
applicationContext,
R.string.audio_focus_denied,
@ -1856,7 +1898,7 @@ class MainActivity : AppCompatActivity() {
}
}
am.addOnModeChangedListener(mainExecutor, audioModeChangedListener!!)
if (!BaresipService.requestAudioFocus(am, AudioAttributes.CONTENT_TYPE_SPEECH))
if (!BaresipService.requestAudioFocus(this, am, AudioAttributes.CONTENT_TYPE_SPEECH))
Toast.makeText(
applicationContext,
R.string.audio_focus_denied,

View File

@ -921,7 +921,7 @@ object Utils {
}
if (current != speakerDevice.type) {
if (speakerDevice.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) {
am.clearCommunicationDevice()
clearCommunicationDevice(am)
Log.d(TAG, "Setting com device to TYPE_BUILTIN_EARPIECE")
if (!am.setCommunicationDevice(speakerDevice))
Log.e(TAG, "Could not set com device")
@ -973,4 +973,24 @@ object Utils {
}
}
fun setCommunicationDevice(am: AudioManager, type: Int) {
val current = am.communicationDevice!!.type
Log.d(TAG, "Current com dev/mode $current/${am.mode}")
for (device in am.availableCommunicationDevices)
if (device.type == type) {
am.setCommunicationDevice(device)
break
}
Log.d(TAG, "New com dev/mode ${am.communicationDevice!!.type}/${am.mode}")
}
fun clearCommunicationDevice(am: AudioManager) {
if (Build.VERSION.SDK_INT >= 31) {
am.clearCommunicationDevice()
} else {
if (am.isSpeakerphoneOn)
am.isSpeakerphoneOn = false
}
}
}

View File

@ -508,8 +508,9 @@
</string>
<string name="no_notifications">You are not able to use this application without \"Notifications\"
permission.</string>
<string name="no_calls">You are not able to make or answer calls without \"Microphone\"
permission.</string>
<string name="no_calls">baresip needs \"Microphone\" permission for voice calls.</string>
<string name="no_bluetooth">baresip is not able to detect Bluetooth connectivity without
\"Nearby devices\" permission.</string>
<string name="no_video_calls">Grant \"Camera\" permission to make or answer video calls.</string>
<string name="no_backup">You are not able create backup without \"Storage\" permission.</string>
<string name="no_restore">You are not able restore backup without \"Storage\" permission.</string>
@ -519,4 +520,6 @@
<string name="show_password">Show Password</string>
<string name="no_network">No network connection!</string>
<string name="audio_focus_denied">Audio focus denied!</string>
<string name="mic_and_bluetooth">baresip needs \"Microphone\" permission for calls and
\"Nearby devices\" permission for Bluetooth detection.</string>
</resources>