Started work on Bluetooth improvements for API leve 31+
This commit is contained in:
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
Reference in New Issue
Block a user