From a771228b02921ac4f91a0d85648a88277d11347e Mon Sep 17 00:00:00 2001 From: zxcpoiu Date: Tue, 10 Apr 2018 17:16:34 +0800 Subject: [PATCH] android: refactor audio route logic to fix some bluetooth bug --- .../incallmanager/InCallManagerModule.java | 135 ++++++++---------- 1 file changed, 63 insertions(+), 72 deletions(-) diff --git a/android/src/main/java/com/zxcpoiu/incallmanager/InCallManagerModule.java b/android/src/main/java/com/zxcpoiu/incallmanager/InCallManagerModule.java index ab8c14d..f64dfad 100644 --- a/android/src/main/java/com/zxcpoiu/incallmanager/InCallManagerModule.java +++ b/android/src/main/java/com/zxcpoiu/incallmanager/InCallManagerModule.java @@ -1775,6 +1775,9 @@ public void updateAudioDeviceState() { // Update the set of available audio devices. Set newAudioDevices = new HashSet<>(); + // always assume device has speaker phone + newAudioDevices.add(AudioDevice.SPEAKER_PHONE); + if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING || bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) { @@ -1782,100 +1785,61 @@ public void updateAudioDeviceState() { } if (hasWiredHeadset) { - // If a wired headset is connected, then it is the only possible option. newAudioDevices.add(AudioDevice.WIRED_HEADSET); - } else { - // No wired headset, hence the audio-device list can contain speaker - // phone (on a tablet), or speaker phone and earpiece (on mobile phone). - newAudioDevices.add(AudioDevice.SPEAKER_PHONE); - if (hasEarpiece()) { - newAudioDevices.add(AudioDevice.EARPIECE); - } } + + if (hasEarpiece()) { + newAudioDevices.add(AudioDevice.EARPIECE); + } + + // --- check whether user selected audio device is available + if (userSelectedAudioDevice != null + && userSelectedAudioDevice != AudioDevice.NONE + && !newAudioDevices.contains(userSelectedAudioDevice)) { + userSelectedAudioDevice = AudioDevice.NONE; + } + // Store state which is set to true if the device list has changed. boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices); // Update the existing audio device set. audioDevices = newAudioDevices; - // Correct user selected audio devices if needed. - if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_UNAVAILABLE - && userSelectedAudioDevice == AudioDevice.BLUETOOTH) { - // If BT is not available, it can't be the user selection. - userSelectedAudioDevice = AudioDevice.NONE; - } - if (hasWiredHeadset && userSelectedAudioDevice == AudioDevice.SPEAKER_PHONE) { - // If user selected speaker phone, but then plugged wired headset then make - // wired headset as user selected device. - userSelectedAudioDevice = AudioDevice.WIRED_HEADSET; - } - if (!hasWiredHeadset && userSelectedAudioDevice == AudioDevice.WIRED_HEADSET) { - // If user selected wired headset, but then unplugged wired headset then make - // speaker phone as user selected device. - userSelectedAudioDevice = AudioDevice.SPEAKER_PHONE; - } - // Need to start Bluetooth if it is available and user either selected it explicitly or - // user did not select any output device. - boolean needBluetoothAudioStart = - bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE - && (userSelectedAudioDevice == AudioDevice.NONE - || userSelectedAudioDevice == AudioDevice.BLUETOOTH); + AudioDevice newAudioDevice = getPreferredAudioDevice(); - // Need to stop Bluetooth audio if user selected different device and - // Bluetooth SCO connection is established or in the process. - boolean needBluetoothAudioStop = - (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED - || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING) - && (userSelectedAudioDevice != AudioDevice.NONE - && userSelectedAudioDevice != AudioDevice.BLUETOOTH); - - if (bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE - || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING - || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) { - Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", " - + "stop=" + needBluetoothAudioStop + ", " - + "BT state=" + bluetoothManager.getState()); - } - - // Start or stop Bluetooth SCO connection given states set earlier. - if (needBluetoothAudioStop) { + // --- stop bluetooth if needed + if (selectedAudioDevice == AudioDevice.BLUETOOTH + && newAudioDevice != AudioDevice.BLUETOOTH + && (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED + || bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTING) + ) { bluetoothManager.stopScoAudio(); bluetoothManager.updateDevice(); } - if (needBluetoothAudioStart && !needBluetoothAudioStop) { + // --- start bluetooth if needed + if (selectedAudioDevice != AudioDevice.BLUETOOTH + && newAudioDevice == AudioDevice.BLUETOOTH + && bluetoothManager.getState() == AppRTCBluetoothManager.State.HEADSET_AVAILABLE) { // Attempt to start Bluetooth SCO audio (takes a few second to start). if (!bluetoothManager.startScoAudio()) { // Remove BLUETOOTH from list of available devices since SCO failed. audioDevices.remove(AudioDevice.BLUETOOTH); audioDeviceSetUpdated = true; + if (userSelectedAudioDevice == AudioDevice.BLUETOOTH) { + userSelectedAudioDevice = AudioDevice.NONE; + } + newAudioDevice = getPreferredAudioDevice(); } } - - // Update selected audio device. - final AudioDevice newAudioDevice; - - if (bluetoothManager.getState() == AppRTCBluetoothManager.State.SCO_CONNECTED) { - // If a Bluetooth is connected, then it should be used as output audio - // device. Note that it is not sufficient that a headset is available; - // an active SCO channel must also be up and running. - newAudioDevice = AudioDevice.BLUETOOTH; - } else if (hasWiredHeadset) { - // If a wired headset is connected, but Bluetooth is not, then wired headset is used as - // audio device. - newAudioDevice = AudioDevice.WIRED_HEADSET; - } else if (userSelectedAudioDevice != null - && userSelectedAudioDevice != AudioDevice.NONE - && userSelectedAudioDevice != defaultAudioDevice) { - newAudioDevice = userSelectedAudioDevice; - } else { - // No wired headset and no Bluetooth, hence the audio-device list can contain speaker - // phone (on a tablet), or speaker phone and earpiece (on mobile phone). - // |defaultAudioDevice| contains either AudioDevice.SPEAKER_PHONE or AudioDevice.EARPIECE - // depending on the user's selgection. - newAudioDevice = defaultAudioDevice; + + if (newAudioDevice == AudioDevice.BLUETOOTH + && bluetoothManager.getState() != AppRTCBluetoothManager.State.SCO_CONNECTED) { + newAudioDevice = getPreferredAudioDevice(true); // --- skip bluetooth } + // Switch to new device but only if there has been any changes. if (newAudioDevice != selectedAudioDevice || audioDeviceSetUpdated) { + // Do the required device switch. setAudioDeviceInternal(newAudioDevice); Log.d(TAG, "New device status: " @@ -1910,4 +1874,31 @@ private WritableMap getAudioDeviceStatusMap() { return data; } + + private AudioDevice getPreferredAudioDevice() { + return getPreferredAudioDevice(false); + } + + private AudioDevice getPreferredAudioDevice(boolean skipBluetooth) { + final AudioDevice newAudioDevice; + + if (userSelectedAudioDevice != null && userSelectedAudioDevice != AudioDevice.NONE) { + newAudioDevice = userSelectedAudioDevice; + } else if (!skipBluetooth && audioDevices.contains(AudioDevice.BLUETOOTH)) { + // If a Bluetooth is connected, then it should be used as output audio + // device. Note that it is not sufficient that a headset is available; + // an active SCO channel must also be up and running. + newAudioDevice = AudioDevice.BLUETOOTH; + } else if (audioDevices.contains(AudioDevice.WIRED_HEADSET)) { + // If a wired headset is connected, but Bluetooth is not, then wired headset is used as + // audio device. + newAudioDevice = AudioDevice.WIRED_HEADSET; + } else if (audioDevices.contains(defaultAudioDevice)) { + newAudioDevice = defaultAudioDevice; + } else { + newAudioDevice = AudioDevice.SPEAKER_PHONE; + } + + return newAudioDevice; + } }