- Initial implementation of audio through Bluetooth headset

This commit is contained in:
Juha Heinanen
2019-10-24 18:13:21 +03:00
parent 7f17a3df3e
commit 595380c29e
2 changed files with 141 additions and 32 deletions
@@ -3,19 +3,19 @@ package com.tutpro.baresip
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.* import android.app.*
import android.app.Notification.VISIBILITY_PUBLIC import android.app.Notification.VISIBILITY_PUBLIC
import android.app.PendingIntent.getActivity
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothHeadset
import android.content.* import android.content.*
import android.media.* import android.media.*
import android.net.* import android.net.*
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.IBinder import android.os.*
import android.os.PowerManager
import android.support.annotation.Keep import android.support.annotation.Keep
import android.support.v4.app.NotificationCompat import android.support.v4.app.NotificationCompat
import android.view.View import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import android.support.v4.content.LocalBroadcastManager import android.support.v4.content.LocalBroadcastManager
import android.os.Build
import android.os.Environment
import android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE import android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.provider.Settings import android.provider.Settings
@@ -25,6 +25,7 @@ import java.io.File
import java.net.InetAddress import java.net.InetAddress
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.util.* import java.util.*
import kotlin.concurrent.schedule
import kotlin.math.roundToInt import kotlin.math.roundToInt
class BaresipService: Service() { class BaresipService: Service() {
@@ -42,11 +43,13 @@ class BaresipService: Service() {
internal lateinit var partialWakeLock: PowerManager.WakeLock internal lateinit var partialWakeLock: PowerManager.WakeLock
internal lateinit var proximityWakeLock: PowerManager.WakeLock internal lateinit var proximityWakeLock: PowerManager.WakeLock
internal lateinit var fl: WifiManager.WifiLock internal lateinit var fl: WifiManager.WifiLock
internal lateinit var br: BroadcastReceiver
internal var rtTimer: Timer? = null internal var rtTimer: Timer? = null
internal var audioFocusRequest: AudioFocusRequest? = null internal var audioFocusRequest: AudioFocusRequest? = null
internal var audioFocused = false internal var audioFocused = false
internal var origCallVolume = -1 internal var origCallVolume = -1
internal val btAdapter = BluetoothAdapter.getDefaultAdapter()
override fun onCreate() { override fun onCreate() {
@@ -118,7 +121,84 @@ class BaresipService: Service() {
proximityWakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, proximityWakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK,
"com.tutpro.baresip:proximity_wakelog") "com.tutpro.baresip:proximity_wakelog")
br = object : BroadcastReceiver() {
override fun onReceive(ctx: Context, intent: Intent) {
when (intent.action) {
BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
BluetoothHeadset.STATE_DISCONNECTED)
when (state) {
BluetoothHeadset.STATE_CONNECTED -> {
Log.d(LOG_TAG, "Bluetooth headset is connected")
if (isAudioFocused()) {
// Without delay, SCO_AUDIO_STATE_CONNECTING ->
// SCO_AUDIO_STATE_DISCONNECTED
Timer("Sco", false).schedule(1000) {
Log.d(LOG_TAG, "Starting Bluetooth SCO")
am.startBluetoothSco()
}
}
}
BluetoothHeadset.STATE_DISCONNECTED -> {
Log.d(LOG_TAG, "Bluetooth headset is disconnected")
if (am.isBluetoothScoOn) {
Log.d(LOG_TAG, "Stopping Bluetooth SCO")
am.stopBluetoothSco()
}
}
}
}
BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
when (state) {
BluetoothHeadset.STATE_AUDIO_CONNECTED -> {
Log.d(LOG_TAG, "Bluetooth headset audio is connected")
}
BluetoothHeadset.STATE_AUDIO_DISCONNECTED -> {
Log.d(LOG_TAG, "Bluetooth headset audio is disconnected")
if (am.isBluetoothScoOn) {
Log.d(LOG_TAG, "Stopping Bluetooth SCO")
am.stopBluetoothSco()
}
}
}
}
AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED -> {
val state = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE,
AudioManager.SCO_AUDIO_STATE_DISCONNECTED)
when (state) {
AudioManager.SCO_AUDIO_STATE_CONNECTING -> {
Log.d(LOG_TAG, "Bluetooth headset SCO is connecting")
}
AudioManager.SCO_AUDIO_STATE_CONNECTED -> {
Log.d(LOG_TAG, "Bluetooth headset SCO is connected")
}
AudioManager.SCO_AUDIO_STATE_DISCONNECTED -> {
Log.d(LOG_TAG, "Bluetooth headset SCO is disconnected")
abandonAudioFocus()
}
AudioManager.SCO_AUDIO_STATE_ERROR -> {
Log.d(LOG_TAG, "Bluetooth headset SCO state ERROR")
}
}
}
}
}
}
if (btAdapter != null) {
val filter = IntentFilter()
filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)
filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)
filter.addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
this.registerReceiver(br, filter)
}
super.onCreate() super.onCreate()
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
@@ -320,6 +400,8 @@ class BaresipService: Service() {
override fun onDestroy() { override fun onDestroy() {
Log.d(LOG_TAG, "At Baresip Service onDestroy") Log.d(LOG_TAG, "At Baresip Service onDestroy")
super.onDestroy() super.onDestroy()
this.unregisterReceiver(br)
if (am.isBluetoothScoOn) am.stopBluetoothSco()
cleanService() cleanService()
if (isServiceRunning) { if (isServiceRunning) {
val broadcastIntent = Intent("com.tutpro.baresip.Restart") val broadcastIntent = Intent("com.tutpro.baresip.Restart")
@@ -508,10 +590,8 @@ class BaresipService: Service() {
CallHistory.save() CallHistory.save()
call.hasHistory = true call.hasHistory = true
} }
if (call.dir == "in") { stopRinging()
stopRinging() am.mode = AudioManager.MODE_IN_COMMUNICATION
am.mode = AudioManager.MODE_IN_COMMUNICATION
}
if (!isAudioFocused()) { if (!isAudioFocused()) {
requestAudioFocus(AudioManager.STREAM_VOICE_CALL) requestAudioFocus(AudioManager.STREAM_VOICE_CALL)
setCallVolume() setCallVolume()
@@ -591,21 +671,26 @@ class BaresipService: Service() {
return return
} }
Log.d(LOG_TAG, "AoR $aor call $callp is closed") Log.d(LOG_TAG, "AoR $aor call $callp is closed")
if (call.status == "incoming") stopRinging() stopRinging()
calls.remove(call) calls.remove(call)
if (Call.calls().size == 0) {
resetCallVolume()
am.mode = AudioManager.MODE_NORMAL
if (am.isSpeakerphoneOn) am.isSpeakerphoneOn = false
if (am.isBluetoothScoOn) {
Log.d(LOG_TAG, "Stopping Bluetooth SCO")
am.stopBluetoothSco()
} else {
abandonAudioFocus()
}
speakerPhone = false
proximitySensing(false)
}
if (ua.account.callHistory && !call.hasHistory) { if (ua.account.callHistory && !call.hasHistory) {
CallHistory.add(CallHistory(aor, call.peerURI, call.dir, false)) CallHistory.add(CallHistory(aor, call.peerURI, call.dir, false))
CallHistory.save() CallHistory.save()
if (call.dir == "in") ua.account.missedCalls = true if (call.dir == "in") ua.account.missedCalls = true
} }
if (Call.calls().size == 0) {
abandonAudioFocus()
resetCallVolume()
am.mode = AudioManager.MODE_NORMAL
if (am.isSpeakerphoneOn) am.isSpeakerphoneOn = false
speakerPhone = false
proximitySensing(false)
}
if (!Utils.isVisible()) if (!Utils.isVisible())
return return
} }
@@ -742,7 +827,7 @@ class BaresipService: Service() {
val intent = Intent(this, MainActivity::class.java) val intent = Intent(this, MainActivity::class.java)
.setAction(Intent.ACTION_MAIN) .setAction(Intent.ACTION_MAIN)
.addCategory(Intent.CATEGORY_LAUNCHER) .addCategory(Intent.CATEGORY_LAUNCHER)
val pi = PendingIntent.getActivity(this, STATUS_REQ_CODE, intent, 0) val pi = getActivity(this, STATUS_REQ_CODE, intent, 0)
snb.setVisibility(VISIBILITY_PUBLIC) snb.setVisibility(VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_stat) .setSmallIcon(R.drawable.ic_stat)
.setContentIntent(pi) .setContentIntent(pi)
@@ -770,10 +855,12 @@ class BaresipService: Service() {
nm.notify(STATUS_NOTIFICATION_ID, snb.build()) nm.notify(STATUS_NOTIFICATION_ID, snb.build())
} }
private fun requestAudioFocus(streamType: Int) { private fun requestAudioFocus(streamType: Int) {
if ((Build.VERSION.SDK_INT >= 26) && (audioFocusRequest == null)) { if ((Build.VERSION.SDK_INT >= 26) && (audioFocusRequest == null)) {
@TargetApi(26) @TargetApi(26)
audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run { audioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE).run {
setAudioAttributes(AudioAttributes.Builder().run { setAudioAttributes(AudioAttributes.Builder().run {
setLegacyStreamType(streamType) setLegacyStreamType(streamType)
build() build()
@@ -783,14 +870,20 @@ class BaresipService: Service() {
@TargetApi(26) @TargetApi(26)
if (am.requestAudioFocus(audioFocusRequest!!) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (am.requestAudioFocus(audioFocusRequest!!) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(LOG_TAG, "Audio focus granted for stream $streamType") Log.d(LOG_TAG, "Audio focus granted for stream $streamType")
if (isBluetoothHeadsetConnected() && !am.isBluetoothScoOn) {
Log.d(LOG_TAG, "Starting Bluetooth Sco")
am.startBluetoothSco()
}
} else { } else {
Log.d(LOG_TAG, "Audio focus denied") Log.d(LOG_TAG, "Audio focus denied")
audioFocusRequest = null audioFocusRequest = null
} }
} else { } else {
if (am.requestAudioFocus(null, streamType, AudioManager.AUDIOFOCUS_GAIN) == if (am.requestAudioFocus(null, streamType, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE) ==
AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(LOG_TAG, "Audio focus granted for stream $streamType") Log.d(LOG_TAG, "Audio focus granted for stream $streamType")
if (isBluetoothHeadsetConnected() && !am.isBluetoothScoOn)
am.startBluetoothSco()
audioFocused = true audioFocused = true
} else { } else {
Log.d(LOG_TAG, "Audio focus denied") Log.d(LOG_TAG, "Audio focus denied")
@@ -799,6 +892,12 @@ class BaresipService: Service() {
} }
} }
private fun isBluetoothHeadsetConnected(): Boolean {
return (btAdapter != null) && btAdapter.isEnabled &&
(btAdapter.getProfileConnectionState(BluetoothHeadset.HEADSET) ==
BluetoothHeadset.STATE_CONNECTED)
}
private fun isAudioFocused(): Boolean { private fun isAudioFocused(): Boolean {
if (Build.VERSION.SDK_INT >= 26) if (Build.VERSION.SDK_INT >= 26)
return audioFocusRequest != null return audioFocusRequest != null
@@ -809,17 +908,24 @@ class BaresipService: Service() {
private fun abandonAudioFocus() { private fun abandonAudioFocus() {
if (Build.VERSION.SDK_INT >= 26) { if (Build.VERSION.SDK_INT >= 26) {
if (audioFocusRequest != null) { if (audioFocusRequest != null) {
am.abandonAudioFocusRequest(audioFocusRequest!!) if (am.abandonAudioFocusRequest(audioFocusRequest!!) ==
audioFocusRequest = null AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
Log.d(LOG_TAG, "Audio focus abandoned")
audioFocusRequest = null
} else {
Log.d(LOG_TAG, "Failed to abandon audio focus")
}
} }
} else { } else {
if (audioFocused) { if (audioFocused) {
am.abandonAudioFocus(null) if (am.abandonAudioFocus(null) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
audioFocused = false Log.d(LOG_TAG, "Audio focus abandoned")
audioFocused = false
} else {
Log.d(LOG_TAG, "Failed to abandon audio focus")
}
} }
} }
if (isAudioFocused())
Log.w(LOG_TAG, "Failed to abandon audio focus")
} }
private fun startRinging() { private fun startRinging() {
@@ -842,12 +948,13 @@ class BaresipService: Service() {
} }
private fun stopRinging() { private fun stopRinging() {
if ((Build.VERSION.SDK_INT < 28) && (rtTimer != null)) { if (am.mode == AudioManager.MODE_RINGTONE) {
rtTimer!!.cancel() if ((Build.VERSION.SDK_INT < 28) && (rtTimer != null)) {
rtTimer = null rtTimer!!.cancel()
rtTimer = null
}
rt.stop()
} }
rt.stop()
abandonAudioFocus()
} }
private fun setCallVolume() { private fun setCallVolume() {
@@ -110,6 +110,7 @@ class MainActivity : AppCompatActivity() {
override fun onTick(millisUntilFinished: Long) { override fun onTick(millisUntilFinished: Long) {
Log.d("Baresip", "Seconds remaining: ${millisUntilFinished / 1000}") Log.d("Baresip", "Seconds remaining: ${millisUntilFinished / 1000}")
} }
override fun onFinish() { override fun onFinish() {
when (stopState) { when (stopState) {
"initial" -> { "initial" -> {
@@ -440,7 +441,8 @@ class MainActivity : AppCompatActivity() {
if (intent.hasExtra("action")) if (intent.hasExtra("action"))
// MainActivity was not visible when call, message, or transfer request came in // MainActivity was not visible when call, message, or transfer request came in
handleIntent(intent) handleIntent(intent)
}
} // OnCreate
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
// Called when MainActivity already exists at the top of current task // Called when MainActivity already exists at the top of current task