Better handling of Bluetooth connections/disconnections during calls.

This commit is contained in:
Nicholas 2022-09-28 15:35:46 -04:00 committed by Cody Henthorne
parent aaf8bf3280
commit aeb5a9cf57
4 changed files with 52 additions and 31 deletions

View file

@ -35,7 +35,7 @@ public final class WebRtcUtil {
public static @NonNull LockManager.PhoneState getInCallPhoneState(@NonNull Context context) { public static @NonNull LockManager.PhoneState getInCallPhoneState(@NonNull Context context) {
AudioManagerCompat audioManager = ApplicationDependencies.getAndroidCallAudioManager(); AudioManagerCompat audioManager = ApplicationDependencies.getAndroidCallAudioManager();
if (audioManager.isSpeakerphoneOn() || audioManager.isBluetoothScoOn() || audioManager.isWiredHeadsetOn()) { if (audioManager.isSpeakerphoneOn() || audioManager.isBluetoothConnected() || audioManager.isWiredHeadsetOn()) {
return LockManager.PhoneState.IN_HANDS_FREE_CALL; return LockManager.PhoneState.IN_HANDS_FREE_CALL;
} else { } else {
return LockManager.PhoneState.IN_CALL; return LockManager.PhoneState.IN_CALL;

View file

@ -0,0 +1,29 @@
package org.thoughtcrime.securesms.webrtc.audio
import android.media.AudioDeviceInfo
import androidx.annotation.RequiresApi
@RequiresApi(31)
object AudioDeviceMapping {
private val systemDeviceTypeMap: Map<SignalAudioManager.AudioDevice, List<Int>> = mapOf(
SignalAudioManager.AudioDevice.BLUETOOTH to listOf(AudioDeviceInfo.TYPE_BLUETOOTH_SCO, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, AudioDeviceInfo.TYPE_BLE_HEADSET),
SignalAudioManager.AudioDevice.EARPIECE to listOf(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE),
SignalAudioManager.AudioDevice.SPEAKER_PHONE to listOf(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE),
SignalAudioManager.AudioDevice.WIRED_HEADSET to listOf(AudioDeviceInfo.TYPE_WIRED_HEADSET, AudioDeviceInfo.TYPE_USB_HEADSET),
SignalAudioManager.AudioDevice.NONE to emptyList()
)
@JvmStatic
fun getEquivalentPlatformTypes(audioDevice: SignalAudioManager.AudioDevice): List<Int> {
return systemDeviceTypeMap[audioDevice]!!
}
@JvmStatic
fun fromPlatformType(type: Int): SignalAudioManager.AudioDevice {
for (kind in SignalAudioManager.AudioDevice.values()) {
if (getEquivalentPlatformTypes(kind).contains(type)) return kind
}
return SignalAudioManager.AudioDevice.NONE
}
}

View file

@ -52,6 +52,15 @@ public abstract class AudioManagerCompat {
audioManager.stopBluetoothSco(); audioManager.stopBluetoothSco();
} }
public boolean isBluetoothConnected() {
if (Build.VERSION.SDK_INT >= 31) {
final SignalAudioManager.AudioDevice audioDevice = AudioDeviceMapping.fromPlatformType(audioManager.getCommunicationDevice().getType());
return SignalAudioManager.AudioDevice.BLUETOOTH == audioDevice;
} else {
return isBluetoothScoOn();
}
}
public boolean isBluetoothScoOn() { public boolean isBluetoothScoOn() {
return audioManager.isBluetoothScoOn(); return audioManager.isBluetoothScoOn();
} }

View file

@ -18,7 +18,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener?) : SignalAudioManager(context, eventListener) { class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener?) : SignalAudioManager(context, eventListener) {
private val TAG = Log.tag(FullSignalAudioManagerApi31::class.java) private val TAG = Log.tag(FullSignalAudioManagerApi31::class.java)
private var defaultDevice = AudioDevice.NONE private var defaultDevice = AudioDevice.EARPIECE
private val deviceCallback = object : AudioDeviceCallback() { private val deviceCallback = object : AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) { override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>) {
@ -26,7 +26,7 @@ class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener
if (state == State.RUNNING) { if (state == State.RUNNING) {
// Switch to any new audio devices immediately. // Switch to any new audio devices immediately.
Log.i(TAG, "onAudioDevicesAdded $addedDevices") Log.i(TAG, "onAudioDevicesAdded $addedDevices")
val firstNewlyAddedDevice = addedDevices.firstNotNullOf { fromPlatformType(it.type) } val firstNewlyAddedDevice = addedDevices.firstNotNullOf { AudioDeviceMapping.fromPlatformType(it.type) }
selectAudioDevice(null, firstNewlyAddedDevice) selectAudioDevice(null, firstNewlyAddedDevice)
} }
} }
@ -34,32 +34,10 @@ class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) { override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>) {
super.onAudioDevicesRemoved(removedDevices) super.onAudioDevicesRemoved(removedDevices)
if (state == State.RUNNING) { if (state == State.RUNNING) {
val currentDevice = androidAudioManager.communicationDevice audioDeviceChangedCallback()
if (currentDevice != null && removedDevices.map { it.address }.contains(currentDevice.address)) {
selectAudioDevice(null, defaultDevice)
} }
} }
} }
}
private val systemDeviceTypeMap: Map<AudioDevice, List<Int>> = mapOf(
AudioDevice.BLUETOOTH to listOf(AudioDeviceInfo.TYPE_BLUETOOTH_SCO, AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, AudioDeviceInfo.TYPE_BLE_HEADSET),
AudioDevice.EARPIECE to listOf(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE),
AudioDevice.SPEAKER_PHONE to listOf(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER_SAFE),
AudioDevice.WIRED_HEADSET to listOf(AudioDeviceInfo.TYPE_WIRED_HEADSET, AudioDeviceInfo.TYPE_USB_HEADSET),
AudioDevice.NONE to emptyList()
)
private fun getEquivalentPlatformTypes(audioDevice: AudioDevice): List<Int> {
return systemDeviceTypeMap[audioDevice]!!
}
private fun fromPlatformType(type: Int): AudioDevice {
for (kind in AudioDevice.values()) {
if (getEquivalentPlatformTypes(kind).contains(type)) return kind
}
return AudioDevice.NONE
}
override fun setDefaultAudioDevice(recipientId: RecipientId?, newDefaultDevice: AudioDevice, clearUserEarpieceSelection: Boolean) { override fun setDefaultAudioDevice(recipientId: RecipientId?, newDefaultDevice: AudioDevice, clearUserEarpieceSelection: Boolean) {
defaultDevice = newDefaultDevice defaultDevice = newDefaultDevice
@ -83,9 +61,9 @@ class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener
handler.postDelayed({ androidAudioManager.requestCallAudioFocus() }, 500) handler.postDelayed({ androidAudioManager.requestCallAudioFocus() }, 500)
} }
if (androidAudioManager.availableCommunicationDevices.any { getEquivalentPlatformTypes(AudioDevice.BLUETOOTH).contains(it.type) }) { if (androidAudioManager.availableCommunicationDevices.any { AudioDeviceMapping.getEquivalentPlatformTypes(AudioDevice.BLUETOOTH).contains(it.type) }) {
selectAudioDevice(null, AudioDevice.BLUETOOTH) selectAudioDevice(null, AudioDevice.BLUETOOTH)
} else if (androidAudioManager.availableCommunicationDevices.any { getEquivalentPlatformTypes(AudioDevice.WIRED_HEADSET).contains(it.type) }) { } else if (androidAudioManager.availableCommunicationDevices.any { AudioDeviceMapping.getEquivalentPlatformTypes(AudioDevice.WIRED_HEADSET).contains(it.type) }) {
selectAudioDevice(null, AudioDevice.WIRED_HEADSET) selectAudioDevice(null, AudioDevice.WIRED_HEADSET)
} }
@ -112,11 +90,11 @@ class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener
val devices: List<AudioDeviceInfo> = androidAudioManager.availableCommunicationDevices val devices: List<AudioDeviceInfo> = androidAudioManager.availableCommunicationDevices
try { try {
val chosenDevice: AudioDeviceInfo = devices.first { getEquivalentPlatformTypes(device).contains(it.type) } val chosenDevice: AudioDeviceInfo = devices.first { AudioDeviceMapping.getEquivalentPlatformTypes(device).contains(it.type) }
val result = androidAudioManager.setCommunicationDevice(chosenDevice) val result = androidAudioManager.setCommunicationDevice(chosenDevice)
if (result) { if (result) {
Log.i(TAG, "Set active device to ID ${chosenDevice.id}, type ${chosenDevice.type}") Log.i(TAG, "Set active device to ID ${chosenDevice.id}, type ${chosenDevice.type}")
eventListener?.onAudioDeviceChanged(activeDevice = device, devices = devices.map { fromPlatformType(it.type) }.toSet()) eventListener?.onAudioDeviceChanged(activeDevice = device, devices = devices.map { AudioDeviceMapping.fromPlatformType(it.type) }.toSet())
} else { } else {
Log.w(TAG, "Setting device $chosenDevice failed.") Log.w(TAG, "Setting device $chosenDevice failed.")
} }
@ -125,13 +103,18 @@ class FullSignalAudioManagerApi31(context: Context, eventListener: EventListener
} }
} }
private fun audioDeviceChangedCallback() {
val activeDevice: AudioDevice = AudioDeviceMapping.fromPlatformType(androidAudioManager.communicationDevice.type)
val devices: Set<AudioDevice> = androidAudioManager.availableCommunicationDevices.map { AudioDeviceMapping.fromPlatformType(it.type) }.toSet()
eventListener?.onAudioDeviceChanged(activeDevice, devices)
}
override fun startIncomingRinger(ringtoneUri: Uri?, vibrate: Boolean) { override fun startIncomingRinger(ringtoneUri: Uri?, vibrate: Boolean) {
Log.i(TAG, "startIncomingRinger(): uri: ${if (ringtoneUri != null) "present" else "null"} vibrate: $vibrate") Log.i(TAG, "startIncomingRinger(): uri: ${if (ringtoneUri != null) "present" else "null"} vibrate: $vibrate")
androidAudioManager.mode = AudioManager.MODE_RINGTONE androidAudioManager.mode = AudioManager.MODE_RINGTONE
if (androidAudioManager.isMicrophoneMute) { if (androidAudioManager.isMicrophoneMute) {
androidAudioManager.isMicrophoneMute = false androidAudioManager.isMicrophoneMute = false
} }
setDefaultAudioDevice(null, AudioDevice.SPEAKER_PHONE, false)
incomingRinger.start(ringtoneUri, vibrate) incomingRinger.start(ringtoneUri, vibrate)
} }