diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt index 5ef2ca6809..5a7b4bd4d8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt @@ -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) diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt index df99493533..a92410e554 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt @@ -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? = 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 {