Detect and recover from SCO interruptions.

This commit is contained in:
Nicholas 2023-04-04 18:31:45 -04:00 committed by Alex Hart
parent fef533f101
commit 0ab5bbb240
2 changed files with 55 additions and 9 deletions

View file

@ -288,7 +288,7 @@ class FullSignalAudioManager(context: Context, eventListener: EventListener?) :
}
val needBluetoothAudioStart = signalBluetoothManager.state == SignalBluetoothManager.State.AVAILABLE &&
(userSelectedAudioDevice == AudioDevice.NONE || userSelectedAudioDevice == AudioDevice.BLUETOOTH || autoSwitchToBluetooth)
(userSelectedAudioDevice == AudioDevice.NONE || userSelectedAudioDevice == AudioDevice.BLUETOOTH || autoSwitchToBluetooth) && !androidAudioManager.isBluetoothScoOn
val needBluetoothAudioStop = (signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTED || signalBluetoothManager.state == SignalBluetoothManager.State.CONNECTING) &&
(userSelectedAudioDevice != AudioDevice.NONE && userSelectedAudioDevice != AudioDevice.BLUETOOTH)

View file

@ -9,6 +9,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioManager
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.util.safeUnregisterReceiver
@ -32,7 +33,10 @@ class SignalBluetoothManager(
handler.assertHandlerThread()
return field
}
private set
private set(value) {
Log.d(TAG, "Updating STATE from $field to $value")
field = value
}
private var bluetoothAdapter: BluetoothAdapter? = null
private var bluetoothDevice: BluetoothDevice? = null
@ -78,6 +82,12 @@ class SignalBluetoothManager(
val bluetoothHeadsetFilter = IntentFilter().apply {
addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)
addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)
addAction(BluetoothHeadset.ACTION_VENDOR_SPECIFIC_HEADSET_EVENT)
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED)
addAction(AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED)
addAction(AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED)
}
bluetoothReceiver = BluetoothHeadsetBroadcastReceiver()
@ -129,12 +139,18 @@ class SignalBluetoothManager(
return false
}
if (androidAudioManager.isBluetoothScoOn) {
Log.i(TAG, "SCO connection already started")
return true
}
state = State.CONNECTING
androidAudioManager.startBluetoothSco()
androidAudioManager.isBluetoothScoOn = true
scoConnectionAttempts++
startTimer()
Log.i(TAG, "SCO audio started successfully.")
return true
}
@ -144,6 +160,7 @@ class SignalBluetoothManager(
Log.i(TAG, "stopScoAudio(): $state")
if (state != State.CONNECTING && state != State.CONNECTED) {
Log.i(TAG, "Skipping SCO stop due to state.")
return
}
@ -151,6 +168,7 @@ class SignalBluetoothManager(
androidAudioManager.stopBluetoothSco()
androidAudioManager.isBluetoothScoOn = false
state = State.DISCONNECTING
Log.i(TAG, "SCO audio stopped successfully.")
}
fun updateDevice() {
@ -172,14 +190,15 @@ class SignalBluetoothManager(
return
}
if (devices == null || devices.isEmpty()) {
if (devices.isNullOrEmpty()) {
bluetoothDevice = null
state = State.UNAVAILABLE
Log.i(TAG, "No connected bluetooth headset")
} else {
bluetoothDevice = devices[0]
state = State.AVAILABLE
Log.i(TAG, "Connected bluetooth headset. headsetState: ${bluetoothHeadset?.getConnectionState(bluetoothDevice)?.toStateString()} scoAudio: ${bluetoothHeadset?.isAudioConnected(bluetoothDevice)}")
val audioConnected = bluetoothHeadset?.isAudioConnected(bluetoothDevice) == true
state = if (audioConnected) State.CONNECTED else State.AVAILABLE
Log.i(TAG, "Connected bluetooth headset. headsetState: ${bluetoothHeadset?.getConnectionState(bluetoothDevice)?.toStateString()} scoAudio: $audioConnected")
}
}
@ -205,7 +224,7 @@ class SignalBluetoothManager(
var scoConnected = false
val devices: List<BluetoothDevice>? = bluetoothHeadset?.connectedDevices
if (devices != null && devices.isNotEmpty()) {
if (!devices.isNullOrEmpty()) {
bluetoothDevice = devices[0]
if (bluetoothHeadset?.isAudioConnected(bluetoothDevice) == true) {
Log.d(TAG, "Connected with $bluetoothDevice")
@ -248,6 +267,7 @@ class SignalBluetoothManager(
scoConnectionAttempts = 0
updateAudioDeviceState()
}
BluetoothHeadset.STATE_DISCONNECTED -> {
stopScoAudio()
updateAudioDeviceState()
@ -312,14 +332,40 @@ class SignalBluetoothManager(
}
}
} else if (intent.action == BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED) {
val connectionState: Int = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
handler.post {
if (state != State.UNINITIALIZED) {
if (wasAudioStateInterrupted(intent)) {
handler.post {
scoConnectionAttempts = 0
updateDevice()
}
} else if (state != State.UNINITIALIZED) {
handler.post {
val connectionState: Int = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED)
onAudioStateChanged(connectionState, isInitialStickyBroadcast)
}
}
} else if (intent.action == AudioManager.ACTION_SCO_AUDIO_STATE_UPDATED) {
if (wasScoDisconnected(intent)) {
handler.post(::updateAudioDeviceState)
}
} else {
Log.d(TAG, "Received broadcast of ${intent.action}")
}
}
private fun wasAudioStateInterrupted(intent: Intent): Boolean {
val connectionState: Int = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, -1)
val prevConnectionState: Int = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, -1)
val bluetoothAudioDevice: BluetoothDevice? = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)!!
Log.i(TAG, "${bluetoothAudioDevice?.name} audio state changed from $prevConnectionState to $connectionState")
return prevConnectionState == BluetoothHeadset.STATE_AUDIO_CONNECTED && connectionState == BluetoothHeadset.STATE_AUDIO_DISCONNECTED && bluetoothHeadset?.getConnectionState(bluetoothAudioDevice) == BluetoothProfile.STATE_CONNECTED
}
private fun wasScoDisconnected(intent: Intent): Boolean {
val scoState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, -1)
val prevScoState = intent.getIntExtra(AudioManager.EXTRA_SCO_AUDIO_PREVIOUS_STATE, -1)
Log.i(TAG, "SCO state updated from $prevScoState to $scoState")
return prevScoState == AudioManager.SCO_AUDIO_STATE_CONNECTED && scoState == AudioManager.SCO_AUDIO_STATE_DISCONNECTED
}
}
enum class State {